<?php

declare(strict_types=1);

require_once __DIR__ . DIRECTORY_SEPARATOR . 'admin_member_auth.php';
require_once __DIR__ . DIRECTORY_SEPARATOR . 'fate_member_password.php';

function fate_public_registration_disabled(): bool
{
    $v = strtolower(trim((string) (getenv('FATE_DISABLE_PUBLIC_REGISTRATION') ?: '')));

    return in_array($v, ['1', 'true', 'yes', 'on'], true);
}

/** @return array<string, true> lowercase field name => true */
function fate_member_table_columns(PDO $pdo): array
{
    static $cached = null;
    if (!empty($GLOBALS['fate_member_table_columns_reload'])) {
        $cached = null;
        unset($GLOBALS['fate_member_table_columns_reload']);
    }
    if ($cached !== null) {
        return $cached;
    }

    $cached = [];
    if (!fate_admin_member_auth_ready()) {
        return $cached;
    }

    $db = FATE_MEMBER_DATABASE;
    $tbl = FATE_MEMBER_TABLE;
    $sql = sprintf(
        'SHOW COLUMNS FROM `%s`.`%s`',
        str_replace('`', '``', $db),
        str_replace('`', '``', $tbl)
    );

    try {
        $st = $pdo->query($sql);
        if ($st !== false) {
            while ($row = $st->fetch(PDO::FETCH_ASSOC)) {
                $f = isset($row['Field']) ? strtolower((string) $row['Field']) : '';
                if ($f !== '') {
                    $cached[$f] = true;
                }
            }
        }
    } catch (Throwable) {
        $cached = [];
    }

    return $cached;
}

/**
 * 僅 ASCII 視為空格／禁用：避免帳號含一般空白或被零寬字元攻破登入鍵。
 */
function fate_public_member_normalize_username_trim(string $raw): string
{
    $u = trim($raw, " \t\n\r\0\x0B");
    if ($u === '') {
        return '';
    }

    return $u;
}

/**
 * @return array{ok:true, username:string}|array{ok:false, err:string}
 */
function fate_public_member_validate_username_candidate(string $raw): array
{
    $u = fate_public_member_normalize_username_trim($raw);
    if ($u === '') {
        return ['ok' => false, 'err' => '請輸入帳號。'];
    }

    if (class_exists('Normalizer')) {
        $n = Normalizer::normalize($u, Normalizer::FORM_C);
        if (is_string($n)) {
            $u = $n;
        }
    }

    if (preg_match('/[\s]/u', $u) === 1) {
        return ['ok' => false, 'err' => '帳號不可包含空白字元或換行。'];
    }

    $lenFn = static function (string $s): int {
        return function_exists('mb_strlen') ? (int) mb_strlen($s, 'UTF-8') : strlen($s);
    };

    $l = $lenFn($u);
    if ($l < 3) {
        return ['ok' => false, 'err' => '帳號至少 3 個字（可含中文、英文、數字等），且不可包含空白或換行。'];
    }
    if ($l > 80) {
        return ['ok' => false, 'err' => '帳號長度請勿超過 80 個字（以 Unicode 計）。'];
    }

    // Unicode 字母／數字＋ ._- （禁止符號類如 @ / # ，避免混淆或 URL 問題）
    if (!preg_match('/^[\p{L}\p{N}._\-]+$/u', $u)) {
        return ['ok' => false, 'err' => '帳號僅可使用文字（含中文）、數字、底線、點或連字號；不可輸入 @、#／空白等符號。'];
    }

    // 與 {3,80} 雙重確認（相容極少数 preg 環境異常）
    if (preg_match('/^[\p{L}\p{N}._\-]{3,80}$/u', $u) !== 1) {
        return ['ok' => false, 'err' => '帳號格式不正確。'];
    }

    return ['ok' => true, 'username' => $u];
}

/** 會員顯示名稱（暱稱）字數上限（Unicode 字元計，中文／英文／數字各算 1 字）。 */
function fate_member_display_name_max_chars(): int
{
    return 10;
}

/**
 * @return array{ok:true, display_name:string}|array{ok:false, err:string}
 */
