<?php

namespace FleekDash\Services;

defined('ABSPATH') || exit;

if (!\function_exists('fleekdash_log_debug')) {
    require_once FLEEKDASH_PLUGIN_DIR . 'includes/Core/debug-helpers.php';
}

/**
 * Handles HTTP download and ZIP validation for language packs.
 * Extracted from LanguageDownloadController to satisfy SonarQube S1448 (max 20 methods per class).
 *
 * @package FleekDash\Services
 * @since 2.2.0
 */
class LanguagePackDownloader
{
    private string $r2CdnUrl;

    private string $localCacheDir;

    public function __construct(string $r2CdnUrl, string $localCacheDir)
    {
        $this->r2CdnUrl = $r2CdnUrl;
        $this->localCacheDir = $localCacheDir;
    }

    /**
     * @param array<string, mixed> $langData
     * @return array{error: \WP_REST_Response}|array{tempFile: string}
     */
    public function downloadToTemp(string $locale, array $langData)
    {
        $downloadUrl = $this->r2CdnUrl . '/' . $langData['url'];
        if (!file_exists($this->localCacheDir)) {
            wp_mkdir_p($this->localCacheDir);
        }
        if (!function_exists('download_url')) {
            require_once ABSPATH . 'wp-admin/includes/file.php';
        }

        $result = $this->verifyDownloadUrl($downloadUrl, $locale);
        if ($result === []) {
            $tempFile = get_temp_dir() . 'fleekdash-' . $locale . '-' . wp_generate_password(8, false) . '.zip';
            fleekdash_log_info("Starting download for language {$locale} from: {$downloadUrl}");
            $result = $this->performHttpDownload($downloadUrl, $tempFile, $locale);
        }
        if ($result !== [] && isset($result['error'])) {
            return $result;
        }
        if ($result === []) {
            $result = $this->validateDownloadedZipFile($tempFile ?? '', $locale, $langData);
        }
        return isset($result['error']) ? $result : ['tempFile' => $tempFile];
    }

    /**
     * @return array{error: \WP_REST_Response}|array{}
     */
    private function verifyDownloadUrl(string $downloadUrl, string $locale)
    {
        $error = null;
        $headResponse = wp_remote_head($downloadUrl, [
            'timeout' => 10,
            'sslverify' => true,
        ]);

        if (is_wp_error($headResponse)) {
            fleekdash_log_error("Failed to verify download URL for {$locale}: " . $headResponse->get_error_message());
            $error = ResponseService::error('Failed to verify download URL: ' . $headResponse->get_error_message(), null, 400);
        } elseif (wp_remote_retrieve_response_code($headResponse) !== 200) {
            $responseCode = wp_remote_retrieve_response_code($headResponse);
            fleekdash_log_error("Download URL for {$locale} returned status {$responseCode} (expected 200)");
            $error = ResponseService::error("Language pack not found on server (HTTP {$responseCode}). Please contact support or try again later.", null, 404);
        } else {
            $contentType = wp_remote_retrieve_header($headResponse, 'content-type');
            $validZip = $contentType && (strpos(strtolower($contentType), 'application/zip') !== false || strpos(strtolower($contentType), 'application/x-zip') !== false);
            if ($contentType && !$validZip) {
                fleekdash_log_error("Download URL for {$locale} has invalid content-type: {$contentType}");
                $error = ResponseService::error('Invalid file type on server. Expected ZIP file.', null, 400);
            }
        }
        return $error !== null ? ['error' => $error] : [];
    }

    /**
     * @return array{error: \WP_REST_Response}|array{}
     */
    private function performHttpDownload(string $downloadUrl, string $tempFile, string $locale)
    {
        $response = \wp_remote_get($downloadUrl, [
            'timeout' => 300,
            'sslverify' => true,
            'stream' => true,
            'filename' => $tempFile,
            'headers' => ['Accept' => 'application/zip, application/x-zip-compressed, */*'],
        ]);

        $error = is_wp_error($response)
            ? $this->mapWpErrorToResponse($response, $locale)
            : $this->validateHttpDownloadResponse($response, $tempFile, $locale);
        return $error !== null ? ['error' => $error] : [];
    }

