<?php

declare(strict_types=1);

namespace FecaMailshots\Admin;

use FecaMailshots\Repository\MailCredentialRepository;
use FecaMailshots\WordPress\WordPressFacade;

final class ProfileAdminPage
{
    use AdminRequestHelpers;

    private const CAPABILITY = 'edit_pages';
    private const TEST_RESULT_OPTION_PREFIX = 'feca_mailshots_profile_test_result_';
    private const NONCE_ACTION = 'feca_mailshots_profile';

    private WordPressFacade $wp;
    /** @var callable(): MailCredentialRepository */
    private $repoFactory;

    /** @param callable(): MailCredentialRepository $repoFactory */
    public function __construct(WordPressFacade $wp, callable $repoFactory)
    {
        $this->wp = $wp;
        $this->repoFactory = $repoFactory;
    }

    public function register(): void
    {
        $this->wp->addAction('admin_menu', [$this, 'registerMenu']);
        $this->wp->addAction('admin_post_feca_mailshots_profile_save', [$this, 'handleSave']);
        $this->wp->addAction('admin_post_feca_mailshots_profile_test', [$this, 'handleTest']);
    }

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

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

        $uid = $this->wp->currentUserId();
        $saved = $uid > 0 ? ($this->repo()->findByUserId($uid) ?? []) : [];
        $status = $this->wp->requestParam('saved', '') === '1' ? 'Profile credentials saved.' : '';
        $test = $uid > 0 ? $this->testResult($uid) : null;
        $action = htmlspecialchars($this->wp->adminUrl('admin-post.php'));

        echo '<div class="wrap feca-mailshots-admin"><h1>Mailshot Profile</h1>';
        echo $this->renderAdminUiStyles();
        if ($status !== '') {
            echo '<div class="feca-banner feca-banner-success">' . htmlspecialchars($status) . '</div>';
        }
        if ($test !== null) {
            $ok = !empty($test['ok']);
            $kind = strtoupper((string) ($test['kind'] ?? 'CREDENTIAL'));
            $title = $kind . ' test ' . ($ok ? 'passed.' : 'failed.');
            $bannerClass = $ok ? 'feca-banner-success' : 'feca-banner-error';
            echo '<div class="feca-banner ' . $bannerClass . '">';
            echo '<strong>' . htmlspecialchars($title) . '</strong>';
            if (!empty($test['messages']) && is_array($test['messages'])) {
                echo '<ul>';
                foreach ($test['messages'] as $line) {
                    echo '<li>' . htmlspecialchars((string) $line) . '</li>';
                }
                echo '</ul>';
            }
            echo '</div>';
        }
        echo '<p>Configure your personal SMTP and IMAP credentials for test/run operations.</p>';
        echo '<p><em>Passwords are stored encrypted in the mailshots database.</em></p>';
        echo '<div class="feca-banner feca-banner-info">';
        echo '<strong>Common configuration patterns</strong>';
        echo '<ul>';
        echo '<li>SMTP port 465 usually means <em>Require TLS</em> should be enabled (implicit TLS).</li>';
        echo '<li>SMTP port 587 usually means <em>Require TLS</em> should be disabled so STARTTLS can be negotiated.</li>';
        echo '<li>IMAP port 993 usually uses mailbox flags <code>/imap/ssl</code>.</li>';
        echo '<li>IMAP port 143 usually uses non-SSL flags such as <code>/imap</code> (or your provider specific STARTTLS mode).</li>';
        echo '</ul>';
        echo '</div>';

        echo '<form method="post" action="' . $action . '" class="feca-form">';
        echo $this->hiddenNonceField(self::NONCE_ACTION);

        echo '<h2>SMTP</h2>';
        echo '<table class="form-table" role="presentation">';
        $this->field('SMTP Host', 'smtp_host', $saved['smtp_host'] ?? '');
        $this->field('SMTP Port', 'smtp_port', (string) ($saved['smtp_port'] ?? ''));
        $this->field('SMTP User', 'smtp_user', $saved['smtp_user'] ?? '');
        $this->field('SMTP Password', 'smtp_password', '', 'password', 'Required on each save/test request');
        $this->field('From Email', 'smtp_from_email', $saved['smtp_from_email'] ?? '');
        $this->field('From Name', 'smtp_from_name', $saved['smtp_from_name'] ?? '');