function fate_public_member_validate_display_name_candidate(string $raw, bool $allowEmpty = false): array
{
    $name = trim($raw);
    if ($name === '') {
        if ($allowEmpty) {
            return ['ok' => true, 'display_name' => ''];
        }

        return ['ok' => false, 'err' => '請輸入顯示名稱。'];
    }

    $len = function_exists('mb_strlen') ? (int) mb_strlen($name, 'UTF-8') : strlen($name);
    $max = fate_member_display_name_max_chars();
    if ($len > $max) {
        return ['ok' => false, 'err' => '顯示名稱最多 ' . $max . ' 個字（中文、英文、數字各算 1 字）。'];
    }

    return ['ok' => true, 'display_name' => $name];
}

/** 註冊是否須填 Email（信箱驗證開啟，或人工審核且資料表含 email 欄位）。 */
function fate_member_register_email_required(PDO $pdo): bool
{
    if (fate_member_email_verify_feature_enabled($pdo)) {
        return true;
    }

    return fate_member_manual_approval_enabled() && fate_members_table_has_column($pdo, 'EMAIL');
}

/** 人工審核模式下，註冊完成後顯示給使用者的說明。 */
function fate_member_register_pending_message(): string
{
    return '帳號已建立，尚待管理員審核。';
}

/** 半遮罩收件信箱（驗證信提示頁用）。 */
function fate_member_public_mask_email(string $email): string
{
    $e = trim(strtolower($email));
    if ($e === '') {
        return '';
    }
    $at = strpos($e, '@');
    if ($at === false || $at <= 0) {
        return '***';
    }
    $user = substr($e, 0, $at);
    $domain = substr($e, $at + 1);
    $head = substr($user, 0, 1);

    return $head !== '' ? ($head . '***@' . $domain) : ('***@' . $domain);
}

/**
 * @return array{ok:bool,id:?int,error:?string}
 */
