<?php
use App\Controllers\PublicController;

/**
 * User accounts — email signup (verified) + Google OAuth.
 *
 * Named `account` (not `auth`) to avoid colliding with the global `Auth` helper class:
 * the router does `new $this->_url[0](...)`, so the controller class name must be unique.
 *
 * Session: on login we set $_SESSION['login'] = { userid, email, name, avatar } and
 * regenerate the session id. Community members are plain logged-in users — they do NOT
 * use the sp_role_perm admin permission grid.
 *
 * Routes (all under /api/account):
 *   POST /register            { name, email, password }  -> creates pending user + emails verify link
 *   GET  /verify?token=..     email link -> flips email_verified, redirects to site
 *   POST /login               { email, password }        -> sets session, returns user
 *   POST /logout                                         -> clears session
 *   GET  /me                                             -> current user or null
 *   GET  /google[?code&state] -> leg 1 redirects to Google, leg 2 handles callback
 */
class Account extends PublicController
{
    // ── request/response helpers ─────────────────────────────────────────────

    private function input(): array
    {
        return json_decode((string) file_get_contents('php://input'), true) ?: [];
    }

    private function currentUser(): ?array
    {
        $u = $_SESSION['login'] ?? null;
        return (is_array($u) && !empty($u['userid'])) ? $u : null;
    }

    /** Shape a sp_users row into the safe object the frontend consumes. */
    private function publicUser(array $row): array
    {
        $clean = fn($v) => ($v === '0' || $v === null) ? '' : trim((string) $v);
        $name  = trim($clean($row['fname'] ?? '') . ' ' . $clean($row['lname'] ?? ''));
        if ($name === '') {
            $name = $clean($row['username'] ?? '') ?: (string) ($row['email'] ?? '');
        }
        $avatar = $clean($row['avatar'] ?? '');
        if ($avatar === '') {
            $avatar = \Functions::getAvatar((string) ($row['email'] ?? ''));
        }
        return [
            'userid' => (int) $row['userid'],
            'email'  => (string) ($row['email'] ?? ''),
            'name'   => $name,
            'avatar' => $avatar,
        ];
    }

    private function startSession(array $row): void
    {
        session_regenerate_id(true);
        $_SESSION['login'] = $this->publicUser($row);
    }

    private function splitName(string $name): array
    {
        $parts = preg_split('/\s+/', trim($name), 2) ?: [];
        return [$parts[0] ?? $name, $parts[1] ?? ''];
    }

    // ── actions ──────────────────────────────────────────────────────────────

    /** GET /api/account -> alias for /me */
    public function index()
    {
        $this->me();
    }

    /** POST /api/account/register */
    public function register()
    {
        $this->guardPublic('account.register', 10, 600); // 10 / 10 min / IP

        $b        = $this->input();
        $name     = trim((string) ($b['name'] ?? ''));
        $email    = strtolower(trim((string) ($b['email'] ?? '')));
        $password = (string) ($b['password'] ?? '');

        if ($name === '') {
            $this->json(null, 422, ['code' => 'name_required', 'message' => 'Please enter your name.']);
        }
        if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
            $this->json(null, 422, ['code' => 'invalid_email', 'message' => 'Enter a valid email address.']);
        }
        if (strlen($password) < 8) {
            $this->json(null, 422, ['code' => 'weak_password', 'message' => 'Password must be at least 8 characters.']);
        }
        if (\Functions::CheckUserExist($email)) {
            $this->json(null, 409, ['code' => 'email_taken', 'message' => 'An account with this email already exists.']);
        }

        [$fname, $lname] = $this->splitName($name);

        $res = json_decode(\Db::insert('sp_users', [
            'username'       => $email,
            'email'          => $email,
            'email_verified' => 0,
            'password'       => password_hash($password, PASSWORD_DEFAULT),
            'fname'          => $fname,
            'lname'          => $lname,
            'role_id'        => null, // community member — no admin role (FK -> sp_roles allows NULL)
            'active'         => 1,
        ]), true);
        if (($res['Code'] ?? 0) != 1) {
            $this->json(null, 500, ['code' => 'db_error', 'message' => 'Could not create the account.']);
        }
        $userid = (int) $res['ID'];

        $token = bin2hex(random_bytes(24)); // 48 chars
        \Db::execute(
            "INSERT INTO sp_user_tokens (userid, token, type, expires_at, created_at) VALUES (?, ?, 'verify', ?, NOW())",
            [$userid, $token, date('Y-m-d H:i:s', time() + 86400)]
        );

        $this->sendVerifyEmail($email, $name, $token);

