<?php

declare(strict_types=1);

require_once __DIR__ . DIRECTORY_SEPARATOR . 'admin_member_auth.php';
require_once __DIR__ . DIRECTORY_SEPARATOR . 'fate_member_email.php';
require_once __DIR__ . DIRECTORY_SEPARATOR . 'fate_public_member_auth.php';
require_once __DIR__ . DIRECTORY_SEPARATOR . 'fate_member_register.php';

/**
 * @return array{
 *   enabled:bool,
 *   email:string,
 *   masked:string,
 *   verified:bool,
 *   verified_at:?string
 * }
 */
function fate_member_account_email_status(PDO $pdo, int $memberId): array
{
    $empty = [
        'enabled' => false,
        'email' => '',
        'masked' => '',
        'verified' => true,
        'verified_at' => null,
    ];
    if ($memberId <= 0 || !fate_admin_member_auth_ready()) {
        return $empty;
    }
    if (!fate_member_email_verify_feature_enabled($pdo)) {
        return $empty;
    }

    $fr = fate_public_member_login_row_fetch_by_id($pdo, $memberId);
    if (!$fr['found'] || !is_array($fr['row'])) {
        return $empty;
    }
    $row = $fr['row'];
    $email = isset($row['email']) ? trim((string) $row['email']) : '';
    $verifiedAt = isset($row['email_verified_at']) ? trim((string) $row['email_verified_at']) : '';
    $verified = $verifiedAt !== '' || !fate_member_requires_email_verify_for_row($pdo, $row);

    return [
        'enabled' => true,
        'email' => $email,
        'masked' => $email !== '' ? fate_member_public_mask_email($email) : '',
        'verified' => $verified,
        'verified_at' => $verifiedAt !== '' ? $verifiedAt : null,
    ];
}

/** @return array{found:bool,row:?array<string,mixed>} */
function fate_public_member_login_row_fetch_by_id(PDO $pdo, int $memberId): array
{
    if ($memberId <= 0 || !fate_admin_member_auth_ready()) {
        return ['found' => false, 'row' => null];
    }
    $db = FATE_MEMBER_DATABASE;
    $tbl = FATE_MEMBER_TABLE;
    $ac = FATE_MEMBER_ACTIVE_COLUMN;
    $cols = ['`id`', '`username`', '`password`', '`display_name`', '`role`', sprintf('`%s` AS `_mem_active`', str_replace('`', '``', $ac))];
    if (fate_members_table_has_column($pdo, 'EMAIL')) {
        $cols[] = '`email`';
    }
    if (fate_members_table_has_column($pdo, 'EMAIL_VERIFIED_AT')) {
        $cols[] = '`email_verified_at`';
    }
    if (fate_member_manual_approval_enabled() && fate_members_table_has_column($pdo, 'APPROVED_AT')) {
        $cols[] = '`approved_at`';
    }
    $sql = sprintf(
        'SELECT %s FROM `%s`.`%s` WHERE `id` = ? LIMIT 1',
        implode(', ', $cols),
        str_replace('`', '``', $db),
        str_replace('`', '``', $tbl)
    );
    try {
        $st = $pdo->prepare($sql);
        $st->execute([$memberId]);
        $row = $st->fetch(PDO::FETCH_ASSOC);

        return is_array($row) ? ['found' => true, 'row' => $row] : ['found' => false, 'row' => null];
    } catch (Throwable) {
        return ['found' => false, 'row' => null];
    }
}

function fate_public_member_sync_email_verified_session(PDO $pdo, int $memberId): void
{
    $st = fate_member_account_email_status($pdo, $memberId);
    fate_public_member_set_email_verified_session($st['verified'] || !$st['enabled']);
}

/**
 * 密碼正確時回傳會員（含尚未驗證 Email 者）。
 *
 * @return array{ok:bool,err?:string,member?:array{id:int,username:string,display_name:string,role:string},needs_email_verify?:bool}
 */
