<?php

declare(strict_types=1);

namespace FecaMailshots\Admin;

use FecaMailshots\Application\MailshotRunService;
use FecaMailshots\Application\MailshotService;
use FecaMailshots\WordPress\WordPressFacade;

final class DownloadPdfAdminPage
{
    use AdminRequestHelpers;

    private const CAPABILITY = 'edit_pages';
    private const RESULT_OPTION_KEY = 'feca_mailshots_download_pdf_ui_result';
    private const PAGE_SLUG = 'feca-mailshots-download-pdf';
    private const DOWNLOAD_MEMORY_LIMIT_ENV = 'FECA_MAILSHOTS_DOWNLOAD_MEMORY_LIMIT';
    private const DOWNLOAD_MEMORY_LIMIT_OPTION = 'feca_mailshots_download_memory_limit';
    private const DEFAULT_DOWNLOAD_MEMORY_LIMIT = '512M';
    private const NONCE_ACTION = 'feca_mailshots_download_pdf';

    /** @var callable(): MailshotRunService */
    private $runServiceFactory;
    /** @var callable(): MailshotService */
    private $mailshotServiceFactory;
    private WordPressFacade $wp;

    /** @param callable(): MailshotRunService $runServiceFactory @param callable(): MailshotService $mailshotServiceFactory */
    public function __construct(callable $runServiceFactory, callable $mailshotServiceFactory, WordPressFacade $wp)
    {
        $this->runServiceFactory = $runServiceFactory;
        $this->mailshotServiceFactory = $mailshotServiceFactory;
        $this->wp = $wp;
    }

    public function register(): void
    {
        $this->wp->addAction('admin_menu', [$this, 'registerMenu']);
        $this->wp->addAction('admin_post_feca_mailshots_download_pdf_ui', [$this, 'handleDownloadUi']);
    }

    public function registerMenu(): void
    {
        $this->wp->addSubmenuPage('feca-mailshot', 'Download PDF', 'Download PDF', self::CAPABILITY, self::PAGE_SLUG, [$this, 'render']);
    }

