<?php
declare(strict_types=1);

require_once __DIR__ . '/bootstrap.php';

if (!is_readable(__DIR__ . '/config.php')) {
    http_response_code(503);
    echo json_encode(['ok' => false, 'error' => 'setup_required']);
    exit;
}

require_once __DIR__ . '/config.php';
require_once __DIR__ . '/admin_2fa_lib.php';

if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
    http_response_code(405);
    echo json_encode(['ok' => false, 'error' => 'method']);
    exit;
}

mocker_require_csrf_post();

const SITE_DEFAULT_ADMIN_HASH = '$2y$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi';
const SITE_ADMIN_MAX_ATTEMPTS = 3;
const SITE_ADMIN_LOCK_SECONDS = 900;

if (SITE_ADMIN_PASSWORD_HASH === '') {
    http_response_code(503);
    echo json_encode(['ok' => false, 'error' => 'setup_required']);
    exit;
}
if (hash_equals(SITE_ADMIN_PASSWORD_HASH, SITE_DEFAULT_ADMIN_HASH)) {
    http_response_code(503);
    echo json_encode(['ok' => false, 'error' => 'setup_required']);
    exit;
}

$ip = trim((string) ($_SERVER['REMOTE_ADDR'] ?? 'unknown'));
$key = 'mocker_admin_attempts_' . hash('sha256', $ip);
$now = time();
$attempts = $_SESSION[$key] ?? ['count' => 0, 'first' => $now, 'locked_until' => 0];
if (!is_array($attempts)) {
    $attempts = ['count' => 0, 'first' => $now, 'locked_until' => 0];
}
$lockedUntil = (int) ($attempts['locked_until'] ?? 0);
if ($lockedUntil > $now) {
    http_response_code(429);
    echo json_encode(['ok' => false, 'error' => 'too_many_attempts', 'retryAfter' => $lockedUntil - $now], JSON_UNESCAPED_UNICODE);
    exit;
}
if ($lockedUntil > 0 && $lockedUntil <= $now) {
    $attempts = ['count' => 0, 'first' => $now, 'locked_until' => 0];
}

$input = json_decode((string) file_get_contents('php://input'), true) ?: [];
$username = trim((string) ($input['username'] ?? ''));
$password = (string) ($input['password'] ?? '');
$twofaCode = trim((string) ($input['twofaCode'] ?? ''));
$twofaChallenge = trim((string) ($input['twofaChallenge'] ?? ''));

$completeLogin = static function (array $payload, int $now, string $key): void {
    $_SESSION['mocker_admin'] = true;
    $_SESSION['mocker_super'] = ($payload['kind'] ?? 'master') !== 'staff';
    $_SESSION['mocker_perms'] = is_array($payload['perms'] ?? null) ? $payload['perms'] : [];
    $_SESSION['mocker_admin_kind'] = (string) ($payload['kind'] ?? 'master');
    $_SESSION['mocker_admin_id'] = (int) ($payload['adminId'] ?? 0);
    $_SESSION['mocker_admin_username'] = (string) ($payload['username'] ?? '');
    $_SESSION['mocker_admin_display'] = (string) ($payload['displayName'] ?? '');
    $_SESSION['mocker_login_at'] = $now;
    unset($_SESSION[$key], $_SESSION['mocker_admin_2fa_pending']);
    session_regenerate_id(true);
    echo json_encode(['ok' => true], JSON_UNESCAPED_UNICODE);
    exit;
};