function fate_public_member_verify_credentials(PDO $pdo, string $username, string $password): array
{
    if (!fate_admin_member_auth_ready()) {
        return ['ok' => false, 'err' => 'config'];
    }
    $u = trim($username);
    if ($u === '' || $password === '') {
        return ['ok' => false, 'err' => 'invalid'];
    }

    require_once __DIR__ . DIRECTORY_SEPARATOR . 'fate_member_email.php';
    fate_member_email_bootstrap_schema($pdo);

    try {
        $fetch = fate_public_member_login_row_fetch($pdo, $u);
    } catch (PDOException $e) {
        return ['ok' => false, 'err' => 'invalid'];
    }

    if (!$fetch['found'] || !is_array($fetch['row'])) {
        return ['ok' => false, 'err' => 'invalid'];
    }

    $row = $fetch['row'];
    $hash = isset($row['password']) ? (string) $row['password'] : '';
    if ($hash === '' || !fate_member_verify_password($password, $hash)) {
        return ['ok' => false, 'err' => 'invalid'];
    }
    if ((int) ($row['_mem_active'] ?? 0) !== 1) {
        return ['ok' => false, 'err' => 'invalid'];
    }

    $id = (int) ($row['id'] ?? 0);
    if ($id <= 0) {
        return ['ok' => false, 'err' => 'invalid'];
    }

    $needsVerify = fate_member_requires_email_verify_for_row($pdo, $row);
    if (!$needsVerify) {
        fate_admin_member_touch_last_login($pdo, $id);
    }

    return [
        'ok' => true,
        'member' => [
            'id' => $id,
            'username' => (string) ($row['username'] ?? ''),
            'display_name' => isset($row['display_name']) ? (string) $row['display_name'] : '',
            'role' => isset($row['role']) ? trim((string) $row['role']) : '',
        ],
        'needs_email_verify' => $needsVerify,
    ];
}

/**
 * @return array{ok:bool,detail:string}
 */
function fate_member_resend_verify_for_member_id(PDO $pdo, int $memberId): array
{
    if ($memberId <= 0) {
        return ['ok' => false, 'detail' => '無效的會員'];
    }

    $fr = fate_public_member_login_row_fetch_by_id($pdo, $memberId);
    if (!$fr['found'] || !is_array($fr['row'])) {
        return ['ok' => false, 'detail' => '找不到會員'];
    }

    $row = $fr['row'];
    if (!fate_member_requires_email_verify_for_row($pdo, $row)) {
        return ['ok' => false, 'detail' => '您的信箱已驗證，無須重寄。'];
    }

    if (!function_exists('fate_verify_method_uses_token')) {
        require_once __DIR__ . DIRECTORY_SEPARATOR . 'fate_mail_settings.php';
    }
    if (!fate_verify_method_uses_token()) {

        return ['ok' => false, 'detail' => '本站未啟用寄信驗證，請改用其他驗證方式。'];

    }

    $cool = fate_member_resend_cooldown_ok();
    if (!$cool['ok']) {
        return ['ok' => false, 'detail' => '請於 ' . (string) $cool['secs'] . ' 秒後再試。'];
    }

    $mail = isset($row['email']) ? trim((string) $row['email']) : '';
    if ($mail === '' || !filter_var($mail, FILTER_VALIDATE_EMAIL)) {
        return ['ok' => false, 'detail' => '請先填寫有效的 Email。'];
    }

    $label = fate_public_member_display_label([
        'id' => $memberId,
        'username' => (string) ($row['username'] ?? ''),
        'display_name' => trim((string) ($row['display_name'] ?? '')),
        'avatar_rel' => '',
    ]) ?: (string) ($row['username'] ?? '');

    $snd = fate_member_send_verify_email_token($pdo, $memberId, $mail, $label);
    if (!$snd['ok']) {
        return ['ok' => false, 'detail' => $snd['detail'] ?? '送信失敗'];
    }
    fate_member_resend_mark();

    return ['ok' => true, 'detail' => ''];
}

/**
 * 僅限尚未驗證：更新 Email、清除驗證時間並重寄驗證信。
 *
 * @return array{ok:bool,detail:string,email?:string}
 */