    public function render(): void
    {
        if (!$this->wp->currentUserCan(self::CAPABILITY)) {
            echo 'Permission denied';
            return;
        }

        $mailshots = $this->mailshotService()->list();
        $selectedMailshotId = (int) ($this->wp->requestParam('mailshot_id', '0') ?? '0');
        if ($selectedMailshotId <= 0 && $mailshots !== []) {
            $selectedMailshotId = (int) ($mailshots[0]['id'] ?? 0);
        }
        $result = $this->consumeOptionArray(self::RESULT_OPTION_KEY);
        $action = htmlspecialchars($this->wp->adminUrl('admin-post.php'));
        echo '<div class="wrap feca-mailshots-admin"><h1>Download PDF</h1>';
        echo $this->renderAdminUiStyles();
        echo '<p>Run the selected mailshot without sending any email, and download generated PDF attachments.</p>';

        if ($result !== null) {
            $ok = !empty($result['ok']);
            $bannerClass = $ok ? 'feca-banner-success' : 'feca-banner-error';
            echo '<div class="feca-banner ' . $bannerClass . '">';
            echo '<strong>' . ($ok ? 'PDF generation succeeded.' : 'PDF generation failed.') . '</strong>';
            if (!empty($result['errors']) && is_array($result['errors'])) {
                echo '<p class="feca-banner-note">' . htmlspecialchars(implode('; ', $result['errors'])) . '</p>';
            } else {
                echo '<p class="feca-banner-note">Generated: ' . (int) ($result['generated_count'] ?? 0)
                    . ', Recipients: ' . (int) ($result['recipient_count'] ?? 0)
                    . ', Skipped: ' . (int) ($result['skipped_count'] ?? 0) . '</p>';
            }
            echo '</div>';
        }

        echo '<form id="feca-download-pdf-form" class="feca-form feca-form-max-860" method="post" action="' . $action . '">';
        echo '<input type="hidden" name="action" value="feca_mailshots_download_pdf_ui">';
        echo $this->hiddenNonceField(self::NONCE_ACTION);
        echo '<input type="hidden" id="feca-download-format-shadow" name="download_format" value="">';
        echo '<div class="feca-control-row">';
        echo '<div class="feca-control feca-control-min-360">';
        echo '<label for="dp_mailshot_id"><strong>Mailshot</strong></label>';
        echo '<select id="dp_mailshot_id" name="mailshot_id">';
        foreach ($mailshots as $mailshot) {
            $id = (int) ($mailshot['id'] ?? 0);
            $purpose = trim((string) ($mailshot['Purpose'] ?? ''));
            if ($purpose === '') {
                $purpose = 'Mailshot #' . $id;
            }
            $selected = $id === $selectedMailshotId ? ' selected' : '';
            echo '<option value="' . $id . '"' . $selected . '>' . htmlspecialchars($purpose) . '</option>';
        }
        echo '</select></div></div>';
        echo '<p class="feca-button-row">';
        echo '<button id="feca-download-merged-btn" class="button button-primary" type="submit" name="download_format" value="merged" onclick="return confirm(\'Generate and download a single merged PDF?\');">Download Merged PDF</button> ';
        echo '<button id="feca-download-zip-btn" class="button" type="submit" name="download_format" value="zip" onclick="return confirm(\'Generate and download a ZIP of individual PDFs?\');">Download ZIP of PDFs</button>';
        echo '</p>';
        echo '<div id="feca-download-progress" class="feca-progress-box">';
        echo '<strong>Generating PDFs...</strong> Please wait. This can take a few seconds for larger mailshots.';
        echo '</div>';
        echo '</form>';
        echo '<script>';
        echo '(function(){';
        echo 'var actualForm=document.getElementById("feca-download-pdf-form");';
        echo 'if(!actualForm){return;}';
        echo 'var merged=document.getElementById("feca-download-merged-btn");';
        echo 'var zip=document.getElementById("feca-download-zip-btn");';
        echo 'var progress=document.getElementById("feca-download-progress");';
        echo 'var shadow=document.getElementById("feca-download-format-shadow");';
        echo 'var clearUi=function(){if(progress){progress.style.display="none";}if(merged){merged.disabled=false;merged.classList.remove("disabled");}if(zip){zip.disabled=false;zip.classList.remove("disabled");}};';
        echo 'actualForm.addEventListener("submit",function(ev){';
        echo 'var submitter=ev&&ev.submitter?ev.submitter:null;';
        echo 'if(shadow&&submitter&&submitter.name==="download_format"){shadow.value=submitter.value||"";}';
        echo 'if(merged){merged.disabled=true;merged.classList.add("disabled");}';
        echo 'if(zip){zip.disabled=true;zip.classList.add("disabled");}';
        echo 'if(progress){progress.style.display="block";}';
        echo 'window.setTimeout(clearUi,45000);';
        echo '});';
        echo 'window.addEventListener("focus",clearUi);';
        echo 'window.addEventListener("pageshow",clearUi);';
        echo 'document.addEventListener("visibilitychange",function(){if(document.visibilityState==="visible"){clearUi();}});';
        echo '})();';
        echo '</script>';
        echo '</div>';
    }

