<?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'));
        $buildStamp = gmdate('Y-m-d H:i:s', (int) @filemtime(__FILE__)) . ' UTC';
        $renderedAt = gmdate('Y-m-d H:i:s') . ' UTC';

        echo '<div class="wrap"><h1>Download PDF</h1>';
        echo '<p style="margin:4px 0 12px 0;color:#6b7280;font-size:12px;">Build stamp: ' . htmlspecialchars($buildStamp, ENT_QUOTES) . ' | Rendered: ' . htmlspecialchars($renderedAt, ENT_QUOTES) . '</p>';
        echo '<p>Run the selected mailshot without sending any email, and download generated PDF attachments.</p>';

        if ($result !== null) {
            $ok = !empty($result['ok']);
            $bg = $ok ? '#f1f8e9' : '#ffebee';
            $border = $ok ? '#8bc34a' : '#ef9a9a';
            echo '<div style="padding:10px;border:1px solid ' . $border . ';background:' . $bg . ';margin:12px 0;">';
            echo '<strong>' . ($ok ? 'PDF generation succeeded.' : 'PDF generation failed.') . '</strong>';
            if (!empty($result['errors']) && is_array($result['errors'])) {
                echo '<p style="margin:6px 0 0 0;">' . htmlspecialchars(implode('; ', $result['errors'])) . '</p>';
            } else {
                echo '<p style="margin:6px 0 0 0;">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" method="post" action="' . $action . '" style="padding:12px;border:1px solid #dcdcde;background:#fff;max-width:860px;">';
        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 style="display:flex;gap:12px;align-items:center;flex-wrap:wrap;">';
        echo '<div style="display:flex;align-items:center;gap:8px;padding-top:6px;min-width:360px;">';
        echo '<label for="dp_mailshot_id" style="white-space:nowrap;padding-left:4px;"><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 style="margin-top:12px;">';
        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" style="display:none;margin-top:8px;padding:10px;border:1px solid #c8d7e1;background:#f6fbff;max-width:520px;">';
        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 '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(submitter){submitter.disabled=true;submitter.classList.add("disabled");}';
        echo 'if(progress){progress.style.display="block";}';
        echo '});';
        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->downloadDebugLog('handleDownloadUi.start', [
            'mailshot_id' => $mailshotId,
            'format' => $format,
            'memory_usage' => memory_get_usage(true),
            'memory_peak' => memory_get_peak_usage(true),
        ]);
        $this->maybeRaiseMemoryLimit($this->resolveDownloadMemoryLimitTarget());

        register_shutdown_function(function () use ($mailshotId, $format): void {
            $err = error_get_last();
            if (!is_array($err)) {
                return;
            }
            $type = (int) ($err['type'] ?? 0);
            $fatalTypes = [E_ERROR, E_PARSE, E_CORE_ERROR, E_COMPILE_ERROR, E_USER_ERROR, E_RECOVERABLE_ERROR];
            if (!in_array($type, $fatalTypes, true)) {
                return;
            }
            $this->downloadDebugLog('handleDownloadUi.shutdown_fatal', [
                'mailshot_id' => $mailshotId,
                'format' => $format,
                'error_type' => $type,
                'error_message' => (string) ($err['message'] ?? ''),
                'error_file' => (string) ($err['file'] ?? ''),
                'error_line' => (int) ($err['line'] ?? 0),
                'memory_usage' => memory_get_usage(true),
                'memory_peak' => memory_get_peak_usage(true),
            ]);
        });

        if ($format === 'zip') {
            try {
                $this->downloadDebugLog('handleDownloadUi.zip.before_generate', ['mailshot_id' => $mailshotId]);
                $result = $this->runService()->generatePdfZipToTemp($mailshotId);
                $this->downloadDebugLog('handleDownloadUi.zip.after_generate', [
                    'mailshot_id' => $mailshotId,
                    'ok' => !empty($result['ok']),
                    'generated_count' => (int) ($result['generated_count'] ?? 0),
                    'recipient_count' => (int) ($result['recipient_count'] ?? 0),
                    'zip_path' => (string) ($result['zip_path'] ?? ''),
                    'memory_usage' => memory_get_usage(true),
                    'memory_peak' => memory_get_peak_usage(true),
                ]);
            } catch (\Throwable $e) {
                $this->downloadDebugLog('handleDownloadUi.zip.exception', [
                    'mailshot_id' => $mailshotId,
                    'error' => $e->getMessage(),
                ]);
                $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->downloadDebugLog('handleDownloadUi.zip.before_send', ['mailshot_id' => $mailshotId, 'zip_path' => $zipPath]);
                $this->sendFileDownload('mailshot_' . $mailshotId . '_pdfs.zip', 'application/zip', $zipPath);
            } catch (\Throwable $e) {
                @unlink($zipPath);
                $this->downloadDebugLog('handleDownloadUi.zip.send_exception', [
                    'mailshot_id' => $mailshotId,
                    'error' => $e->getMessage(),
                ]);
                $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 {
            $this->downloadDebugLog('handleDownloadUi.merged.before_generate', ['mailshot_id' => $mailshotId]);
            $result = $this->runService()->generatePdfBatch($mailshotId, true, false);
            $this->downloadDebugLog('handleDownloadUi.merged.after_generate', [
                'mailshot_id' => $mailshotId,
                'ok' => !empty($result['ok']),
                'generated_count' => (int) ($result['generated_count'] ?? 0),
                'recipient_count' => (int) ($result['recipient_count'] ?? 0),
                'memory_usage' => memory_get_usage(true),
                'memory_peak' => memory_get_peak_usage(true),
            ]);
        } catch (\Throwable $e) {
            $this->downloadDebugLog('handleDownloadUi.merged.exception', [
                'mailshot_id' => $mailshotId,
                'error' => $e->getMessage(),
            ]);
            $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->downloadDebugLog('handleDownloadUi.merged.before_send', ['mailshot_id' => $mailshotId, 'bytes_len' => strlen($bytes)]);
            $this->sendBinaryDownload('mailshot_' . $mailshotId . '_merged.pdf', 'application/pdf', $bytes);
        } catch (\Throwable $e) {
            $this->downloadDebugLog('handleDownloadUi.merged.send_exception', [
                'mailshot_id' => $mailshotId,
                'error' => $e->getMessage(),
            ]);
            $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)();
    }

    /** @param array<string,mixed> $context */
    private function downloadDebugLog(string $event, array $context = []): void
    {
        $line = '[feca-mailshots/download-pdf][' . gmdate('Y-m-d H:i:s') . ' UTC] ' . $event;
        if ($context !== []) {
            $json = json_encode($context);
            if (is_string($json) && $json !== '') {
                $line .= ' ' . $json;
            }
        }

        error_log($line);

        if (!defined('ABSPATH')) {
            return;
        }

        $path = ABSPATH . 'wp-content/uploads/feca_mailshots_download_debug.log';
        $dir = dirname($path);
        if (!is_dir($dir)) {
            @mkdir($dir, 0755, true);
        }
        if (is_dir($dir) && is_writable($dir)) {
            @error_log($line . PHP_EOL, 3, $path);
        }
    }

    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');
        $this->downloadDebugLog('handleDownloadUi.memory_limit_adjust', [
            'old' => $old,
            'new' => $new,
            'target' => $target,
        ]);
    }

    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;
    }
}