function fate_member_change_email_unverified(PDO $pdo, int $memberId, string $newEmailRaw): array
{
    if ($memberId <= 0) {
        return ['ok' => false, 'detail' => '無效的會員'];
    }
    if (!fate_member_email_verify_feature_enabled($pdo)) {
        return ['ok' => false, 'detail' => '本站未啟用信箱驗證'];
    }

    $fr = fate_public_member_login_row_fetch_by_id($pdo, $memberId);
    if (!$fr['found'] || !is_array($fr['row'])) {
        return ['ok' => false, 'detail' => '找不到會員'];
    }
    $row = $fr['row'];
    if (!fate_member_requires_email_verify_for_row($pdo, $row)) {
        return ['ok' => false, 'detail' => '信箱已驗證，無法自行修改。若有需要請聯絡管理員。'];
    }

    $mailNorm = strtolower(trim($newEmailRaw));
    $validEv = filter_var($mailNorm, FILTER_VALIDATE_EMAIL);
    if ($validEv === false) {
        return ['ok' => false, 'detail' => '請填寫有效的 Email。'];
    }
    $mailNorm = strtolower(trim((string) $validEv));

    $existing = fate_member_lookup_by_email($pdo, $mailNorm);
    if ($existing['found'] && (int) ($existing['id'] ?? 0) !== $memberId) {
        if (!empty($existing['verified'])) {
            return ['ok' => false, 'detail' => '此 Email 已被其他帳號使用。'];
        }

        return ['ok' => false, 'detail' => '此 Email 已註冊但尚未驗證，請改用該帳號登入或換一個信箱。'];
    }

    $db = FATE_MEMBER_DATABASE;
    $tbl = FATE_MEMBER_TABLE;
    try {
        $sql = sprintf(
            'UPDATE `%s`.`%s` SET `email` = ?, `email_verified_at` = NULL WHERE `id` = ? LIMIT 1',
            str_replace('`', '``', $db),
            str_replace('`', '``', $tbl)
        );
        $pdo->prepare($sql)->execute([$mailNorm, $memberId]);
    } catch (Throwable $e) {
        return ['ok' => false, 'detail' => '無法更新 Email：' . $e->getMessage()];
    }

    try {
        $pdo->prepare('DELETE FROM `fate_member_email_verify_tokens` WHERE `member_id` = ?')->execute([$memberId]);
    } catch (Throwable) {
        // 略過
    }

    if (!function_exists('fate_verify_method_uses_token')) {
        require_once __DIR__ . DIRECTORY_SEPARATOR . 'fate_mail_settings.php';
    }
    if (fate_verify_method_uses_token()) {
        $snd = fate_member_resend_verify_for_member_id($pdo, $memberId);
        if (!$snd['ok']) {
            return ['ok' => false, 'detail' => 'Email 已更新，但驗證信未能寄出：' . ($snd['detail'] ?? '')];
        }

        return ['ok' => true, 'detail' => '已更新 Email 並寄出驗證信。', 'email' => $mailNorm];
    }

    return ['ok' => true, 'detail' => '已更新 Email，請依指示完成驗證。', 'email' => $mailNorm];
}

/**
 * 新手村會員表（預設 spa.users，含 ncoin）。
 */
function fate_member_spa_users_qualified(): ?string
{
    $db = defined('FATE_SPA_USERS_DATABASE')
        ? (string) FATE_SPA_USERS_DATABASE
        : trim((string) (getenv('FATE_SPA_USERS_DATABASE') ?: 'spa'));
    $tbl = defined('FATE_SPA_USERS_TABLE')
        ? (string) FATE_SPA_USERS_TABLE
        : trim((string) (getenv('FATE_SPA_USERS_TABLE') ?: 'users'));
    if (!preg_match('/^[A-Za-z0-9_]+$/', $db) || !preg_match('/^[A-Za-z0-9_]+$/', $tbl)) {
        return null;
    }

    return sprintf(
        '`%s`.`%s`',
        str_replace('`', '``', $db),
        str_replace('`', '``', $tbl)
    );
}

function fate_member_normalize_ncoin_amount(mixed $raw): int
{
    if (!is_numeric($raw)) {
        return 0;
    }

    return max(0, (int) round((float) $raw));
}