        $this->json(['status' => 'pending_verification', 'email' => $email]);
    }

    /** GET /api/account/verify?token=... — clicked from the email; redirects back to the site. */
    public function verify()
    {
        $this->guardPublic('account.verify', 30, 600);

        $token = (string) ($_GET['token'] ?? '');
        $row   = $token !== '' ? \Db::getRow(
            "SELECT id, userid FROM sp_user_tokens
             WHERE token = ? AND type = 'verify' AND used_at IS NULL AND expires_at > NOW() LIMIT 1",
            [$token]
        ) : null;

        if (!$row) {
            $this->redirect('/?verified=0');
        }

        \Db::execute("UPDATE sp_users SET email_verified = 1 WHERE userid = ?", [$row['userid']]);
        \Db::execute("UPDATE sp_user_tokens SET used_at = NOW() WHERE id = ?", [$row['id']]);
        $this->redirect('/?verified=1');
    }

    /** POST /api/account/login */
    public function login()
    {
        $this->guardPublic('account.login', 20, 600);

        $b        = $this->input();
        $email    = strtolower(trim((string) ($b['email'] ?? '')));
        $password = (string) ($b['password'] ?? '');

        $row = \Db::getRow("SELECT * FROM sp_users WHERE email = ? LIMIT 1", [$email]);
        if (!$row || empty($row['password']) || !password_verify($password, $row['password'])) {
            $this->json(null, 401, ['code' => 'bad_credentials', 'message' => 'Incorrect email or password.']);
        }
        if ((int) ($row['active'] ?? 0) !== 1) {
            $this->json(null, 403, ['code' => 'account_disabled', 'message' => 'This account is disabled.']);
        }
        if ((int) ($row['email_verified'] ?? 0) !== 1) {
            $this->json(null, 403, ['code' => 'unverified', 'message' => 'Please verify your email before signing in.']);
        }

        $this->startSession($row);
        $this->json(['user' => $this->publicUser($row)]);
    }

    /** POST /api/account/logout */
    public function logout()
    {
        $this->guardPublic('account.logout', 60, 60);
        unset($_SESSION['login']);
        session_regenerate_id(true);
        $this->json(['status' => 'logged_out']);
    }

    /** GET /api/account/me */
    public function me()
    {
        $this->guardPublic('account.me', 600, 60);
        $this->json(['user' => $this->currentUser()]);
    }

    /** GET /api/account/google — leg 1 (redirect to consent) and leg 2 (callback), by ?code presence. */
    public function google()
    {
        $cfg = $this->googleConfig();
        if (!$cfg) {
            $this->json(null, 500, ['code' => 'oauth_unconfigured', 'message' => 'Google sign-in is not configured.']);
        }

        // ── Leg 1: no code → send the user to Google's consent screen ──
        if (($_GET['code'] ?? '') === '') {
            $state = bin2hex(random_bytes(16));
            $_SESSION['oauth_state'] = $state;
            $_SESSION['oauth_popup'] = (($_GET['mode'] ?? '') === 'popup'); // async popup vs full redirect
            $q = http_build_query([
                'client_id'     => $cfg['client_id'],
                'redirect_uri'  => $cfg['redirect_uri'],
                'response_type' => 'code',
                'scope'         => $cfg['scope'],
                'state'         => $state,
                'access_type'   => 'online',
                'prompt'        => 'select_account',
            ]);
            header('Location: ' . $cfg['auth_uri'] . '?' . $q);
            exit;
        }

        // ── Leg 2: callback — verify state (CSRF), exchange code, fetch profile ──
        $state = (string) ($_GET['state'] ?? '');
        if ($state === '' || !hash_equals((string) ($_SESSION['oauth_state'] ?? ''), $state)) {
            $this->finishOauth(false);
        }
        unset($_SESSION['oauth_state']);

        try {
            $http = new \GuzzleHttp\Client(['timeout' => 10, 'http_errors' => false]);

            $tok = json_decode((string) $http->post($cfg['token_uri'], ['form_params' => [
                'code'          => (string) $_GET['code'],
                'client_id'     => $cfg['client_id'],
                'client_secret' => $cfg['client_secret'],
                'redirect_uri'  => $cfg['redirect_uri'],
                'grant_type'    => 'authorization_code',
            ]])->getBody(), true) ?: [];

            $access = (string) ($tok['access_token'] ?? '');
            if ($access === '') {
                throw new \RuntimeException('token exchange failed');
            }

            $info = json_decode((string) $http->get($cfg['userinfo_uri'], [
                'headers' => ['Authorization' => 'Bearer ' . $access],
            ])->getBody(), true) ?: [];
        } catch (\Throwable $e) {
            error_log('[account] google oauth failed: ' . $e->getMessage());
            $this->finishOauth(false);
        }

        $sub   = (string) ($info['sub'] ?? '');
        $email = strtolower(trim((string) ($info['email'] ?? '')));
        if ($sub === '' || $email === '') {
            $this->finishOauth(false);
        }

        try {
            $row = $this->upsertGoogleUser($sub, $email, $info);
        } catch (\Throwable $e) {
            error_log('[account] google upsert failed: ' . $e->getMessage());
            $this->finishOauth(false);
        }
        $this->startSession($row);
        $this->finishOauth(true);
    }

    // ── internals ─────────────────────────────────────────────────────────────

    /** Read + decrypt the google_oauth row from sp_settings. */
    private function googleConfig(): ?array
    {
        $meta = json_decode((string) \Db::getValue(
            "SELECT metval FROM sp_settings WHERE keyval = 'google_oauth' AND status = 1 LIMIT 1"
        ), true);
        if (!$meta || empty($meta['client_id'])) {
            return null;
        }
        $meta['client_secret'] = \Functions::Decrypt((string) ($meta['client_secret'] ?? ''));
        return $meta;
    }

    /** Find-or-create a sp_users row for a Google identity, and link it in sp_oauth_accounts. */
    private function upsertGoogleUser(string $sub, string $email, array $info): array
    {
        // 1. Already linked → return that user.
        $link = \Db::getRow(
            "SELECT userid FROM sp_oauth_accounts WHERE provider = 'google' AND provider_uid = ? LIMIT 1",
            [$sub]
        );
        if ($link) {
            return \Db::getRow("SELECT * FROM sp_users WHERE userid = ? LIMIT 1", [$link['userid']]);
        }

        // 2. Existing account with this email → link Google to it (and mark verified).
        $user = \Db::getRow("SELECT * FROM sp_users WHERE email = ? LIMIT 1", [$email]);
        if ($user) {
            if ((int) ($user['email_verified'] ?? 0) !== 1) {
                \Db::execute("UPDATE sp_users SET email_verified = 1 WHERE userid = ?", [$user['userid']]);
            }
        } else {
            // 3. New user — Google already verified the email, so email_verified = 1.
            [$fname, $lname] = $this->splitName((string) ($info['name'] ?? $email));
            $res = json_decode(\Db::insert('sp_users', [
                'username'       => $email,
                'email'          => $email,
                'email_verified' => 1,
                'password'       => '',
                'fname'          => (string) ($info['given_name'] ?? $fname),
                'lname'          => (string) ($info['family_name'] ?? $lname),
                'avatar'         => (string) ($info['picture'] ?? ''),
                'role_id'        => null, // community member — no admin role (FK -> sp_roles allows NULL)
                'active'         => 1,
            ]), true);
            if (($res['Code'] ?? 0) != 1) {
                error_log('[account] google user create failed: ' . ($res['Message'] ?? '?'));
                $this->finishOauth(false);
            }
            $user = \Db::getRow("SELECT * FROM sp_users WHERE userid = ? LIMIT 1", [(int) $res['ID']]);
        }

        \Db::execute(
            "INSERT INTO sp_oauth_accounts (userid, provider, provider_uid, email, created_at) VALUES (?, 'google', ?, ?, NOW())",
            [$user['userid'], $sub, $email]
        );
        return $user;
    }

    /** Send the verification email; falls back to logging the link when email is unconfigured. */
    private function sendVerifyEmail(string $email, string $name, string $token): void
    {
        $link = URL . '/api/account/verify?token=' . urlencode($token);

        $tpl  = dirname(__DIR__, 2) . '/templates/emails/verify.html';
        $html = @file_get_contents($tpl) ?: '<p>Confirm your email: <a href="%VERIFY_URL%">%VERIFY_URL%</a></p>';
        $html = str_replace(
            ['%VERIFY_URL%', '%NAME%', '%SITE%'],
            [$link, htmlspecialchars($name !== '' ? $name : 'there', ENT_QUOTES), PROJECT_NAME],
            $html
        );

        $sent = \Mailer::send($email, $name, 'Confirm your ' . PROJECT_NAME . ' account', $html, "Confirm your email: {$link}");
        if (!$sent) {
            // No email provider configured (or send failed) → log the link so signup stays testable.
            error_log("[account] verify link for {$email}: {$link}");
        }
    }

    /** 302 to a path on the public site, then stop. */
    private function redirect(string $path): void
    {
        header('Location: ' . URL . $path);
        exit;
    }

    /**
     * Finish the OAuth callback. In popup mode (opened by the AuthModal) we return a tiny
     * HTML page that postMessages the result to the opener and closes the popup — so the
     * user stays on the page they started from, which just refreshes. Otherwise (fallback
     * when the popup was blocked) we 302-redirect to the homepage with a status flag.
     */
    private function finishOauth(bool $ok): void
    {
        $popup = !empty($_SESSION['oauth_popup']);
        unset($_SESSION['oauth_popup']);

        if ($popup) {
            $origin   = json_encode(URL);
            $fallback = json_encode(URL . '/?login=' . ($ok ? 'success' : 'error'));
            header('Content-Type: text/html; charset=UTF-8');
            echo '<!doctype html><meta charset="utf-8"><title>Signing in…</title>'
               . '<script>(function(){try{if(window.opener)window.opener.postMessage({type:"google-auth",ok:' . ($ok ? 'true' : 'false') . '},' . $origin . ');}catch(e){}'
               . 'window.close();setTimeout(function(){location.href=' . $fallback . ';},400);})();</script>'
               . '<p style="font:14px system-ui;margin:2rem">You can close this window.</p>';
            exit;
        }

        $this->redirect($ok ? '/?login=success' : '/?login=error');
    }
}
