<?php

use App\Controllers\PublicController;

/**
 * Admin console authentication.
 *
 *   POST /api/adminauth/login   { username, password }  -> sets admin session
 *   POST /api/adminauth/logout                          -> clears it
 *   GET  /api/adminauth/me                              -> { admin: bool, username }
 *
 * A dedicated single-admin tier, separate from community accounts and the
 * bearer token. Login sets $_SESSION['admin']; every privileged console
 * endpoint (built in later phases) will call requireAdmin() before doing
 * anything. Rate-limited hard, and every attempt is written to cja_admin_log.
 *
 * Extends PublicController for origin + throttle; the admin check is bespoke
 * (session flag), not the framework's token/api auth.
 */
class AdminAuth extends PublicController
{
    // requireAdmin() is inherited from PublicController (non-static).

    private function logAttempt(string $action, string $detail): void
    {
        try {
            \Db::insert('cja_admin_log', [
                'actor'  => $_SESSION['admin']['username'] ?? 'anon',
                'action' => $action,
                'detail' => mb_substr($detail, 0, 500),
                'ip'     => $_SERVER['REMOTE_ADDR'] ?? null,
            ]);
        } catch (\Throwable $e) {
            // logging must never block auth
        }
    }

    public function login(): void
    {
        // 8 attempts / 5 min / IP — brute-force resistant, human-friendly.
        $this->guardPublic('admin_login', 8, 300);

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

        $row = \Db::getRow('SELECT admin_id, username, password_hash FROM cja_admin WHERE username = ?', [$username]);

        // Constant-ish work whether or not the user exists (avoid enumeration).
        $hash = $row['password_hash'] ?? '$2y$10$invalidinvalidinvalidinvalidinvalidinvalidinvalidin';
        $ok = $row && password_verify($password, $hash);

        if (!$ok) {
            $this->logAttempt('login_fail', "username={$username}");
            $this->json(null, 401, ['code' => 'bad_credentials', 'message' => 'Incorrect username or password.']);
        }

        session_regenerate_id(true);
        $_SESSION['admin'] = ['ok' => true, 'username' => $row['username'], 'at' => time()];
        \Db::update('cja_admin', ['last_login_at' => date('Y-m-d H:i:s')], 'admin_id = ?', [$row['admin_id']]);
        $this->logAttempt('login', "username={$row['username']}");

        $this->json(['admin' => true, 'username' => $row['username']]);
    }

    /**
     * Secret-link login: GET /admin/<token> (nginx routes it here as
     * ?t=<token>). Validates against the stored token hash, sets the admin
     * session, and redirects to /admin. On failure, redirects with ?e=1.
     *
     * Note: the token appears in this request's path, so it can land in access
     * logs — rotate it if that matters. A password login remains available.
     */
    public function token(): void
    {
        $this->guardPublic('admin_token', 10, 300);
        $t = (string) ($_GET['t'] ?? '');

        // Single admin: fetch the row that has a token set and verify.
        $row = \Db::getRow('SELECT admin_id, username, token_hash FROM cja_admin WHERE token_hash IS NOT NULL LIMIT 1');
        $ok = $row && $t !== '' && password_verify($t, $row['token_hash']);

        if (!$ok) {
            $this->logAttempt('token_fail', '');
            header('Location: /admin?e=1', true, 302);
            exit;
        }

        session_regenerate_id(true);
        $_SESSION['admin'] = ['ok' => true, 'username' => $row['username'], 'at' => time()];
        \Db::update('cja_admin', ['last_login_at' => date('Y-m-d H:i:s')], 'admin_id = ?', [$row['admin_id']]);
        $this->logAttempt('login_token', "username={$row['username']}");

        header('Location: /admin', true, 302);
        exit;
    }

    public function logout(): void
    {
        $this->logAttempt('logout', '');
        unset($_SESSION['admin']);
        session_regenerate_id(true);
        $this->json(['admin' => false]);
    }

    /**
     * nginx auth_request target. Returns 200 (empty) if the current session is
     * an admin, 401 otherwise. No body — nginx only reads the status. This is
     * what gates every /devconsole and /_preview request at the edge.
     */
    public function check(): void
    {
        if (empty($_SESSION['admin']['ok'])) {
            http_response_code(401);
            exit;
        }
        http_response_code(204);
        exit;
    }

    public function me(): void
    {
        $admin = $_SESSION['admin']['ok'] ?? false;
        $this->json([
            'admin'    => (bool) $admin,
            'username' => $admin ? ($_SESSION['admin']['username'] ?? null) : null,
        ]);
    }
}