/** 確保 FATE 會員表有 ncoin 欄位（站內錢包）。 */
function fate_member_ensure_ncoin_column(PDO $pdo): void
{
    static $ensured = false;
    if ($ensured || !fate_admin_member_auth_ready()) {
        return;
    }
    $ensured = true;

    $cols = fate_member_table_columns($pdo);
    if (isset($cols['ncoin'])) {
        return;
    }

    $dbEsc = str_replace('`', '``', (string) FATE_MEMBER_DATABASE);
    $tblEsc = str_replace('`', '``', (string) FATE_MEMBER_TABLE);
    try {
        $pdo->exec(sprintf(
            'ALTER TABLE `%s`.`%s` ADD COLUMN `ncoin` INT NOT NULL DEFAULT 0',
            $dbEsc,
            $tblEsc
        ));
    } catch (Throwable) {
        /* 可能已由其他請求新增 */
    }
    $GLOBALS['fate_member_table_columns_reload'] = true;
}

/** 讀取 FATE 會員表 ncoin（無欄位時回傳 null）。 */
function fate_member_read_local_ncoin(PDO $pdo, int $memberId): ?int
{
    if ($memberId <= 0 || !fate_admin_member_auth_ready()) {
        return null;
    }
    $cols = fate_member_table_columns($pdo);
    if (!isset($cols['ncoin'])) {
        return null;
    }
    $dbEsc = str_replace('`', '``', (string) FATE_MEMBER_DATABASE);
    $tblEsc = str_replace('`', '``', (string) FATE_MEMBER_TABLE);
    try {
        $st = $pdo->prepare(sprintf('SELECT `ncoin` FROM `%s`.`%s` WHERE `id` = ? LIMIT 1', $dbEsc, $tblEsc));
        $st->execute([$memberId]);
        $raw = $st->fetchColumn();
        if ($raw === false) {
            return null;
        }

        return fate_member_normalize_ncoin_amount($raw);
    } catch (Throwable) {
        return null;
    }
}

/** 將站內 ncoin 寫回會員表（補齊帳本與欄位落差）。 */
function fate_member_sync_local_ncoin(PDO $pdo, int $memberId, int $balance): void
{
    if ($memberId <= 0) {
        return;
    }
    fate_member_ensure_ncoin_column($pdo);
    $cols = fate_member_table_columns($pdo);
    if (!isset($cols['ncoin'])) {
        return;
    }
    $balance = max(0, $balance);
    $dbEsc = str_replace('`', '``', (string) FATE_MEMBER_DATABASE);
    $tblEsc = str_replace('`', '``', (string) FATE_MEMBER_TABLE);
    try {
        $pdo->prepare(sprintf('UPDATE `%s`.`%s` SET `ncoin` = ? WHERE `id` = ? LIMIT 1', $dbEsc, $tblEsc))
            ->execute([$balance, $memberId]);
    } catch (Throwable) {
        /* 略過 */
    }
}

/**
 * 依 Email／帳號查主站 spa.users.ncoin。
 *
 * @return int|null null＝無主站對應或無 ncoin 欄位
 */