if ($twofaChallenge !== '') {
    $pending = $_SESSION['mocker_admin_2fa_pending'] ?? null;
    if (!is_array($pending)) {
        http_response_code(400);
        echo json_encode(['ok' => false, 'error' => 'twofa_missing_challenge'], JSON_UNESCAPED_UNICODE);
        exit;
    }
    $expiresAt = (int) ($pending['expiresAt'] ?? 0);
    if ($expiresAt <= $now) {
        unset($_SESSION['mocker_admin_2fa_pending']);
        http_response_code(400);
        echo json_encode(['ok' => false, 'error' => 'twofa_expired'], JSON_UNESCAPED_UNICODE);
        exit;
    }
    $expectedChallenge = (string) ($pending['challenge'] ?? '');
    if ($expectedChallenge === '' || !hash_equals($expectedChallenge, $twofaChallenge)) {
        http_response_code(401);
        echo json_encode(['ok' => false, 'error' => 'twofa_invalid_challenge'], JSON_UNESCAPED_UNICODE);
        exit;
    }
    $attempts = (int) ($pending['attempts'] ?? 0);
    if ($attempts >= 5) {
        unset($_SESSION['mocker_admin_2fa_pending']);
        http_response_code(429);
        echo json_encode(['ok' => false, 'error' => 'too_many_attempts'], JSON_UNESCAPED_UNICODE);
        exit;
    }
    $pending['attempts'] = $attempts + 1;
    $_SESSION['mocker_admin_2fa_pending'] = $pending;
    $secret = (string) ($pending['secret'] ?? '');
    $ok = false;
    if ($twofaCode !== '' && $secret !== '') {
        $ok = mocker_admin_twofa_verify_code($secret, $twofaCode, 1);
    }
    if (!$ok) {
        $hashes = is_array($pending['backupHashes'] ?? null) ? $pending['backupHashes'] : [];
        [$used, $next] = mocker_admin_twofa_consume_backup_code($hashes, $twofaCode);
        if ($used) {
            $ok = true;
            $pending['backupHashes'] = $next;
            $_SESSION['mocker_admin_2fa_pending'] = $pending;
            $adminId = (int) ($pending['adminId'] ?? 0);
            try {
                require_once __DIR__ . '/db.php';
                $pdo = mocker_db();
                if ($adminId > 0) {
                    $up = $pdo->prepare('UPDATE admin_operators SET twofa_backup_codes_json = :j, updated_at = :u WHERE id = :id');
                    $up->execute([':j' => json_encode($next, JSON_UNESCAPED_UNICODE), ':u' => gmdate('c'), ':id' => $adminId]);
                } else {
                    $up = $pdo->prepare('UPDATE admin_master_2fa SET twofa_backup_codes_json = :j, updated_at = :u WHERE id = 1');
                    $up->execute([':j' => json_encode($next, JSON_UNESCAPED_UNICODE), ':u' => gmdate('c')]);
                }
            } catch (Throwable $e) {
            }
        }
    }
    if (!$ok) {
        http_response_code(401);
        echo json_encode(['ok' => false, 'error' => 'twofa_invalid', 'remaining' => max(0, 5 - (int) $pending['attempts'])], JSON_UNESCAPED_UNICODE);
        exit;
    }
    $payload = is_array($pending['payload'] ?? null) ? $pending['payload'] : [];
    $completeLogin($payload, $now, $key);
}

$fail = static function () use ($key, &$attempts, $now): void {
    $first = (int) ($attempts['first'] ?? $now);
    if (($now - $first) > SITE_ADMIN_LOCK_SECONDS) {
        $attempts = ['count' => 1, 'first' => $now, 'locked_until' => 0];
    } else {
        $count = (int) ($attempts['count'] ?? 0) + 1;
        $attempts['count'] = $count;
        $attempts['first'] = $first;
        if ($count >= SITE_ADMIN_MAX_ATTEMPTS) {
            $attempts['locked_until'] = $now + SITE_ADMIN_LOCK_SECONDS;
        }
    }
    $_SESSION[$key] = $attempts;
    http_response_code(401);
    echo json_encode(
        [
            'ok' => false,
            'error' => 'invalid_password',
            'remaining' => max(0, SITE_ADMIN_MAX_ATTEMPTS - (int) ($attempts['count'] ?? 0)),
            'locked' => ((int) ($attempts['locked_until'] ?? 0)) > $now,
        ],
        JSON_UNESCAPED_UNICODE
    );
    exit;
};

