<?php
declare(strict_types=1);

/**
 * /forgot_password.php
 * Запит на відновлення пароля по email.
 *
 * Поведінка:
 * - noindex + no-store
 * - CSRF
 * - IP block (як для login)
 * - rate-limit (email scopes + uid scopes)
 * - не розкриває, чи існує email у системі
 * - відправляє лист із посиланням на /reset_password.php?token=...
 */

require_once __DIR__ . '/includes/start.php';
require_once __DIR__ . '/includes/mailer.php';
require_once __DIR__ . '/includes/security.php';
require_once __DIR__ . '/includes/security_ip_rules.php';
require_once __DIR__ . '/includes/security_rate_limit.php';

if (!isset($pdo) || !($pdo instanceof PDO)) {
    $pdo = function_exists('db') ? db() : null;
}
if (!($pdo instanceof PDO)) {
    http_response_code(500);
    exit('DB error');
}

if (!function_exists('tr_forgot')) {
    function tr_forgot(string $key, array $fallbacks): string {
        $v = function_exists('t') ? (string)t($key) : $key;
        if ($v !== '' && $v !== $key) return $v;

        $l = function_exists('lang') ? (string)lang() : 'uk';
        if (!in_array($l, ['uk', 'ru', 'en'], true)) $l = 'uk';

        return (string)($fallbacks[$l] ?? $fallbacks['uk'] ?? $key);
    }
}
if (!function_exists('h')) {
    function h(?string $s): string { return htmlspecialchars((string)$s, ENT_QUOTES, 'UTF-8'); }
}
if (!function_exists('db_has_column')) {
    function db_has_column(PDO $pdo, string $table, string $col): bool {
        try {
            $st = $pdo->prepare("SELECT 1 FROM information_schema.columns WHERE table_schema = DATABASE() AND table_name = ? AND column_name = ? LIMIT 1");
            $st->execute([$table, $col]);
            return (bool)$st->fetchColumn();
        } catch (Throwable $e) {
            return false;
        }
    }
}
if (!function_exists('pick_first_existing_col')) {
    function pick_first_existing_col(PDO $pdo, string $table, array $candidates): ?string {
        foreach ($candidates as $c) {
            if (db_has_column($pdo, $table, (string)$c)) return (string)$c;
        }
        return null;
    }
}
if (!function_exists('db_table_exists_local')) {
    function db_table_exists_local(PDO $pdo, string $table): bool {
        if (function_exists('start_db_table_exists')) {
            try {
                return (bool)start_db_table_exists($pdo, $table);
            } catch (Throwable $e) {
                // fallback нижче
            }
        }

        try {
            $st = $pdo->prepare("SELECT 1 FROM information_schema.tables WHERE table_schema = DATABASE() AND table_name = ? LIMIT 1");
            $st->execute([$table]);
            return (bool)$st->fetchColumn();
        } catch (Throwable $e) {
            return false;
        }
    }
}
if (!function_exists('redirect_303')) {
    function redirect_303(string $to): void {
        if (!headers_sent()) {
            header('Location: ' . $to, true, 303);
        } else {
            echo '<meta http-equiv="refresh" content="0;url=' . htmlspecialchars($to, ENT_QUOTES, 'UTF-8') . '">';
            echo '<script>location.href=' . json_encode($to) . ';</script>';
        }
        exit;
    }
}
if (!function_exists('abs_url_local')) {
    function abs_url_local(string $path): string {
        if (function_exists('abs_url')) return (string)abs_url($path);
        $https = (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off');
        $scheme = $https ? 'https' : 'http';
        $host = (string)($_SERVER['HTTP_HOST'] ?? 'localhost');
        return $scheme . '://' . $host . $path;
    }
}

function forgot_rate_build_email_scopes(string $mode, string $ip, string $email): array {
    $ip = trim($ip);
    $email = mb_strtolower(trim($email), 'UTF-8');

    $scopes = [];

    if ($mode === 'ip') {
        if ($ip !== '') $scopes[] = ['type' => 'ip', 'key' => $ip];
    } elseif ($mode === 'user') {
        if ($email !== '') $scopes[] = ['type' => 'email', 'key' => $email];
    } else {
        if ($ip !== '' && $email !== '') $scopes[] = ['type' => 'ip_email', 'key' => $ip . '|' . $email];
        if ($ip !== '') $scopes[] = ['type' => 'ip', 'key' => $ip];
        if ($email !== '') $scopes[] = ['type' => 'email', 'key' => $email];
    }

    $uniq = [];
    foreach ($scopes as $s) {
        $k = (string)$s['type'] . '|' . (string)$s['key'];
        $uniq[$k] = $s;
    }
    return array_values($uniq);
}

function forgot_rate_lock_message(?string $lockedUntil): string {
    $lockedUntil = trim((string)$lockedUntil);

    $generic = tr_forgot('rate_locked_generic', [
        'uk' => 'Забагато спроб. Спробуйте пізніше.',
        'ru' => 'Слишком много попыток. Попробуйте позже.',
        'en' => 'Too many attempts. Try again later.',
    ]);

    if ($lockedUntil === '') return $generic;

    $ts = strtotime($lockedUntil);
    if ($ts <= 0) return $generic;

    $leftSec = $ts - time();
    if ($leftSec < 0) $leftSec = 0;
    $mins = (int)ceil($leftSec / 60);
    if ($mins < 1) $mins = 1;

    return tr_forgot('rate_locked_until', [
        'uk' => 'Забагато спроб. Спробуйте знову через ' . $mins . ' хв.',
        'ru' => 'Слишком много попыток. Попробуйте снова через ' . $mins . ' мин.',
        'en' => 'Too many attempts. Try again in ' . $mins . ' min.',
    ]);
}

$title = tr_forgot('forgot_password_title', [
    'uk' => 'Відновлення пароля',
    'ru' => 'Восстановление пароля',
    'en' => 'Password reset',
]);

$meta_robots = 'noindex, nofollow, noarchive, nosnippet';
$meta_desc   = '';
$canonical   = function_exists('abs_url') ? abs_url('/forgot_password.php') : '/forgot_password.php';
if (!headers_sent()) {
    header('X-Robots-Tag: noindex, nofollow, noarchive, nosnippet', true);
    header('Cache-Control: no-store', true);
}

$emailCol    = pick_first_existing_col($pdo, 'users', ['email']);
$activeCol   = pick_first_existing_col($pdo, 'users', ['is_active', 'active', 'verified', 'email_verified', 'is_verified']);
$fullNameCol = pick_first_existing_col($pdo, 'users', ['full_name', 'name']);
$usernameCol = pick_first_existing_col($pdo, 'users', ['username']);
$resetTableOk = db_table_exists_local($pdo, 'password_reset_tokens');

$schemaError = null;
if (!$emailCol) {
    $schemaError = 'Table users must contain email column.';
} elseif (!$resetTableOk) {
    $schemaError = 'Table password_reset_tokens not found. Run the SQL migration first.';
}

if (is_post()) {
    $clientIp = (string)($_SERVER['REMOTE_ADDR'] ?? '');
    $emailForScope = '';
    try { $emailForScope = mb_strtolower(trim((string)($_POST['email'] ?? '')), 'UTF-8'); } catch (Throwable $e) { $emailForScope = ''; }

    try {
        if (function_exists('security_ip_is_blocked')) {
            $chk = security_ip_is_blocked($pdo, $clientIp, 'login');
            if (!empty($chk['blocked'])) {
                try { security_log_login_attempt($pdo, false, 'password_reset_ip_blocked', null, $emailForScope); } catch (Throwable $e) {}

                $reason = trim(strip_tags((string)($chk['reason'] ?? '')));
                if ($reason === '') $reason = 'Blocked';

                flash_set('err', tr_forgot('ip_blocked_msg', [
                    'uk' => 'Вас заблоковано: ' . $reason,
                    'ru' => 'Вы заблокированы: ' . $reason,
                    'en' => 'You are blocked: ' . $reason,
                ]));
                redirect_303('/forgot_password.php');
            }
        }
    } catch (Throwable $e) {}

    $rateSettings = null;
    $rateEmailScopes = [];
    try {
        $rateSettings = security_rate_get_settings($pdo);
        $mode = (string)($rateSettings['mode'] ?? 'both');
        if (!in_array($mode, ['ip', 'user', 'both'], true)) $mode = 'both';

        $rateEmailScopes = forgot_rate_build_email_scopes($mode, $clientIp, $emailForScope);

        $lockedInfo = security_rate_is_locked($pdo, $rateEmailScopes);
        if (!empty($lockedInfo['locked'])) {
            try { security_log_login_attempt($pdo, false, 'password_reset_rate_locked', null, $emailForScope); } catch (Throwable $e) {}
            flash_set('err', forgot_rate_lock_message((string)($lockedInfo['locked_until'] ?? '')));
            redirect_303('/forgot_password.php');
        }
    } catch (Throwable $e) {
        $rateSettings = null;
        $rateEmailScopes = [];
    }

    if (!csrf_check(post_str('csrf'))) {
        try { security_log_login_attempt($pdo, false, 'password_reset_csrf', null, $emailForScope); } catch (Throwable $e) {}

        try {
            if (is_array($rateSettings) && $rateEmailScopes) {
                security_rate_register_failure($pdo, $rateSettings, $rateEmailScopes);
            }
        } catch (Throwable $e) {}

        flash_set('err', t('bad_csrf'));
        redirect_303('/forgot_password.php');
    }

    if ($schemaError !== null) {
        flash_set('err', $schemaError);
        redirect_303('/forgot_password.php');
    }

    $email = mb_strtolower(trim(post_str('email')));
    if ($email === '' || !filter_var($email, FILTER_VALIDATE_EMAIL)) {
        try { security_log_login_attempt($pdo, false, 'password_reset_bad_email', null, $email); } catch (Throwable $e) {}
        try {
            if (is_array($rateSettings) && $rateEmailScopes) {
                security_rate_register_failure($pdo, $rateSettings, $rateEmailScopes);
            }
        } catch (Throwable $e) {}

        flash_set('err', tr_forgot('bad_email', [
            'uk' => 'Невірний email.',
            'ru' => 'Неверный email.',
            'en' => 'Invalid email.',
        ]));
        redirect_303('/forgot_password.php');
    }

    $selCols = ['id', "`$emailCol` AS email_val"];
    if ($activeCol)   $selCols[] = "`$activeCol` AS active_val";
    if ($fullNameCol) $selCols[] = "`$fullNameCol` AS full_name_val";
    if ($usernameCol) $selCols[] = "`$usernameCol` AS username_val";

    $st = $pdo->prepare('SELECT ' . implode(',', $selCols) . " FROM users WHERE `$emailCol`=? LIMIT 1");
    $st->execute([$email]);
    $u = $st->fetch(PDO::FETCH_ASSOC) ?: null;

    $rateUserScopes = [];
    try {
        if (is_array($rateSettings) && $u) {
            $mode = (string)($rateSettings['mode'] ?? 'both');
            if (!in_array($mode, ['ip', 'user', 'both'], true)) $mode = 'both';

            $uid = (int)($u['id'] ?? 0);
            if ($uid > 0 && function_exists('security_rate_scopes')) {
                $rateUserScopes = security_rate_scopes($mode, $clientIp, $uid);
                $lockedInfo2 = security_rate_is_locked($pdo, $rateUserScopes);
                if (!empty($lockedInfo2['locked'])) {
                    try { security_log_login_attempt($pdo, false, 'password_reset_rate_locked_uid', $uid, $email); } catch (Throwable $e) {}
                    flash_set('err', forgot_rate_lock_message((string)($lockedInfo2['locked_until'] ?? '')));
                    redirect_303('/forgot_password.php');
                }
            }
        }
    } catch (Throwable $e) {
        $rateUserScopes = [];
    }

    try {
        $pdo->exec("DELETE FROM password_reset_tokens WHERE (used_at IS NOT NULL AND used_at < (NOW() - INTERVAL 7 DAY)) OR expires_at < (NOW() - INTERVAL 7 DAY)");
    } catch (Throwable $e) {}

    $genericOk = tr_forgot('forgot_password_sent_generic', [
        'uk' => 'Якщо акаунт з таким email існує й активований, ми надіслали посилання для відновлення пароля.',
        'ru' => 'Если аккаунт с таким email существует и активирован, мы отправили ссылку для восстановления пароля.',
        'en' => 'If an account with this email exists and is active, we sent a password reset link.',
    ]);

    $didSend = false;
    $uid = $u ? (int)($u['id'] ?? 0) : 0;
    $isActive = $u ? ((int)($u['active_val'] ?? 1) === 1) : false;

    if ($u && $uid > 0 && $isActive) {
        try {
            $pdo->prepare("UPDATE password_reset_tokens SET used_at = NOW() WHERE user_id = ? AND used_at IS NULL")
                ->execute([$uid]);

            $rawToken  = bin2hex(random_bytes(32));
            $tokenHash = hash('sha256', $rawToken);
            $ua = trim((string)($_SERVER['HTTP_USER_AGENT'] ?? ''));
            if (function_exists('mb_substr')) {
                $ua = mb_substr($ua, 0, 255, 'UTF-8');
            } else {
                $ua = substr($ua, 0, 255);
            }

            $ins = $pdo->prepare("INSERT INTO password_reset_tokens (user_id, email, token_hash, expires_at, requested_ip, requested_ua, created_at) VALUES (?, ?, ?, DATE_ADD(NOW(), INTERVAL 60 MINUTE), ?, ?, NOW())");
            $ins->execute([$uid, $email, $tokenHash, ($clientIp !== '' ? $clientIp : null), ($ua !== '' ? $ua : null)]);

            $displayName = trim((string)($u['full_name_val'] ?? ''));
            if ($displayName === '') $displayName = trim((string)($u['username_val'] ?? ''));
            if ($displayName === '') $displayName = $email;

            $resetLink = abs_url_local('/reset_password.php?token=' . urlencode($rawToken));
            $subj = tr_forgot('reset_password_subject', [
                'uk' => 'Відновлення пароля — VMEREGI',
                'ru' => 'Восстановление пароля — VMEREGI',
                'en' => 'Password reset — VMEREGI',
            ]);

            $html = '<div style="font-family:Arial,sans-serif;line-height:1.5;color:#111">'
                  . '<h2 style="margin:0 0 12px 0;">' . h(tr_forgot('reset_password_mail_title', [
                        'uk' => 'Відновлення пароля',
                        'ru' => 'Восстановление пароля',
                        'en' => 'Password reset',
                    ])) . '</h2>'
                  . '<p style="margin:0 0 14px 0;">' . h(tr_forgot('reset_password_mail_text', [
                        'uk' => 'Натисни кнопку нижче, щоб задати новий пароль. Посилання діє 60 хвилин.',
                        'ru' => 'Нажми кнопку ниже, чтобы задать новый пароль. Ссылка действует 60 минут.',
                        'en' => 'Use the button below to set a new password. The link is valid for 60 minutes.',
                    ])) . '</p>'
                  . '<p style="margin:18px 0;">'
                  . '  <a href="' . h($resetLink) . '" style="display:inline-block;padding:12px 18px;border-radius:10px;background:#1f3a5f;color:#fff;text-decoration:none;">'
                  .        h(tr_forgot('reset_password_button', [
                                'uk' => 'Задати новий пароль',
                                'ru' => 'Задать новый пароль',
                                'en' => 'Set new password',
                            ]))
                  . '  </a>'
                  . '</p>'
                  . '<p style="margin:18px 0 6px 0;color:#555;font-size:13px;">' . h(tr_forgot('reset_password_link_fallback', [
                        'uk' => 'Якщо кнопка не працює, відкрий це посилання:',
                        'ru' => 'Если кнопка не работает, открой эту ссылку:',
                        'en' => 'If the button does not work, open this link:',
                    ])) . '</p>'
                  . '<div style="font-size:13px;"><a href="' . h($resetLink) . '">' . h($resetLink) . '</a></div>'
                  . '</div>';

            $text = tr_forgot('reset_password_mail_title', [
                        'uk' => 'Відновлення пароля',
                        'ru' => 'Восстановление пароля',
                        'en' => 'Password reset',
                    ]) . "\n\n"
                  . tr_forgot('reset_password_mail_text', [
                        'uk' => 'Натисни або відкрий посилання нижче, щоб задати новий пароль. Посилання діє 60 хвилин.',
                        'ru' => 'Нажми или открой ссылку ниже, чтобы задать новый пароль. Ссылка действует 60 минут.',
                        'en' => 'Open the link below to set a new password. The link is valid for 60 minutes.',
                    ]) . "\n"
                  . $resetLink . "\n";

            $send = ['ok' => false, 'error' => 'mailer_send not found'];
            if (function_exists('mailer_send')) {
                $send = (array)mailer_send($email, $displayName, $subj, $html, $text);
            }

            $didSend = !empty($send['ok']);
            if ($didSend) {
                try { security_log_login_attempt($pdo, true, 'password_reset_request_ok', $uid, $email); } catch (Throwable $e) {}
            } else {
                try { security_log_login_attempt($pdo, false, 'password_reset_mail_fail', $uid, $email); } catch (Throwable $e) {}
            }
        } catch (Throwable $e) {
            try { security_log_login_attempt($pdo, false, 'password_reset_request_fail', $uid > 0 ? $uid : null, $email); } catch (Throwable $e2) {}
        }
    } elseif ($u && $uid > 0 && !$isActive) {
        try { security_log_login_attempt($pdo, false, 'password_reset_inactive', $uid, $email); } catch (Throwable $e) {}
    } else {
        try { security_log_login_attempt($pdo, false, 'password_reset_unknown_email', null, $email); } catch (Throwable $e) {}
    }

    try {
        if (is_array($rateSettings)) {
            if ($rateEmailScopes) security_rate_register_failure($pdo, $rateSettings, $rateEmailScopes);
            if ($rateUserScopes)  security_rate_register_failure($pdo, $rateSettings, $rateUserScopes);
        }
    } catch (Throwable $e) {}

    flash_set('ok', $genericOk);
    redirect_303('/forgot_password.php');
}

require __DIR__ . '/includes/header.php';
?>

<div class="row justify-content-center">
  <div class="col-md-6 col-lg-5">
    <div class="card card-app p-4">
      <h1 class="h4 mb-3"><?= h($title) ?></h1>

      <?php if ($schemaError !== null): ?>
        <div class="alert alert-danger mb-3"><?= h($schemaError) ?></div>
      <?php else: ?>
        <p class="text-muted small mb-3">
          <?= h(tr_forgot('forgot_password_intro', [
              'uk' => 'Введи email, і ми надішлемо посилання для відновлення пароля.',
              'ru' => 'Введи email, и мы отправим ссылку для восстановления пароля.',
              'en' => 'Enter your email and we will send a password reset link.',
          ])) ?>
        </p>
      <?php endif; ?>

      <form method="post" class="vstack gap-3" action="/forgot_password.php">
        <?= csrf_field() ?>

        <div>
          <label class="form-label"><?= h(t('email')) ?></label>
          <input class="form-control" type="email" name="email" required autocomplete="email">
        </div>

        <button class="btn btn-primary w-100" type="submit"<?= $schemaError !== null ? ' disabled' : '' ?>>
          <?= h(tr_forgot('forgot_password_submit', [
              'uk' => 'Надіслати посилання',
              'ru' => 'Отправить ссылку',
              'en' => 'Send link',
          ])) ?>
        </button>

        <div class="text-muted small">
          <a href="/login.php"><?= h(t('login')) ?></a>
        </div>
      </form>
    </div>
  </div>
</div>

<?php require __DIR__ . '/includes/footer.php'; ?>