    public function handleDownloadUi(): void
    {
        if (!$this->enforceMutationGuardOrJson(self::CAPABILITY, self::NONCE_ACTION)) {
            return;
        }

        $mailshotId = $this->requestInt('mailshot_id', 0);
        $format = $this->requestString('download_format', 'merged');
        if (!in_array($format, ['merged', 'zip'], true)) {
            $format = 'merged';
        }

        $this->maybeRaiseMemoryLimit($this->resolveDownloadMemoryLimitTarget());
        $downloadBaseName = $this->downloadBaseNameForMailshot($mailshotId);

        if ($format === 'zip') {
            try {
                $result = $this->runService()->generatePdfZipToTemp($mailshotId);
            } catch (\Throwable $e) {
                $this->wp->updateOption(self::RESULT_OPTION_KEY, ['ok' => false, 'errors' => ['ZIP generation failed: ' . $e->getMessage()]]);
                $this->redirectTo($this->wp->adminUrl('admin.php?page=' . self::PAGE_SLUG . '&mailshot_id=' . $mailshotId));
                return;
            }
            if (empty($result['ok'])) {
                $this->wp->updateOption(self::RESULT_OPTION_KEY, $result);
                $this->redirectTo($this->wp->adminUrl('admin.php?page=' . self::PAGE_SLUG . '&mailshot_id=' . $mailshotId));
                return;
            }

            $zipPath = (string) ($result['zip_path'] ?? '');
            if ($zipPath === '' || !is_file($zipPath)) {
                $this->wp->updateOption(self::RESULT_OPTION_KEY, ['ok' => false, 'errors' => ['ZIP generation failed: archive file not found.']]);
                $this->redirectTo($this->wp->adminUrl('admin.php?page=' . self::PAGE_SLUG . '&mailshot_id=' . $mailshotId));
                return;
            }

            try {
                $this->sendFileDownload($downloadBaseName . '_pdfs.zip', 'application/zip', $zipPath);
            } catch (\Throwable $e) {
                @unlink($zipPath);
                $this->wp->updateOption(self::RESULT_OPTION_KEY, ['ok' => false, 'errors' => ['ZIP download failed: ' . $e->getMessage()]]);
                $this->redirectTo($this->wp->adminUrl('admin.php?page=' . self::PAGE_SLUG . '&mailshot_id=' . $mailshotId));
            }
            return;
        }

        try {
            $result = $this->runService()->generatePdfBatch($mailshotId, true, false);
        } catch (\Throwable $e) {
            $this->wp->updateOption(self::RESULT_OPTION_KEY, ['ok' => false, 'errors' => ['Merged PDF generation failed: ' . $e->getMessage()]]);
            $this->redirectTo($this->wp->adminUrl('admin.php?page=' . self::PAGE_SLUG . '&mailshot_id=' . $mailshotId));
            return;
        }
        if (empty($result['ok'])) {
            $this->wp->updateOption(self::RESULT_OPTION_KEY, $result);
            $this->redirectTo($this->wp->adminUrl('admin.php?page=' . self::PAGE_SLUG . '&mailshot_id=' . $mailshotId));
            return;
        }

        $bytes = (string) ($result['merged_pdf_bytes'] ?? '');
        if ($bytes === '') {
            $this->wp->updateOption(self::RESULT_OPTION_KEY, ['ok' => false, 'errors' => ['Merged PDF bytes are empty.']]);
            $this->redirectTo($this->wp->adminUrl('admin.php?page=' . self::PAGE_SLUG . '&mailshot_id=' . $mailshotId));
            return;
        }
        try {
            $this->sendBinaryDownload($downloadBaseName . '_merged.pdf', 'application/pdf', $bytes);
        } catch (\Throwable $e) {
            $this->wp->updateOption(self::RESULT_OPTION_KEY, ['ok' => false, 'errors' => ['Merged PDF download failed: ' . $e->getMessage()]]);
            $this->redirectTo($this->wp->adminUrl('admin.php?page=' . self::PAGE_SLUG . '&mailshot_id=' . $mailshotId));
            return;
        }
    }

    private function sendBinaryDownload(string $filename, string $contentType, string $bytes): void
    {
        if (headers_sent()) {
            throw new \RuntimeException('Cannot send download: headers already sent.');
        }
        header('Content-Type: ' . $contentType);
        header('Content-Disposition: attachment; filename="' . str_replace('"', '', $filename) . '"');
        header('Content-Length: ' . strlen($bytes));
        header('X-Content-Type-Options: nosniff');
        echo $bytes;
        exit;
    }