if ($username !== '') {
    if ($password === '') {
        $fail();
    }
    require_once __DIR__ . '/admin_auth_lib.php';
    require_once __DIR__ . '/db.php';
    try {
        $pdo = mocker_db();
    } catch (Throwable $e) {
        http_response_code(503);
        echo json_encode(['ok' => false, 'error' => 'db'], JSON_UNESCAPED_UNICODE);
        exit;
    }
    $st = $pdo->prepare(
        'SELECT id, username, password_hash, display_name, is_super, permissions_json, twofa_enabled, twofa_secret, twofa_backup_codes_json
         FROM admin_operators WHERE username = :u COLLATE NOCASE LIMIT 1'
    );
    $st->execute([':u' => $username]);
    $row = $st->fetch(PDO::FETCH_ASSOC);
    if (!$row || !password_verify($password, (string) $row['password_hash'])) {
        $fail();
    }
    $keys = mocker_admin_permission_keys();
    $raw = json_decode((string) $row['permissions_json'], true);
    $perms = [];
    if (is_array($raw)) {
        foreach ($raw as $p) {
            $p = (string) $p;
            if (in_array($p, $keys, true)) {
                $perms[] = $p;
            }
        }
        $perms = array_values(array_unique($perms));
    }
    $isSuper = (int) ($row['is_super'] ?? 0) === 1;
    if ($isSuper) {
        $perms = $keys;
    } elseif ($perms === []) {
        $fail();
    }
    $payload = [
        'kind' => 'staff',
        'perms' => $perms,
        'adminId' => (int) $row['id'],
        'username' => (string) $row['username'],
        'displayName' => trim((string) ($row['display_name'] ?? '')),
    ];
    $twofaEnabled = (int) ($row['twofa_enabled'] ?? 0) === 1;
    $twofaSecret = trim((string) ($row['twofa_secret'] ?? ''));
    if ($twofaEnabled && $twofaSecret !== '') {
        $challenge = rtrim(strtr(base64_encode(random_bytes(24)), '+/', '-_'), '=');
        $backupHashes = json_decode((string) ($row['twofa_backup_codes_json'] ?? '[]'), true);
        $_SESSION['mocker_admin_2fa_pending'] = [
            'challenge' => $challenge,
            'expiresAt' => $now + 300,
            'attempts' => 0,
            'secret' => $twofaSecret,
            'backupHashes' => is_array($backupHashes) ? $backupHashes : [],
            'adminId' => (int) $row['id'],
            'payload' => $payload,
        ];
        echo json_encode(['ok' => true, 'requires2fa' => true, 'twofaChallenge' => $challenge], JSON_UNESCAPED_UNICODE);
        exit;
    }
    $completeLogin($payload, $now, $key);
}

if ($password === '' || !password_verify($password, SITE_ADMIN_PASSWORD_HASH)) {
    $fail();
}

$master2fa = null;
try {
    require_once __DIR__ . '/db.php';
    $pdo = mocker_db();
    $master2fa = $pdo->query('SELECT twofa_enabled, twofa_secret, twofa_backup_codes_json FROM admin_master_2fa WHERE id = 1 LIMIT 1')->fetch(PDO::FETCH_ASSOC);
} catch (Throwable $e) {
    $master2fa = null;
}
$master2faEnabled = is_array($master2fa) && (int) ($master2fa['twofa_enabled'] ?? 0) === 1;
$masterSecret = is_array($master2fa) ? trim((string) ($master2fa['twofa_secret'] ?? '')) : '';
if ($master2faEnabled && $masterSecret !== '') {
    $challenge = rtrim(strtr(base64_encode(random_bytes(24)), '+/', '-_'), '=');
    $backupHashes = json_decode((string) ($master2fa['twofa_backup_codes_json'] ?? '[]'), true);
    $_SESSION['mocker_admin_2fa_pending'] = [
        'challenge' => $challenge,
        'expiresAt' => $now + 300,
        'attempts' => 0,
        'secret' => $masterSecret,
        'backupHashes' => is_array($backupHashes) ? $backupHashes : [],
        'adminId' => 0,
        'payload' => [
            'kind' => 'master',
            'perms' => [],
            'adminId' => 0,
            'username' => '',
            'displayName' => '',
        ],
    ];
    echo json_encode(['ok' => true, 'requires2fa' => true, 'twofaChallenge' => $challenge], JSON_UNESCAPED_UNICODE);
    exit;
}
$completeLogin([
    'kind' => 'master',
    'perms' => [],
    'adminId' => 0,
    'username' => '',
    'displayName' => '',
], $now, $key);
