<?php
declare(strict_types=1);

/**
 * /login.php
 * Логін:
 * - noindex
 * - IP block (з показом причини)
 * - rate-limit (email scopes + uid scopes)
 * - CSRF
 * - підтримка різних схем users: email/pass_hash/active
 * - ✅ підтримка ?to=... (повернення назад після логіну)
 *
 * ОНОВЛЕНО:
 * - safe redirect по to= (тільки локальний шлях)
 * - to= прокидуємо через форму (hidden input)
 * - 303 redirect для POST (PRG)
 */

require_once __DIR__ . '/includes/start.php';
require_once __DIR__ . '/includes/security.php';
require_once __DIR__ . '/includes/security_ip_rules.php'; // ✅ IP/CIDR rules
require_once __DIR__ . '/includes/security_rate_limit.php'; // ✅ rate-limit (app_settings + security_rate_limits)

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

$title = t('login');

/* SEO: login page should not be indexed */
$meta_robots = 'noindex, nofollow, noarchive, nosnippet';
$meta_desc   = '';
$canonical   = function_exists('abs_url') ? abs_url('/login.php') : '/login.php';
if (!headers_sent()) {
    header('X-Robots-Tag: noindex, nofollow, noarchive, nosnippet', true);
}

/* small i18n fallback */
if (!function_exists('tr_login')) {
    function tr_login(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);
    }
}

/* ===== helpers ===== */
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('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('safe_to_local')) {
    function safe_to_local(string $raw, string $fallback = '/'): string
    {
        $raw = trim($raw);
        if ($raw === '') return $fallback;

        // 1 раз декодимо (to у нас часто urlencode)
        $raw = rawurldecode($raw);

        // якщо абсолютний URL — беремо тільки path+query
        if (preg_match('~^https?://~i', $raw)) {
            $p = parse_url($raw);
            $path = (string)($p['path'] ?? '');
            $q = isset($p['query']) && (string)$p['query'] !== '' ? ('?' . (string)$p['query']) : '';
            $raw = $path . $q;
        }

        // якщо є safe_local_path — використовуємо
        if (function_exists('safe_local_path')) {
            try {
                $raw = (string)safe_local_path($raw, $fallback);
            } catch (Throwable $e) {
                $raw = $fallback;
            }
        } else {
            if ($raw === '' || $raw[0] !== '/' || str_starts_with($raw, '//')) {
                $raw = $fallback;
            }
        }

        // не дозволяємо редіректити на логін/реєстрацію/логаут/системні
        $p2 = (string)parse_url($raw, PHP_URL_PATH);
        if ($p2 === '') $p2 = '/';
        $blocked = ['/login.php','/register.php','/logout.php','/set_lang.php'];
        if (in_array($p2, $blocked, true)) return $fallback;

        return $raw;
    }
}
if (!function_exists('login_self_url')) {
    function login_self_url(string $to): string
    {
        $to = trim($to);
        if ($to === '' || $to === '/') return '/login.php';
        return '/login.php?to=' . urlencode($to);
    }
}

/* ===== resolve ?to= for redirect after login ===== */
$to = safe_to_local((string)($_GET['to'] ?? ($_POST['to'] ?? '')), '/');
$selfUrl = login_self_url($to);

/* detect columns */
$emailCol  = pick_first_existing_col($pdo, 'users', ['email']);
$passCol   = pick_first_existing_col($pdo, 'users', ['pass_hash','password_hash']);
$activeCol = pick_first_existing_col($pdo, 'users', ['is_active','active','verified','email_verified','is_verified']);

if (!$emailCol || !$passCol) {
    http_response_code(500);
    require __DIR__ . '/includes/header.php';
    ?>
    <div class="container my-4">
      <div class="alert alert-danger">
        <div class="fw-bold mb-1">DB schema missing</div>
        <div>Table <code>users</code> must contain <code>email</code> and <code>pass_hash</code> (or <code>password_hash</code>).</div>
      </div>
    </div>
    <?php
    require __DIR__ . '/includes/footer.php';
    exit;
}

