<?php

require_once __DIR__ . '/../lib/settings.php';
require_once __DIR__ . '/../lib/login_rate_limit.php';
require_once __DIR__ . '/../lib/keycloak_auth.php';

switch ($route) {

case 'auth/login':
    if ($method !== 'POST') {
        json_error('METHOD_NOT_ALLOWED', 'Method not allowed', 405);
    }

    $body     = read_json_body();
    $username = trim($body['username'] ?? '');
    $password = (string)($body['password'] ?? '');

    if ($username === '' || $password === '') {
        json_error('MISSING_FIELDS', 'Username and password are required', 400);
    }

    $securitySettings = qdb_settings_get();
    [$allowed, $retryAfter] = qdb_login_rate_limit_check($username, $securitySettings);
    if (!$allowed) {
        qdb_login_rate_limit_deny($retryAfter);
    }

    try {
        [$pdo, $tmpDb, $lockFp] = qdb_open_read_or_fail();
        $stmt = $pdo->prepare(
            "SELECT userID, passwordHash, role, entityID, mustChangePassword
             FROM users WHERE username = :u"
        );
        $stmt->execute([':u' => $username]);
        $user = $stmt->fetch(PDO::FETCH_ASSOC);

        if (!$user || !password_verify($password, $user['passwordHash'])) {
            qdb_discard($tmpDb, $lockFp);
            $retryAfter = qdb_login_rate_limit_record_failure($username, $securitySettings);
            if ($retryAfter > 0) {
                qdb_login_rate_limit_deny($retryAfter);
            }
            json_error('INVALID_CREDENTIALS', 'Invalid username or password', 401);
        }

        qdb_login_rate_limit_clear($username);

        $assignedClients = [];
        if (($user['role'] ?? '') === 'coach' && ($user['entityID'] ?? '') !== '') {
            $cStmt = $pdo->prepare(
                "SELECT clientCode FROM client
                 WHERE coachID = :cid AND COALESCE(archived, 0) = 0
                 ORDER BY clientCode"
            );
            $cStmt->execute([':cid' => $user['entityID']]);
            $assignedClients = array_map(
                fn($code) => ['clientCode' => $code],
                $cStmt->fetchAll(PDO::FETCH_COLUMN)
            );
        }

        qdb_discard($tmpDb, $lockFp);

        qdb_reject_coach_web_login((string)($user['role'] ?? ''));

        if ((int)$user['mustChangePassword'] === 1) {
            $tempToken = bin2hex(random_bytes(32));
            token_add($tempToken, qdb_session_ttl_seconds($securitySettings, true), [
                'role'     => $user['role'],
                'entityID' => $user['entityID'],
                'userID'   => $user['userID'],
                'temp'     => true,
            ]);
            json_success([
                'mustChangePassword' => true,
                'token'              => $tempToken,
                'user'               => $username,
                'role'               => $user['role'],
            ]);
        }

        $token = bin2hex(random_bytes(32));
        token_add($token, qdb_session_ttl_seconds($securitySettings, false), [
            'role'     => $user['role'],
            'entityID' => $user['entityID'],
            'userID'   => $user['userID'],
        ]);
        $loginData = [
            'token' => $token,
            'user'  => $username,
            'role'  => $user['role'],
        ];
        if ($assignedClients !== []) {
            $loginData['clientsPayload'] = qdb_sensitive_envelope(
                json_encode(['clients' => $assignedClients], JSON_UNESCAPED_UNICODE),
                $token
            );
        }
        json_success($loginData);
    } catch (Throwable $e) {
        qdb_handler_fail($e, 'auth', null, $tmpDb ?? null, $lockFp ?? null);
    }
    break;

case 'auth/change-password':
    if ($method !== 'POST') {
        json_error('METHOD_NOT_ALLOWED', 'Method not allowed', 405);
    }

    $bearerToken = get_bearer_token();
    if (!$bearerToken) {
        json_error('UNAUTHORIZED', 'Bearer token required', 401);
    }
    $tokenRec = token_get_record($bearerToken);
    if (!$tokenRec) {
        json_error('FORBIDDEN', 'Invalid or expired token', 403);
    }

    $body        = read_json_body();
    $username    = trim($body['username'] ?? '');
    $oldPassword = (string)($body['old_password'] ?? '');
    $newPassword = (string)($body['new_password'] ?? '');

    if ($username === '' || $oldPassword === '' || $newPassword === '') {
        json_error('MISSING_FIELDS', 'username, old_password, and new_password are required', 400);
    }
    if (strlen($newPassword) < 6) {
        json_error('PASSWORD_TOO_SHORT', 'New password must be at least 6 characters', 400);
    }

    if (($tokenRec['userID'] ?? '') === '') {
        json_error('FORBIDDEN', 'Token not associated with a user', 403);
    }

    try {
        [$pdo, $tmpDb, $lockFp] = qdb_open_write_or_fail();

        $stmt = $pdo->prepare(
            "SELECT userID, passwordHash, role, entityID FROM users WHERE username = :u"
        );
        $stmt->execute([':u' => $username]);
        $user = $stmt->fetch(PDO::FETCH_ASSOC);

        if (!$user) {
            qdb_discard($tmpDb, $lockFp);
            json_error('INVALID_CREDENTIALS', 'Invalid credentials', 401);
        }

        if ($user['userID'] !== $tokenRec['userID']) {
            qdb_discard($tmpDb, $lockFp);
            json_error('FORBIDDEN', 'Token does not match user', 403);
        }

        qdb_reject_coach_web_login((string)($user['role'] ?? ''));

        if (!password_verify($oldPassword, $user['passwordHash'])) {
            qdb_discard($tmpDb, $lockFp);
            json_error('INVALID_CREDENTIALS', 'Old password incorrect', 401);
        }

        $newHash = password_hash($newPassword, PASSWORD_DEFAULT);
        $pdo->prepare(
            "UPDATE users SET passwordHash = :h, mustChangePassword = 0 WHERE userID = :uid"
        )->execute([':h' => $newHash, ':uid' => $user['userID']]);

        token_revoke_all_for_user_on_pdo($pdo, $user['userID']);

        qdb_save($tmpDb, $lockFp);

        $securitySettings = qdb_settings_get();

        // Issue a fresh session for this browser only
        $newToken = bin2hex(random_bytes(32));
        token_add($newToken, qdb_session_ttl_seconds($securitySettings, false), [
            'role'     => $user['role'],
            'entityID' => $user['entityID'],
            'userID'   => $user['userID'],
        ]);

        json_success([
            'token' => $newToken,
            'user'  => $username,
            'role'  => $user['role'],
        ]);
    } catch (Throwable $e) {
        qdb_handler_fail($e, 'auth', null, $tmpDb ?? null, $lockFp ?? null);
    }
    break;

case 'auth/keycloak-config':
    if ($method !== 'GET') {
        json_error('METHOD_NOT_ALLOWED', 'Method not allowed', 405);
    }
    $kcConfig = qdb_keycloak_config();
    json_success([
        'enabled' => qdb_keycloak_is_configured($kcConfig),
        'displayName' => $kcConfig['display_name'],
        'loginUrl' => qdb_keycloak_is_configured($kcConfig) ? 'auth/keycloak-login' : '',
    ]);
    break;

case 'auth/keycloak-login':
    if ($method !== 'GET') {
        json_error('METHOD_NOT_ALLOWED', 'Method not allowed', 405);
    }
    $kcConfig = qdb_keycloak_config();
    if (!qdb_keycloak_is_configured($kcConfig)) {
        json_error('KEYCLOAK_NOT_CONFIGURED', 'University login is not configured', 503);
    }
    try {
        $discovery = qdb_keycloak_discovery($kcConfig);
        $nonce = bin2hex(random_bytes(16));
        $params = [
            'client_id' => $kcConfig['client_id'],
            'redirect_uri' => $kcConfig['redirect_uri'],
            'response_type' => 'code',
            'scope' => 'openid profile email',
            'state' => qdb_keycloak_create_state($nonce),
            'nonce' => $nonce,
        ];
        qdb_keycloak_redirect($discovery['authorization_endpoint'] . '?' . http_build_query($params));
    } catch (Throwable $e) {
        qdb_handler_fail($e, 'keycloak-login', null, null, null);
    }
    break;

case 'auth/keycloak-callback':
    if ($method !== 'GET') {
        json_error('METHOD_NOT_ALLOWED', 'Method not allowed', 405);
    }
    if (isset($_GET['error'])) {
        json_error('KEYCLOAK_DENIED', 'University login was cancelled or denied', 401);
    }
    $code = trim((string)($_GET['code'] ?? ''));
    $state = trim((string)($_GET['state'] ?? ''));
    if ($code === '' || $state === '') {
        json_error('MISSING_FIELDS', 'Keycloak callback requires code and state', 400);
    }
    $kcConfig = qdb_keycloak_config();
    if (!qdb_keycloak_is_configured($kcConfig)) {
        json_error('KEYCLOAK_NOT_CONFIGURED', 'University login is not configured', 503);
    }
    try {
        qdb_keycloak_verify_state($state);
        $discovery = qdb_keycloak_discovery($kcConfig);
        $tokenResponse = qdb_keycloak_exchange_code($kcConfig, $discovery, $code);
        $accessToken = (string)($tokenResponse['access_token'] ?? '');
        if ($accessToken === '') {
            json_error('KEYCLOAK_TOKEN_FAILED', 'University login did not return an access token', 401);
        }
        $claims = qdb_keycloak_userinfo($discovery, $accessToken);
        $username = qdb_keycloak_claim_username($kcConfig, $claims);

        [$pdo, $tmpDb, $lockFp] = qdb_open_read_or_fail();
        $stmt = $pdo->prepare(
            "SELECT userID, role, entityID, mustChangePassword
             FROM users WHERE username = :u"
        );
        $stmt->execute([':u' => $username]);
        $user = $stmt->fetch(PDO::FETCH_ASSOC);
        qdb_discard($tmpDb, $lockFp);

        if (!$user) {
            json_error('KEYCLOAK_USER_NOT_ALLOWED', 'University account is not registered for this application', 403);
        }
        qdb_reject_coach_web_login((string)($user['role'] ?? ''));
        if ((int)($user['mustChangePassword'] ?? 0) === 1) {
            json_error('PASSWORD_CHANGE_REQUIRED', 'Please sign in locally once to change your temporary password before using University login', 403);
        }

        $securitySettings = qdb_settings_get();
        $token = bin2hex(random_bytes(32));
        token_add($token, qdb_session_ttl_seconds($securitySettings, false), [
            'role' => $user['role'],
            'entityID' => $user['entityID'],
            'userID' => $user['userID'],
        ]);
        qdb_keycloak_finish_html($token, $username, (string)$user['role']);
    } catch (Throwable $e) {
        qdb_handler_fail($e, 'keycloak-callback', null, $tmpDb ?? null, $lockFp ?? null);
    }
    break;

default:
    json_error('NOT_FOUND', 'Unknown auth route', 404);
}