    /**
     * @return \WP_REST_Response|null
     */
    private function mapWpErrorToResponse(\WP_Error $wpError, string $locale)
    {
        $errorCode = $wpError->get_error_code();
        $errorMessage = $wpError->get_error_message();
        fleekdash_log_error("Failed to download language pack {$locale}: [{$errorCode}] {$errorMessage}");
        $error = null;
        if ($errorCode === 'http_404' || strpos($errorMessage, '404') !== false) {
            $error = ResponseService::error('Language pack not found on server. Please contact support or try again later.', null, 404);
        } elseif ($errorCode === 'http_403' || $errorCode === 'http_401' || strpos($errorMessage, '403') !== false || strpos($errorMessage, '401') !== false) {
            $error = ResponseService::error('Access denied to language pack. Please contact support.', null, 403);
        } elseif (strpos($errorMessage, 'timeout') !== false || $errorCode === 'http_request_failed') {
            $error = ResponseService::error('Download timed out. Please check your connection and try again.', null, 408);
        } else {
            $error = ResponseService::error('Failed to download language pack: ' . $errorMessage, null, 400);
        }
        return $error;
    }

    /**
     * @param array<string, mixed> $response wp_remote_get response
     * @return \WP_REST_Response|null
     */
    private function validateHttpDownloadResponse($response, string $tempFile, string $locale)
    {
        $responseCode = wp_remote_retrieve_response_code($response);
        if ($responseCode !== 200) {
            @unlink($tempFile);
            fleekdash_log_error("Download failed for {$locale}: HTTP {$responseCode}");
            return $responseCode === 404
                ? ResponseService::error('Language pack not found on server. Please contact support or try again later.', null, 404)
                : ResponseService::error("Download failed with HTTP {$responseCode}. Please try again.", null, $responseCode);
        }
        if (!file_exists($tempFile) || !is_readable($tempFile)) {
            fleekdash_log_error("Downloaded file for {$locale} is not accessible at: {$tempFile}");
            @unlink($tempFile);
            return ResponseService::error('Downloaded file is not accessible');
        }
        return null;
    }

    /**
     * @param array<string, mixed> $langData
     * @return array{error: \WP_REST_Response}|array{}
     */
    private function validateDownloadedZipFile(string $tempFile, string $locale, array $langData)
    {
        $errorResponse = $this->validateZipSignature($tempFile, $locale);
        if ($errorResponse === null) {
            $errorResponse = $this->validateZipSize($tempFile, $locale, $langData);
        }
        if ($errorResponse === null) {
            $errorResponse = $this->validateZipChecksum($tempFile, $langData);
        }
        if ($errorResponse !== null) {
            return ['error' => $errorResponse];
        }
        fleekdash_log_info("Language pack {$locale} downloaded successfully. Size: " . filesize($tempFile) . ' bytes');
        return [];
    }

    /**
     * @return \WP_REST_Response|null
     */
    private function validateZipSignature(string $tempFile, string $locale)
    {
        $fileHandle = @fopen($tempFile, 'rb');
        if (!$fileHandle) {
            return null;
        }
        $firstBytes = fread($fileHandle, 4);
        fclose($fileHandle);
        if (substr($firstBytes, 0, 2) === 'PK') {
            return null;
        }
        fleekdash_log_error("Downloaded file for {$locale} is not a valid ZIP file. First bytes: " . bin2hex($firstBytes));
        $preview = file_get_contents($tempFile, false, null, 0, 200);
        @unlink($tempFile);
        $isErrorPage = $preview && (strpos($preview, '<html') !== false || strpos($preview, '<!DOCTYPE') !== false);
        return ResponseService::error(
            $isErrorPage ? 'Server returned an error page instead of the language pack file. Please contact support.' : 'Downloaded file is not a valid ZIP archive. File may be corrupted.',
            null,
            $isErrorPage ? 404 : 400
        );
    }