/* ===== rate-limit helpers (login scopes) ===== */
function login_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 { // both
        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 login_rate_lock_message(?string $lockedUntil): string {
    $lockedUntil = trim((string)$lockedUntil);

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

/* Якщо вже залогінений — одразу назад */
if (function_exists('current_user') && current_user()) {
    redirect_303($to);
}

if (is_post()) {

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

    /* ✅ BLOCKED IP CHECK (login) — show reason to user */
    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, 'ip_blocked', null, $emailForScope); } catch (Throwable $e) {}

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

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

    /* ✅ RATE LIMIT: check locked (before CSRF/password) */
    $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 = login_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, 'rate_locked_login', null, $emailForScope); } catch (Throwable $e) {}
            flash_set('err', login_rate_lock_message((string)($lockedInfo['locked_until'] ?? '')));
            redirect_303($selfUrl);
        }
    } catch (Throwable $e) {
        $rateSettings = null;
        $rateEmailScopes = [];
    }

    if (!csrf_check(post_str('csrf'))) {
        try { security_log_login_attempt($pdo, false, '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($selfUrl);
    }

    $email = mb_strtolower(post_str('email'));
    $pass  = post_str('password');

    // беремо потрібні поля
    $selCols = ["id", "`$emailCol` AS email_val", "`$passCol` AS pass_val"];
    if ($activeCol) $selCols[] = "`$activeCol` AS active_val";

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

    // якщо користувач знайдений — будуємо “акаунтні” scopes по user_id
    $rateUserScopes = [];
    try {
        if (is_array($rateSettings)) {
            $mode = (string)($rateSettings['mode'] ?? 'both');
            if (!in_array($mode, ['ip','user','both'], true)) $mode = 'both';

            $uid = ($u ? (int)($u['id'] ?? 0) : 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, 'rate_locked_login_uid', $uid, $email); } catch (Throwable $e) {}
                    flash_set('err', login_rate_lock_message((string)($lockedInfo2['locked_until'] ?? '')));
                    redirect_303($selfUrl);
                }
            }
        }
    } catch (Throwable $e) {
        $rateUserScopes = [];
    }

    $passHash = $u ? (string)($u['pass_val'] ?? '') : '';
    if (!$u || $passHash === '' || !password_verify($pass, $passHash)) {
        try {
            $uid = $u ? (int)($u['id'] ?? 0) : 0;
            security_log_login_attempt($pdo, false, 'bad_credentials', $uid > 0 ? $uid : 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('err', t('bad_login'));
        redirect_303($selfUrl);
    }

    if ($activeCol) {
        if ((int)($u['active_val'] ?? 0) !== 1) {
            try { security_log_login_attempt($pdo, false, 'inactive', (int)$u['id'], (string)($u['email_val'] ?? $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('err', t('activation_need'));
            redirect_303($selfUrl);
        }
    }

    login_user((int)$u['id']);

    // ✅ SUCCESS: clear limiter (email + uid scopes)
    try {
        if (is_array($rateSettings)) {
            if ($rateEmailScopes) security_rate_clear($pdo, $rateEmailScopes);
            if ($rateUserScopes)  security_rate_clear($pdo, $rateUserScopes);
        }
    } catch (Throwable $e) {}

    // log: success
    try {
        security_log_login_attempt($pdo, true, 'ok', (int)$u['id'], (string)($u['email_val'] ?? $email));
    } catch (Throwable $e) {}

    flash_set('ok', t('login') . ' OK');
    redirect_303($to);
}

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(t('login')) ?></h1>

      <form method="post" class="vstack gap-3" action="<?= h($selfUrl) ?>">
        <?= csrf_field() ?>
        <input type="hidden" name="to" value="<?= h($to) ?>">

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

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

        <button class="btn btn-primary w-100" type="submit"><?= h(t('login')) ?></button>

        <div class="text-center small">
          <a href="/forgot_password.php"><?= h(tr_login('forgot_password_link', [
              'uk' => 'Забули пароль?',
              'ru' => 'Забыли пароль?',
              'en' => 'Forgot password?',
          ])) ?></a>
        </div>

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

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