    private function sendFileDownload(string $filename, string $contentType, string $path): void
    {
        if (headers_sent()) {
            throw new \RuntimeException('Cannot send download: headers already sent.');
        }
        $size = @filesize($path);
        if (!is_int($size) || $size <= 0) {
            throw new \RuntimeException('Cannot send download: file is missing or empty.');
        }

        header('Content-Type: ' . $contentType);
        header('Content-Disposition: attachment; filename="' . str_replace('"', '', $filename) . '"');
        header('Content-Length: ' . (string) $size);
        header('X-Content-Type-Options: nosniff');

        $in = @fopen($path, 'rb');
        if (!is_resource($in)) {
            throw new \RuntimeException('Cannot read download file.');
        }
        while (!feof($in)) {
            $chunk = fread($in, 8192);
            if ($chunk === false) {
                fclose($in);
                throw new \RuntimeException('Failed while reading download file.');
            }
            echo $chunk;
        }
        fclose($in);
        @unlink($path);
        exit;
    }

    private function runService(): MailshotRunService
    {
        return ($this->runServiceFactory)();
    }

    private function mailshotService(): MailshotService
    {
        return ($this->mailshotServiceFactory)();
    }

    private function downloadBaseNameForMailshot(int $mailshotId): string
    {
        try {
            foreach ($this->mailshotService()->list() as $mailshot) {
                if ((int) ($mailshot['id'] ?? 0) !== $mailshotId) {
                    continue;
                }
                $purpose = trim((string) ($mailshot['Purpose'] ?? ''));
                if ($purpose !== '') {
                    return $this->safeDownloadBaseName($purpose);
                }
            }
        } catch (\Throwable $e) {
            // Fall back to a stable id-based name if the purpose cannot be loaded.
        }

        return 'mailshot_' . max(0, $mailshotId);
    }

    private function safeDownloadBaseName(string $name): string
    {
        $name = trim($name);
        if ($name === '') {
            return 'mailshot';
        }

        if (function_exists('iconv')) {
            $ascii = @iconv('UTF-8', 'ASCII//TRANSLIT//IGNORE', $name);
            if (is_string($ascii) && trim($ascii) !== '') {
                $name = $ascii;
            }
        }

        $name = preg_replace('/[^A-Za-z0-9._-]+/', '_', $name) ?? '';
        $name = preg_replace('/_+/', '_', $name) ?? '';
        $name = trim($name, '._-');

        return $name !== '' ? $name : 'mailshot';
    }

    private function maybeRaiseMemoryLimit(string $target): void
    {
        if (!function_exists('ini_get') || !function_exists('ini_set')) {
            return;
        }

        $current = (string) ini_get('memory_limit');
        $currentBytes = $this->memoryLimitToBytes($current);
        $targetBytes = $this->memoryLimitToBytes($target);

        if ($currentBytes < 0 || $targetBytes <= 0) {
            return;
        }
        if ($currentBytes >= $targetBytes) {
            return;
        }

        $old = $current;
        @ini_set('memory_limit', $target);
        $new = (string) ini_get('memory_limit');
        if ($new === $old) {
            return;
        }
    }

    private function resolveDownloadMemoryLimitTarget(): string
    {
        $optionValue = $this->wp->getOption(self::DOWNLOAD_MEMORY_LIMIT_OPTION, '');
        if (is_string($optionValue)) {
            $v = trim($optionValue);
            if ($this->memoryLimitToBytes($v) > 0) {
                return $v;
            }
        }

        $envValue = getenv(self::DOWNLOAD_MEMORY_LIMIT_ENV);
        if (is_string($envValue)) {
            $v = trim($envValue);
            if ($this->memoryLimitToBytes($v) > 0) {
                return $v;
            }
        }

        return self::DEFAULT_DOWNLOAD_MEMORY_LIMIT;
    }

    private function memoryLimitToBytes(string $limit): int
    {
        $v = trim($limit);
        if ($v === '') {
            return 0;
        }
        if ($v === '-1') {
            return -1;
        }

        $unit = strtolower(substr($v, -1));
        if (ctype_alpha($unit)) {
            $num = (float) substr($v, 0, -1);
            switch ($unit) {
                case 'g':
                    return (int) ($num * 1024 * 1024 * 1024);
                case 'm':
                    return (int) ($num * 1024 * 1024);
                case 'k':
                    return (int) ($num * 1024);
                default:
                    return (int) $num;
            }
        }

        return (int) $v;
    }
}
