<?php

declare(strict_types=1);

/**
 * 反向 Email 驗證（Inbound / Reverse Verification）。
 *
 * 用途：對外 25 埠被封、無法主動寄信時的替代驗證方式。
 * 流程：會員從註冊信箱寄一封空信到驗證信箱（如 verify@kui.cx），
 *       後端以 IMAP 檢查 INBOX 是否有該寄件者的「未讀」信，命中即標記已驗證並刪信。
 *
 * 設定：後台「Email 設定」頁可填寫（存於 fate_mail_settings.json），
 *       環境變數 FATE_IMAP_* 優先；密碼走金鑰庫 imap_password（fate_secrets.json）。
 *   FATE_VERIFY_METHOD        token｜reverse｜both（驗證方式總開關）
 *   FATE_IMAP_HOST            預設 localhost
 *   FATE_IMAP_PORT            預設 993
 *   FATE_IMAP_SECURE          ssl｜tls｜none（推導旗標；亦可用 FATE_IMAP_FLAGS 直接覆寫）
 *   FATE_IMAP_FOLDER          預設 INBOX
 *   FATE_IMAP_VERIFY_ADDRESS  收驗證信的信箱，例如 verify@kui.cx
 *   FATE_IMAP_USER            IMAP 帳號（預設＝VERIFY_ADDRESS）
 *   FATE_IMAP_PASSWORD        IMAP 密碼（亦可存 fate_secrets.json 的 imap_password）
 *   FATE_IMAP_REQUIRE_SPF     設 1 時，額外要求 Authentication-Results 非 spf=fail/softfail
 */

require_once __DIR__ . DIRECTORY_SEPARATOR . 'fate_member_email.php';
require_once __DIR__ . DIRECTORY_SEPARATOR . 'fate_api_keys.php';
require_once __DIR__ . DIRECTORY_SEPARATOR . 'fate_mail_settings.php';

/**
 * 反向 IMAP 設定。設定來源集中於 fate_mail_resolved_imap()
 * （後台 fate_mail_settings.json，環境變數 FATE_IMAP_* 優先；密碼走金鑰庫 imap_password）。
 *
 * @return array{host:string,port:int,flags:string,folder:string,address:string,user:string,pass:string,require_spf:bool}
 */
function fate_reverse_imap_config(): array
{
    $c = fate_mail_resolved_imap();

    // host 未填時預設 localhost（多數情境 IMAP 與站台同機）
    if ($c['host'] === '') {
        $c['host'] = 'localhost';
    }
    if ((int) $c['port'] <= 0) {
        $c['port'] = 993;
    }
    if ($c['folder'] === '') {
        $c['folder'] = 'INBOX';
    }
    if ($c['user'] === '') {
        $c['user'] = $c['address'];
    }

    return [
        'host' => $c['host'],
        'port' => (int) $c['port'],
        'flags' => $c['flags'],
        'folder' => $c['folder'],
        'address' => $c['address'],
        'user' => $c['user'],
        'pass' => $c['pass'],
        'require_spf' => (bool) $c['require_spf'],
    ];
}

/** 反向 IMAP 驗證是否可用（擴充已載入且設定齊全）。 */
function fate_reverse_imap_ready(): bool
{
    if (!function_exists('imap_open')) {
        return false;
    }
    $c = fate_reverse_imap_config();

    return $c['host'] !== '' && $c['user'] !== '' && $c['pass'] !== '';
}

/** 取得收驗證信的信箱地址（供前台提示顯示）。 */
function fate_reverse_imap_verify_address(): string
{
    return fate_reverse_imap_config()['address'];
}

/**
 * 測試 IMAP 連線（供後台「測試連線」按鈕）。使用已儲存的設定。
 *
 * @return array{ok:bool, error:?string, mailbox?:string, address?:string, total?:int, unseen?:int}
 */