        $tlsChecked = !empty($saved['smtp_require_tls']) ? 'checked' : '';
        echo '<tr><th scope="row">Require TLS</th><td><label><input type="checkbox" name="smtp_require_tls" value="1" ' . $tlsChecked . '> Use implicit TLS transport</label></td></tr>';
        echo '</table>';

        echo '<h2>IMAP Sent Copy</h2>';
        echo '<table class="form-table" role="presentation">';
        $this->field('IMAP Host', 'imap_host', $saved['imap_host'] ?? '');
        $this->field('IMAP Port', 'imap_port', (string) ($saved['imap_port'] ?? ''));
        $this->field('IMAP User', 'imap_user', $saved['imap_user'] ?? '');
        $this->field('IMAP Password', 'imap_password', '', 'password', 'Required on each save/test request');
        $this->field('IMAP Sent Folder', 'imap_sent_folder', $saved['imap_sent_folder'] ?? '');
        $this->field('IMAP Mailbox Flags', 'imap_mailbox_flags', $saved['imap_mailbox_flags'] ?? '');
        echo '</table>';

        echo '<p class="feca-button-row">';
        echo '<button type="submit" class="button button-primary" name="action" value="feca_mailshots_profile_save">Save Profile</button> ';
        echo '<button type="submit" class="button" name="action" value="feca_mailshots_profile_test" formaction="' . $action . '" formmethod="post" title="Test SMTP login" onclick="this.form.test_kind.value=\'smtp\';">Test SMTP</button> ';
        echo '<button type="submit" class="button" name="action" value="feca_mailshots_profile_test" formaction="' . $action . '" formmethod="post" title="Test IMAP login" onclick="this.form.test_kind.value=\'imap\';">Test IMAP</button>';
        echo '<input type="hidden" name="test_kind" value="">';
        echo '</p>';
        echo '</form></div>';
    }

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

        $uid = $this->wp->currentUserId();
        if ($uid <= 0) {
            $this->wp->sendJson(['ok' => false, 'error' => 'No current user.'], 400);
            return;
        }

        $payload = [
            'smtp_host' => trim((string) ($this->wp->requestParam('smtp_host', '') ?? '')),
            'smtp_port' => trim((string) ($this->wp->requestParam('smtp_port', '') ?? '')),
            'smtp_user' => trim((string) ($this->wp->requestParam('smtp_user', '') ?? '')),
            'smtp_password' => (string) ($this->wp->requestParam('smtp_password', '') ?? ''),
            'smtp_from_email' => trim((string) ($this->wp->requestParam('smtp_from_email', '') ?? '')),
            'smtp_from_name' => trim((string) ($this->wp->requestParam('smtp_from_name', '') ?? '')),
            'smtp_require_tls' => $this->wp->requestParam('smtp_require_tls', '') === '1',
            'imap_host' => trim((string) ($this->wp->requestParam('imap_host', '') ?? '')),
            'imap_port' => trim((string) ($this->wp->requestParam('imap_port', '') ?? '')),
            'imap_user' => trim((string) ($this->wp->requestParam('imap_user', '') ?? '')),
            'imap_password' => (string) ($this->wp->requestParam('imap_password', '') ?? ''),
            'imap_sent_folder' => trim((string) ($this->wp->requestParam('imap_sent_folder', '') ?? '')),
            'imap_mailbox_flags' => trim((string) ($this->wp->requestParam('imap_mailbox_flags', '') ?? '')),
        ];
        $errors = [];
        foreach (['smtp_host', 'smtp_port', 'smtp_user', 'smtp_password', 'smtp_from_email', 'imap_host', 'imap_port', 'imap_user', 'imap_password', 'imap_sent_folder', 'imap_mailbox_flags'] as $required) {
            if ($payload[$required] === '') {
                $errors[] = 'Missing required field: ' . $required;
            }
        }
        if ($errors !== []) {
            $this->wp->sendJson(['ok' => false, 'errors' => $errors], 400);
            return;
        }
        $this->repo()->upsertForUser($uid, $payload);

        if (!headers_sent()) {
            $location = $this->wp->adminUrl('admin.php?page=feca-mailshots-profile&saved=1');
            header('Location: ' . $location, true, 302);
            exit;
        }

        $this->wp->sendJson(['ok' => true, 'saved' => true]);
    }

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

        $uid = $this->wp->currentUserId();
        if ($uid <= 0) {
            $this->wp->sendJson(['ok' => false, 'error' => 'No current user.'], 400);
            return;
        }

        $kind = strtolower(trim((string) ($this->wp->requestParam('test_kind', '') ?? '')));
        if ($kind !== 'smtp' && $kind !== 'imap') {
            $this->wp->updateOption($this->testResultOptionKey($uid), [
                'kind' => 'unknown',
                'ok' => false,
                'messages' => ['Choose either "Test SMTP" or "Test IMAP".'],
            ]);
            if (!headers_sent()) {
                header('Location: ' . $this->wp->adminUrl('admin.php?page=feca-mailshots-profile&tested=1'), true, 302);
                exit;
            }
            $this->wp->sendJson(['ok' => false, 'error' => 'Invalid test kind.'], 400);
            return;
        }

        $payload = [
            'smtp_host' => trim((string) ($this->wp->requestParam('smtp_host', '') ?? '')),
            'smtp_port' => trim((string) ($this->wp->requestParam('smtp_port', '') ?? '')),
            'smtp_user' => trim((string) ($this->wp->requestParam('smtp_user', '') ?? '')),
            'smtp_password' => (string) ($this->wp->requestParam('smtp_password', '') ?? ''),
            'smtp_from_email' => trim((string) ($this->wp->requestParam('smtp_from_email', '') ?? '')),
            'smtp_from_name' => trim((string) ($this->wp->requestParam('smtp_from_name', '') ?? '')),
            'smtp_require_tls' => $this->wp->requestParam('smtp_require_tls', '') === '1',
            'imap_host' => trim((string) ($this->wp->requestParam('imap_host', '') ?? '')),
            'imap_port' => trim((string) ($this->wp->requestParam('imap_port', '') ?? '')),
            'imap_user' => trim((string) ($this->wp->requestParam('imap_user', '') ?? '')),
            'imap_password' => (string) ($this->wp->requestParam('imap_password', '') ?? ''),
            'imap_sent_folder' => trim((string) ($this->wp->requestParam('imap_sent_folder', '') ?? '')),
            'imap_mailbox_flags' => trim((string) ($this->wp->requestParam('imap_mailbox_flags', '') ?? '')),
        ];

        $result = $kind === 'smtp' ? $this->runSmtpTest($payload) : $this->runImapTest($payload);
        $this->wp->updateOption($this->testResultOptionKey($uid), $result);

        if (!headers_sent()) {
            header('Location: ' . $this->wp->adminUrl('admin.php?page=feca-mailshots-profile&tested=1'), true, 302);
            exit;
        }

        $this->wp->sendJson($result);
    }

    private function field(string $label, string $name, string $value, string $type = 'text', string $hint = ''): void
    {
        echo '<tr>';
        echo '<th scope="row"><label for="' . htmlspecialchars($name) . '">' . htmlspecialchars($label) . '</label></th>';
        echo '<td><input class="regular-text" type="' . htmlspecialchars($type) . '" id="' . htmlspecialchars($name) . '" name="' . htmlspecialchars($name) . '" value="' . htmlspecialchars($value, ENT_QUOTES) . '">';
        if ($hint !== '') {
            echo '<p class="description">' . htmlspecialchars($hint) . '</p>';
        }
        echo '</td></tr>';
    }

    private function repo(): MailCredentialRepository
    {
        return ($this->repoFactory)();
    }

    /** @param array<string, mixed> $payload */
    private function runSmtpTest(array $payload): array
    {
        $messages = [];
        foreach (['smtp_host', 'smtp_port', 'smtp_user', 'smtp_password', 'smtp_from_email'] as $required) {
            if (trim((string) ($payload[$required] ?? '')) === '') {
                return ['kind' => 'smtp', 'ok' => false, 'messages' => ['Missing required field: ' . $required]];
            }
        }

        $host = (string) $payload['smtp_host'];
        $port = (int) $payload['smtp_port'];
        $user = (string) $payload['smtp_user'];
        $pass = (string) $payload['smtp_password'];
        $requireTls = !empty($payload['smtp_require_tls']);
        foreach ($this->smtpConfigDiagnostics($port, $requireTls) as $hint) {
            $messages[] = 'Config check: ' . $hint;
        }
        $transport = $requireTls ? 'tls://' : '';

        $fp = @stream_socket_client($transport . $host . ':' . $port, $errno, $errstr, 15);
        if (!is_resource($fp)) {
            $messages[] = 'SMTP connect failed: ' . (string) $errstr;
            return ['kind' => 'smtp', 'ok' => false, 'messages' => $messages];
        }

        try {
            $this->smtpExpect($fp, [220]);
            $this->smtpCmd($fp, 'EHLO localhost', [250]);
            $messages[] = sprintf('Connected to SMTP server %s:%d.', $host, $port);

            if (!$requireTls) {
                $line = $this->smtpCmd($fp, 'STARTTLS', [220], false);
                if ($line !== null) {
                    if (!stream_socket_enable_crypto($fp, true, STREAM_CRYPTO_METHOD_TLS_CLIENT)) {
                        return ['kind' => 'smtp', 'ok' => false, 'messages' => ['SMTP STARTTLS negotiation failed.']];
                    }
                    $this->smtpCmd($fp, 'EHLO localhost', [250]);
                    $messages[] = 'STARTTLS negotiated successfully.';
                } else {
                    $messages[] = 'STARTTLS not offered; continued without TLS (as configured).';
                }
            } else {
                $messages[] = 'TLS transport required and connection established.';
            }

            $this->smtpCmd($fp, 'AUTH LOGIN', [334]);
            $this->smtpCmd($fp, base64_encode($user), [334]);
            $this->smtpCmd($fp, base64_encode($pass), [235]);
            $messages[] = 'SMTP authentication succeeded.';

            $from = (string) $payload['smtp_from_email'];
            $this->smtpCmd($fp, 'MAIL FROM:<' . $from . '>', [250]);
            $messages[] = 'MAIL FROM accepted for configured sender address.';
            $this->smtpCmd($fp, 'QUIT', [221], false);
        } catch (\Throwable $e) {
            return ['kind' => 'smtp', 'ok' => false, 'messages' => array_merge($messages, [$e->getMessage()])];
        } finally {
            fclose($fp);
        }

        return ['kind' => 'smtp', 'ok' => true, 'messages' => $messages];
    }

    /** @param array<string, mixed> $payload */
    private function runImapTest(array $payload): array
    {
        $messages = [];
        foreach (['imap_host', 'imap_port', 'imap_user', 'imap_password', 'imap_sent_folder', 'imap_mailbox_flags'] as $required) {
            if (trim((string) ($payload[$required] ?? '')) === '') {
                return ['kind' => 'imap', 'ok' => false, 'messages' => ['Missing required field: ' . $required]];
            }
        }

        if (!function_exists('imap_open')) {
            return ['kind' => 'imap', 'ok' => false, 'messages' => ['IMAP extension is not available in PHP runtime.']];
        }

        $host = (string) $payload['imap_host'];
        $port = (int) $payload['imap_port'];
        $user = (string) $payload['imap_user'];
        $pass = (string) $payload['imap_password'];
        $folder = trim((string) ($payload['imap_sent_folder'] ?? ''));
        $flags = trim((string) ($payload['imap_mailbox_flags'] ?? ''));
        foreach ($this->imapConfigDiagnostics($port, $flags) as $hint) {
            $messages[] = 'Config check: ' . $hint;
        }
        $mailbox = sprintf('{%s:%d%s}%s', $host, $port, $flags, $folder);

        $imap = @imap_open($mailbox, $user, $pass);
        if ($imap === false) {
            $messages[] = 'IMAP login failed: ' . (imap_last_error() ?: 'unknown error');
            return ['kind' => 'imap', 'ok' => false, 'messages' => $messages];
        }

        imap_close($imap);

        return [
            'kind' => 'imap',
            'ok' => true,
            'messages' => array_merge($messages, [
                sprintf('Connected and authenticated to IMAP server %s:%d.', $host, $port),
                sprintf('Mailbox opened successfully: %s', $mailbox),
            ]),
        ];
    }

    /** @return list<string> */
    private function smtpConfigDiagnostics(int $port, bool $requireTls): array
    {
        $hints = [];
        if ($port === 465 && !$requireTls) {
            $hints[] = 'Port 465 usually requires "Require TLS" enabled (implicit TLS).';
        }
        if ($port === 587 && $requireTls) {
            $hints[] = 'Port 587 is usually STARTTLS; try disabling "Require TLS" so STARTTLS can be negotiated.';
        }
        if ($port === 25 && $requireTls) {
            $hints[] = 'Port 25 with implicit TLS is uncommon; check provider docs for the expected TLS mode.';
        }
        return $hints;
    }

    /** @return list<string> */
    private function imapConfigDiagnostics(int $port, string $flags): array
    {
        $hints = [];
        $flagsLower = strtolower($flags);
        if ($port === 993 && strpos($flagsLower, '/ssl') === false) {
            $hints[] = 'IMAP port 993 usually expects SSL flags (for example /imap/ssl).';
        }
        if ($port === 143 && strpos($flagsLower, '/ssl') !== false) {
            $hints[] = 'IMAP port 143 is usually non-SSL; /ssl flags may cause login/connect failures.';
        }
        return $hints;
    }

    /** @param resource $fp @param list<int> $codes */
    private function smtpCmd($fp, string $cmd, array $codes, bool $throwOnMismatch = true): ?string
    {
        fwrite($fp, $cmd . "\r\n");
        return $this->smtpExpect($fp, $codes, $throwOnMismatch);
    }

    /** @param resource $fp @param list<int> $codes */
    private function smtpExpect($fp, array $codes, bool $throwOnMismatch = true): ?string
    {
        $lastLine = null;
        while (true) {
            $line = fgets($fp, 4096);
            if ($line === false) {
                if ($throwOnMismatch) {
                    throw new \RuntimeException('SMTP read failed while testing credentials.');
                }
                return null;
            }

            $lastLine = $line;
            if (!preg_match('/^(\d{3})([\s-])/', $line, $m)) {
                if ($throwOnMismatch) {
                    throw new \RuntimeException('SMTP malformed response: ' . trim($line));
                }
                return null;
            }

            $code = (int) $m[1];
            if (!in_array($code, $codes, true)) {
                if ($throwOnMismatch) {
                    throw new \RuntimeException('SMTP unexpected response: ' . trim($line));
                }
                return null;
            }

            $continuation = $m[2] === '-';
            if (!$continuation) {
                return $lastLine;
            }
        }
    }

    private function testResultOptionKey(int $uid): string
    {
        return self::TEST_RESULT_OPTION_PREFIX . $uid;
    }

    /** @return array{kind:string,ok:bool,messages:list<string>}|null */
    private function testResult(int $uid): ?array
    {
        $raw = $this->wp->getOption($this->testResultOptionKey($uid), null);
        $this->wp->deleteOption($this->testResultOptionKey($uid));
        if (!is_array($raw) || !isset($raw['kind']) || !isset($raw['ok']) || !isset($raw['messages']) || !is_array($raw['messages'])) {
            return null;
        }

        return [
            'kind' => (string) $raw['kind'],
            'ok' => (bool) $raw['ok'],
            'messages' => array_map(static fn($m): string => (string) $m, $raw['messages']),
        ];
    }
}