function fate_member_lookup_spa_ncoin(PDO $pdo, int $memberId, string $username = ''): ?int
{
    if ($memberId <= 0) {
        return null;
    }

    $spaQt = fate_member_spa_users_qualified();
    if ($spaQt === null) {
        return null;
    }

    try {
        $colChk = $pdo->query(sprintf('SHOW COLUMNS FROM %s LIKE \'ncoin\'', $spaQt));
        if ($colChk === false || $colChk->fetch(PDO::FETCH_ASSOC) === false) {
            return null;
        }
    } catch (Throwable) {
        return null;
    }

    $email = '';
    $uname = trim($username);
    $fr = fate_public_member_login_row_fetch_by_id($pdo, $memberId);
    if ($fr['found'] && is_array($fr['row'])) {
        $email = isset($fr['row']['email']) ? trim((string) $fr['row']['email']) : '';
        if ($uname === '' && isset($fr['row']['username'])) {
            $uname = trim((string) $fr['row']['username']);
        }
    }

    $lookup = static function (PDO $pdo, string $qualified, string $whereSql, string $param): ?int {
        try {
            $st = $pdo->prepare(sprintf('SELECT `ncoin` FROM %s WHERE %s LIMIT 1', $qualified, $whereSql));
            $st->execute([$param]);
            $raw = $st->fetchColumn();
            if ($raw === false) {
                return null;
            }

            return fate_member_normalize_ncoin_amount($raw);
        } catch (Throwable $e) {
            if (str_contains($e->getMessage(), 'Unknown column') && str_contains($e->getMessage(), 'ncoin')) {
                return null;
            }
            throw $e;
        }
    };

    try {
        if ($email !== '' && filter_var($email, FILTER_VALIDATE_EMAIL)) {
            $byEmail = $lookup($pdo, $spaQt, '`email` = ?', $email);
            if ($byEmail !== null) {
                return $byEmail;
            }
        }
        if ($uname !== '') {
            $byName = $lookup($pdo, $spaQt, '`name` = ?', $uname);
            if ($byName !== null) {
                return $byName;
            }
            if (filter_var($uname, FILTER_VALIDATE_EMAIL)) {
                $byUnameEmail = $lookup($pdo, $spaQt, '`email` = ?', $uname);
                if ($byUnameEmail !== null) {
                    return $byUnameEmail;
                }
            }
        }
    } catch (Throwable) {
        return null;
    }

    return null;
}

/** 帳本淨額（fate_reward_ledger）；無表或無紀錄時可能為 0 或 null。 */
function fate_member_ledger_ncoin_net(PDO $pdo, int $memberId): ?int
{
    if ($memberId <= 0) {
        return null;
    }
    try {
        $st = $pdo->prepare(
            'SELECT COALESCE(SUM(`amount`), 0) - COALESCE(SUM(CASE WHEN `reverted` = 1 THEN `amount` ELSE 0 END), 0) AS net
             FROM `fate_reward_ledger` WHERE `member_id` = ?'
        );
        $st->execute([$memberId]);
        $raw = $st->fetchColumn();
        if ($raw === false) {
            return null;
        }

        return max(0, (int) $raw);
    } catch (Throwable) {
        return null;
    }
}

/**
 * 讀取站內 N 幣餘額（側欄／會員頁顯示用）。
 *
 * 優先 FATE 會員表 ncoin，並以帳本淨額補齊未入帳的歷史獎勵；
 * 無站內欄位時改查主站 spa.users；皆無則不顯示（null）。
 *
 * @return int|null null＝系統無 ncoin 可讀（不顯示區塊）
 */
function fate_member_read_ncoin(PDO $pdo, int $memberId, string $username = ''): ?int
{
    if ($memberId <= 0 || !fate_admin_member_auth_ready()) {
        return null;
    }

    fate_member_ensure_ncoin_column($pdo);

    $cols = fate_member_table_columns($pdo);
    $hasLocal = isset($cols['ncoin']);
    $hasSpa = fate_member_spa_users_qualified() !== null;
    $ledgerNet = fate_member_ledger_ncoin_net($pdo, $memberId);
    $hasLedger = $ledgerNet !== null;

    if (!$hasLocal && !$hasSpa && !$hasLedger) {
        return null;
    }

    $local = $hasLocal ? fate_member_read_local_ncoin($pdo, $memberId) : null;
    $spa = $hasSpa ? fate_member_lookup_spa_ncoin($pdo, $memberId, $username) : null;

    if ($hasLocal && $local !== null) {
        $balance = $local;
        if ($ledgerNet !== null && $ledgerNet > $balance) {
            $balance = $ledgerNet;
            fate_member_sync_local_ncoin($pdo, $memberId, $balance);
        } elseif ($balance === 0 && $spa !== null && ($ledgerNet === null || $ledgerNet === 0)) {
            return $spa;
        }

        return $balance;
    }

    if ($spa !== null) {
        return $spa;
    }
    if ($ledgerNet !== null) {
        if ($hasLocal) {
            fate_member_sync_local_ncoin($pdo, $memberId, $ledgerNet);
        }

        return $ledgerNet;
    }

    return 0;
}