function fate_reverse_imap_test(): array
{
    if (!function_exists('imap_open')) {
        return ['ok' => false, 'error' => '伺服器未啟用 PHP IMAP 擴充（請安裝 php-imap 後重啟）。'];
    }

    $c = fate_reverse_imap_config();
    if ($c['user'] === '' || $c['pass'] === '') {
        return ['ok' => false, 'error' => '尚未設定 IMAP 帳號或密碼（請先填寫並按「儲存設定」後再測試）。'];
    }

    $mailbox = fate_reverse_imap_mailbox($c);

    $inbox = @imap_open($mailbox, $c['user'], $c['pass'], 0, 1);
    if ($inbox === false) {
        $err = imap_last_error() ?: '連線失敗';
        imap_errors();
        return ['ok' => false, 'error' => '連線失敗：' . $err, 'mailbox' => $mailbox];
    }

    try {
        $total = (int) @imap_num_msg($inbox);
        $unseenHits = @imap_search($inbox, 'UNSEEN');
        $unseen = is_array($unseenHits) ? count($unseenHits) : 0;
        imap_errors();

        return [
            'ok' => true,
            'error' => null,
            'mailbox' => $mailbox,
            'address' => $c['address'],
            'total' => $total,
            'unseen' => $unseen,
        ];
    } catch (Throwable $e) {
        return ['ok' => false, 'error' => '讀取信箱失敗：' . $e->getMessage(), 'mailbox' => $mailbox];
    } finally {
        @imap_close($inbox);
        imap_errors();
    }
}

/** 組出 IMAP mailbox 連線字串。 */
function fate_reverse_imap_mailbox(array $c): string
{
    return '{' . $c['host'] . ':' . $c['port'] . $c['flags'] . '}' . $c['folder'];
}

/**
 * 由會員 id 取回小寫 email（找不到回空字串）。
 */
function fate_reverse_imap_member_email(PDO $pdo, int $memberId): string
{
    if ($memberId <= 0) {
        return '';
    }
    $db = str_replace('`', '``', (string) FATE_MEMBER_DATABASE);
    $tbl = str_replace('`', '``', (string) FATE_MEMBER_TABLE);
    try {
        $st = $pdo->prepare("SELECT `email` FROM `{$db}`.`{$tbl}` WHERE `id` = ? LIMIT 1");
        $st->execute([$memberId]);
        $email = $st->fetchColumn();
    } catch (Throwable) {
        return '';
    }

    return is_string($email) ? strtolower(trim($email)) : '';
}

/**
 * 從 IMAP overview 取出純寄件位址（小寫）。
 */
function fate_reverse_imap_overview_from(object $ov): string
{
    $raw = isset($ov->from) ? (string) $ov->from : '';
    if ($raw === '') {
        return '';
    }
    if (function_exists('imap_rfc822_parse_adrlist')) {
        $list = @imap_rfc822_parse_adrlist($raw, '');
        if (is_array($list) && isset($list[0]->mailbox, $list[0]->host)) {
            return strtolower($list[0]->mailbox . '@' . $list[0]->host);
        }
    }
    if (preg_match('/<([^>]+)>/', $raw, $m)) {
        return strtolower(trim($m[1]));
    }

    return strtolower(trim($raw));
}

/**
 * 檢查單封信 SPF 是否未失敗（require_spf 開啟時用）。
 */
function fate_reverse_imap_spf_ok($inbox, int $msgno): bool
{
    $header = (string) @imap_fetchheader($inbox, $msgno);
    if ($header === '') {
        return false;
    }
    if (preg_match('/Authentication-Results:.*spf=(fail|softfail)/is', $header)) {
        return false;
    }
    if (preg_match('/Received-SPF:\s*(fail|softfail)/is', $header)) {
        return false;
    }

    return true;
}

/**
 * 反向驗證主流程：檢查會員的註冊信箱是否寄過未讀信到驗證信箱；
 * 命中即沿用 fate_member_mark_email_verified() 標記已驗證，並刪信、清除。
 *
 * @return array{ok:bool, verified:bool, member:?array, detail:string}
 */