function fate_public_member_register(PDO $pdo, string $usernameRaw, string $plainPassword, string $displayNameRaw, string $emailRaw = ''): array
{
    if (!fate_admin_member_auth_ready()) {
        return ['ok' => false, 'id' => null, 'error' => '會員資料庫未設定完成。'];
    }

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

    $cols = fate_member_table_columns($pdo);
    if ($cols === []) {
        return ['ok' => false, 'id' => null, 'error' => '無法讀取會員資料表結構（表是否存在？）。'];
    }

    if (!isset($cols['username'], $cols['password'])) {
        return [
            'ok' => false,
            'id' => null,
            'error' => '會員表需含 username、password 欄位。可執行 fate/sql/install.sql 建立。',
        ];
    }

    $uv = fate_public_member_validate_username_candidate($usernameRaw);
    if (!$uv['ok']) {
        return ['ok' => false, 'id' => null, 'error' => $uv['err'] ?? '帳號格式不正確'];
    }
    $username = $uv['username'];

    $plainPwd = trim($plainPassword);
    if ($plainPwd === '') {
        return ['ok' => false, 'id' => null, 'error' => '請輸入密碼。'];
    }
    $pwLen = function_exists('mb_strlen') ? mb_strlen($plainPwd, 'UTF-8') : strlen($plainPwd);
    if ($pwLen < 8) {
        return ['ok' => false, 'id' => null, 'error' => '密碼至少 8 字元。'];
    }
    $plainPwd = substr($plainPwd, 0, 72);

    $dn = trim($displayNameRaw);
    if ($dn === '') {
        $dn = $username;
    } else {
        $dv = fate_public_member_validate_display_name_candidate($dn);
        if (!$dv['ok']) {
            return ['ok' => false, 'id' => null, 'error' => $dv['err'] ?? '顯示名稱格式不正確。'];
        }
        $dn = $dv['display_name'];
    }

    $wantMailVerify = fate_member_email_verify_feature_enabled($pdo);
    $needsEmail = fate_member_register_email_required($pdo);
    $mailNorm = strtolower(trim((string) $emailRaw));
    if ($needsEmail) {
        if ($mailNorm === '') {
            $emptyErr = fate_member_manual_approval_enabled()
                ? '請填寫 Email。'
                : '請填寫 Email（註冊後須收取驗證信才可登入）。';

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

    $db = FATE_MEMBER_DATABASE;
    $tbl = FATE_MEMBER_TABLE;

    try {
        $uq = sprintf(
            'SELECT `id` FROM `%s`.`%s` WHERE `username` = ? LIMIT 1',
            str_replace('`', '``', $db),
            str_replace('`', '``', $tbl)
        );
        $st = $pdo->prepare($uq);
        $st->execute([$username]);
        if ((int) $st->fetchColumn() > 0) {
            return ['ok' => false, 'id' => null, 'error' => '此帳號已被使用，請換一個。'];
        }
    } catch (Throwable $e) {
        return ['ok' => false, 'id' => null, 'error' => '無法查詢會員表：' . $e->getMessage()];
    }

    $hash = fate_member_password_hash_new($plainPwd);

    /** @var array<string, mixed> keyed column (no backticks) => value */
    $rowVals = [];
    $rowVals['username'] = $username;
    $rowVals['password'] = $hash;

    if (isset($cols['display_name'])) {
        $rowVals['display_name'] = $dn;
    }

    if ($needsEmail && isset($cols['email'])) {
        $rowVals['email'] = $mailNorm;
        if (isset($cols['email_verified_at'])) {
            $rowVals['email_verified_at'] = null;
        }

        $existingMail = fate_member_lookup_by_email($pdo, $mailNorm);
        if ($existingMail['found']) {
            if ($wantMailVerify && !$existingMail['verified']) {
                return [
                    'ok' => false,
                    'id' => null,
                    'error' => '此 Email 已註冊但尚未驗證。請改用原帳號登入，或至「還沒收到驗證信？」重寄驗證信。',
                ];
            }

            return [
                'ok' => false,
                'id' => null,
                'error' => '此 Email 已被使用。若為您的帳號，請直接登入。',
            ];
        }
    }

    $needsManualApproval = fate_member_manual_approval_enabled() && isset($cols['approved_at']);
    if ($needsManualApproval) {
        $rowVals['approved_at'] = null;
    }

    $activeVal = $needsManualApproval ? 0 : 1;
    $acColLow = strtolower(FATE_MEMBER_ACTIVE_COLUMN);
    $activeInserted = false;
    if ($acColLow !== '' && isset($cols[$acColLow])) {
        $rowVals[FATE_MEMBER_ACTIVE_COLUMN] = $activeVal;
        $activeInserted = true;
    }
    if (!$activeInserted && isset($cols['status'])) {
        $rowVals['status'] = $activeVal;
        $activeInserted = true;
    }
    if (!$activeInserted) {
        return [
            'ok' => false,
            'id' => null,
            'error' => '會員表缺少啟用欄位（環境變數 FATE_MEMBER_ACTIVE_COLUMN，或資料表须有 status）。',
        ];
    }

    if (isset($cols['role'])) {
        $rowVals['role'] = '';
    }

    try {
        $colNames = array_keys($rowVals);
        $quoted = [];
        foreach ($colNames as $cn) {
            $quoted[] = '`' . str_replace('`', '``', (string) $cn) . '`';
        }
        $holders = implode(', ', array_fill(0, count($rowVals), '?'));
        $sql = sprintf(
            'INSERT INTO `%s`.`%s` (%s) VALUES (%s)',
            str_replace('`', '``', $db),
            str_replace('`', '``', $tbl),
            implode(', ', $quoted),
            $holders
        );
        $ins = $pdo->prepare($sql);
        $ins->execute(array_values($rowVals));
        $newId = (int) $pdo->lastInsertId();

        return ['ok' => true, 'id' => $newId > 0 ? $newId : null, 'error' => null];
    } catch (Throwable $e) {
        $msg = $e->getMessage();
        if (str_contains($msg, '1062') && str_contains($msg, 'email')) {
            return [
                'ok' => false,
                'id' => null,
                'error' => '此 Email 已被使用。若您曾註冊過，請直接登入，或至登入頁的「還沒收到驗證信？」重寄驗證信。',
            ];
        }
        if (str_contains($msg, '1062') && str_contains($msg, 'username')) {
            return ['ok' => false, 'id' => null, 'error' => '此帳號已被使用，請換一個。'];
        }

        return ['ok' => false, 'id' => null, 'error' => '註冊失敗：' . $msg];
    }
}

/**
 * 註冊表單 AJAX：欄位是否可用。
 *
 * @return array{ok:bool,field:string,valid:bool,available:bool,message:string}
 */
function fate_register_check_field_availability(PDO $pdo, string $field, string $valueRaw): array
{
    $field = strtolower(trim($field));
    $bad = static function (string $f, bool $valid, bool $available, string $message): array {
        return [
            'ok' => true,
            'field' => $f,
            'valid' => $valid,
            'available' => $available,
            'message' => $message,
        ];
    };

    if (!fate_admin_member_auth_ready()) {
        return ['ok' => false, 'field' => $field, 'valid' => false, 'available' => false, 'message' => '會員功能未就緒。'];
    }

    require_once __DIR__ . DIRECTORY_SEPARATOR . 'fate_member_email.php';
    fate_member_email_bootstrap_schema($pdo);
    $cols = fate_member_table_columns($pdo);
    if ($cols === []) {
        return ['ok' => false, 'field' => $field, 'valid' => false, 'available' => false, 'message' => '無法讀取會員表。'];
    }

    $db = FATE_MEMBER_DATABASE;
    $tbl = FATE_MEMBER_TABLE;
    $dbEsc = str_replace('`', '``', $db);
    $tblEsc = str_replace('`', '``', $tbl);

    if ($field === 'username') {
        $uv = fate_public_member_validate_username_candidate($valueRaw);
        if (!$uv['ok']) {
            return $bad('username', false, false, (string) ($uv['err'] ?? '帳號格式不正確。'));
        }
        try {
            $st = $pdo->prepare(sprintf('SELECT `id` FROM `%s`.`%s` WHERE `username` = ? LIMIT 1', $dbEsc, $tblEsc));
            $st->execute([$uv['username']]);
            if ((int) $st->fetchColumn() > 0) {
                return $bad('username', true, false, '此帳號已被使用，請換一個。');
            }

            return $bad('username', true, true, '此帳號可以使用。');
        } catch (Throwable $e) {
            return ['ok' => false, 'field' => 'username', 'valid' => false, 'available' => false, 'message' => '無法查詢帳號。'];
        }
    }

    if ($field === 'display_name') {
        $dn = trim($valueRaw);
        if ($dn === '') {
            return $bad('display_name', true, true, '');
        }
        $dv = fate_public_member_validate_display_name_candidate($dn);
        if (!$dv['ok']) {
            return $bad('display_name', false, false, (string) ($dv['err'] ?? '顯示名稱格式不正確。'));
        }
        $dn = $dv['display_name'];
        if (!isset($cols['display_name'])) {
            return $bad('display_name', true, true, '');
        }
        try {
            $st = $pdo->prepare(sprintf('SELECT `id` FROM `%s`.`%s` WHERE `display_name` = ? LIMIT 1', $dbEsc, $tblEsc));
            $st->execute([$dn]);
            if ((int) $st->fetchColumn() > 0) {
                return $bad('display_name', true, false, '此暱稱已被使用，請換一個。');
            }

            return $bad('display_name', true, true, '此暱稱可以使用。');
        } catch (Throwable $e) {
            return ['ok' => false, 'field' => 'display_name', 'valid' => false, 'available' => false, 'message' => '無法查詢暱稱。'];
        }
    }

    if ($field === 'email') {
        if (!isset($cols['email'])) {
            return $bad('email', true, true, '');
        }
        $mailNorm = strtolower(trim($valueRaw));
        if ($mailNorm === '') {
            return $bad('email', false, false, '請填寫 Email。');
        }
        $validEv = filter_var($mailNorm, FILTER_VALIDATE_EMAIL);
        if ($validEv === false) {
            return $bad('email', false, false, '請填寫有效的 Email。');
        }
        $mailNorm = strtolower(trim((string) $validEv));

        require_once __DIR__ . DIRECTORY_SEPARATOR . 'fate_member_email.php';
        $existing = fate_member_lookup_by_email($pdo, $mailNorm);
        if ($existing['found']) {
            if (!$existing['verified']) {
                return $bad(
                    'email',
                    true,
                    false,
                    '此 Email 已註冊但尚未驗證。請改用原帳號登入，或至登入頁重寄驗證信。'
                );
            }

            return $bad('email', true, false, '此 Email 已被使用。若為您的帳號，請直接登入。');
        }

        return $bad('email', true, true, '此 Email 可以使用。');
    }

    return ['ok' => false, 'field' => $field, 'valid' => false, 'available' => false, 'message' => '不支援的欄位。'];
}