    /**
     * @param array<string, mixed> $langData
     * @return \WP_REST_Response|null
     */
    private function validateZipSize(string $tempFile, string $locale, array $langData)
    {
        $downloadedSize = filesize($tempFile);
        $expectedSize = (int) $langData['size'];
        $sizeDiffPercent = $expectedSize > 0 ? abs(($downloadedSize - $expectedSize) / $expectedSize) * 100 : 100;
        $sizeToleranceBytes = max(500, (int) ($expectedSize * 0.05));

        if (abs($downloadedSize - $expectedSize) <= $sizeToleranceBytes || $sizeDiffPercent <= 5) {
            return null;
        }
        fleekdash_log_error("Language download size mismatch for {$locale}: Expected {$expectedSize} bytes, Got {$downloadedSize} bytes");
        @unlink($tempFile);
        $message = $downloadedSize < $expectedSize * 0.5
            ? 'Downloaded file appears incomplete. Expected: ' . $expectedSize . ' bytes, Got: ' . $downloadedSize . ' bytes. Please try again.'
            : 'Downloaded file size mismatch. Expected: ' . $expectedSize . ' bytes, Got: ' . $downloadedSize . ' bytes';
        return ResponseService::error($message, null, 400);
    }

    /**
     * @param array<string, mixed> $langData
     * @return \WP_REST_Response|null
     */
    private function validateZipChecksum(string $tempFile, array $langData)
    {
        if (!isset($langData['checksum'])) {
            return null;
        }
        $checksum = hash_file('sha256', $tempFile);
        $expectedChecksum = str_replace('sha256:', '', $langData['checksum']);
        if ($checksum === $expectedChecksum) {
            return null;
        }
        unlink($tempFile);
        return ResponseService::error('Checksum validation failed. File may be corrupted.', null, 400);
    }

    /**
     * Extract ZIP to cache dir and validate JSON. Called after download.
     *
     * @return \WP_REST_Response|null Error response or null on success
     */
    public function extractAndValidate(string $tempFile, string $locale)
    {
        $error = null;
        if (!class_exists('ZipArchive')) {
            if (file_exists($tempFile)) {
                @unlink($tempFile);
            }
            fleekdash_log_error('[LanguageDownload] PHP Zip extension (ZipArchive) not available in current SAPI');
            $error = ResponseService::error('Server missing ZIP support (ZipArchive). Enable php_zip extension on the web server.', null, 500);
        } elseif (!$this->extractZipToCache($tempFile)) {
            $error = $this->lastExtractError ?? ResponseService::error('Unknown extract error');
            $this->lastExtractError = null;
        } else {
            $error = $this->validateLanguageJson($locale);
        }
        return $error;
    }

    /** @var \WP_REST_Response|null */
    private $lastExtractError = null;

    private function extractZipToCache(string $tempFile): bool
    {
        $zip = new \ZipArchive();
        if ($zip->open($tempFile) !== true) {
            unlink($tempFile);
            $this->lastExtractError = ResponseService::error('Failed to open ZIP file');
            return false;
        }
        $extracted = $zip->extractTo($this->localCacheDir);
        $zip->close();
        unlink($tempFile);
        if (!$extracted) {
            $this->lastExtractError = ResponseService::error('Failed to extract language pack');
            return false;
        }
        return true;
    }

    /**
     * @return \WP_REST_Response|null
     */
    private function validateLanguageJson(string $locale)
    {
        $jsonFile = $this->localCacheDir . "/fleekdash-{$locale}-js.json";
        if (!file_exists($jsonFile)) {
            return null;
        }
        $this->removeBomFromFile($jsonFile);
        $jsonContent = file_get_contents($jsonFile);
        $jsonData = json_decode($jsonContent, true);
        if (!$jsonData) {
            fleekdash_log_error("JSON validation failed for {$locale}: " . json_last_error_msg());
            return ResponseService::error('Downloaded language pack has invalid JSON');
        }
        return null;
    }

    private function removeBomFromFile(string $filePath): bool
    {
        $result = false;
        if (file_exists($filePath)) {
            $content = file_get_contents($filePath);
            $result = $content !== false
                && (substr($content, 0, 3) !== pack('CCC', 0xEF, 0xBB, 0xBF)
                    ? true
                    : (bool) file_put_contents($filePath, substr($content, 3)));
        }
        return $result;
    }
}