function fate_member_email_reverse_verify(PDO $pdo, int $memberId, ?string &$error = null): array
{
    $error = null;

    if (!function_exists('imap_open')) {
        $error = '伺服器未啟用 PHP IMAP 擴充（請安裝 php-imap）。';
        return ['ok' => false, 'verified' => false, 'member' => null, 'detail' => $error];
    }

    $email = fate_reverse_imap_member_email($pdo, $memberId);
    if ($email === '' || !filter_var($email, FILTER_VALIDATE_EMAIL)) {
        $error = '查無有效的會員 Email，請先於帳號設定填寫 Email。';
        return ['ok' => false, 'verified' => false, 'member' => null, 'detail' => $error];
    }

    $c = fate_reverse_imap_config();
    if ($c['host'] === '' || $c['user'] === '' || $c['pass'] === '') {
        $error = '反向驗證信箱尚未設定（FATE_IMAP_*）。';
        return ['ok' => false, 'verified' => false, 'member' => null, 'detail' => $error];
    }

    $mailbox = fate_reverse_imap_mailbox($c);

    // OP_READWRITE(0) 才能刪信；第 5 參數 retries=1 避免卡住
    $inbox = @imap_open($mailbox, $c['user'], $c['pass'], 0, 1);
    if ($inbox === false) {
        $err = imap_last_error() ?: '連線失敗';
        imap_errors();
        $error = '無法連線驗證信箱：' . $err;
        return ['ok' => false, 'verified' => false, 'member' => null, 'detail' => $error];
    }

    try {
        $criteria = 'UNSEEN FROM "' . str_replace('"', '', $email) . '"';
        $hits = @imap_search($inbox, $criteria, SE_UID);

        if ($hits === false || $hits === []) {
            imap_errors();
            $error = '尚未收到您的驗證來信，請確認已從註冊信箱寄出後再試（信件約需數秒送達）。';
            return ['ok' => true, 'verified' => false, 'member' => null, 'detail' => $error];
        }

        // 精確比對寄件位址，避免 IMAP FROM 子字串誤判
        $matchedUids = [];
        foreach ($hits as $uid) {
            $ovs = @imap_fetch_overview($inbox, (string) $uid, FT_UID);
            if (!is_array($ovs) || !isset($ovs[0])) {
                continue;
            }
            if (fate_reverse_imap_overview_from($ovs[0]) !== $email) {
                continue;
            }
            if ($c['require_spf']) {
                $msgno = imap_msgno($inbox, (int) $uid);
                if ($msgno <= 0 || !fate_reverse_imap_spf_ok($inbox, $msgno)) {
                    continue;
                }
            }
            $matchedUids[] = (int) $uid;
        }

        if ($matchedUids === []) {
            $error = '收到的來信寄件者與註冊信箱不符或未通過 SPF 檢查，請改用註冊信箱寄出。';
            return ['ok' => true, 'verified' => false, 'member' => null, 'detail' => $error];
        }

        // 沿用既有流程標記已驗證（同時升級會員等級、清除 token）
        $marked = fate_member_mark_email_verified($pdo, $memberId);

        // 命中信件標記刪除並永久清除（防重放、避免信箱堆積）
        foreach ($matchedUids as $uid) {
            @imap_delete($inbox, (string) $uid, FT_UID);
        }
        @imap_expunge($inbox);

        if (!$marked['ok']) {
            $error = (string) ($marked['detail'] ?? '更新驗證狀態失敗');
            return ['ok' => false, 'verified' => false, 'member' => null, 'detail' => $error];
        }

        return ['ok' => true, 'verified' => true, 'member' => $marked['member'] ?? null, 'detail' => ''];
    } catch (Throwable $e) {
        $error = '驗證過程發生錯誤：' . $e->getMessage();
        return ['ok' => false, 'verified' => false, 'member' => null, 'detail' => $error];
    } finally {
        @imap_close($inbox);
        imap_errors();
    }
}
