<?php
/**
 * MSPE Authentication API
 */

require_once 'config.php';

$method = $_SERVER['REQUEST_METHOD'];
$body = getRequestBody();

switch ($method) {
    case 'POST':
        $action = $body['action'] ?? 'login';
        
        if ($action === 'login') {
            handleLogin($body);
        } elseif ($action === 'logout') {
            handleLogout();
        } elseif ($action === 'verify') {
            handleVerify();
        } elseif ($action === 'request_password_reset') {
            requestPasswordReset($body);
        } elseif ($action === 'reset_password') {
            resetPassword($body);
        } else {
            jsonResponse(['success' => false, 'message' => 'Invalid action'], 400);
        }
        break;
        
    default:
        jsonResponse(['success' => false, 'message' => 'Method not allowed'], 405);
}

function handleLogin($body) {
    global $db;
    
    $username = $body['username'] ?? '';
    $password = $body['password'] ?? '';
    $rememberMe = !empty($body['remember']);
    
    if (empty($username) || empty($password)) {
        jsonResponse(['success' => false, 'message' => 'Username and password required'], 400);
    }
    
    // ── Rate limiting: max 10 attempts per IP per 15 minutes (DB-backed) ──
    $ip = $_SERVER['REMOTE_ADDR'] ?? '0.0.0.0';
    $maxAttempts = 10;
    $windowSeconds = 900; // 15 minutes
    if (countRecentFailedLogins($ip, $windowSeconds) >= $maxAttempts) {
        jsonResponse(['success' => false, 'message' => 'Too many login attempts. Try again later.'], 429);
    }

    // ── Per-username rate limiting: max 5 attempts per username per 15 minutes ──
    $maxUsernameAttempts = 5;
    if (countRecentFailedLoginsByUsername($username, $windowSeconds) >= $maxUsernameAttempts) {
        jsonResponse(['success' => false, 'message' => 'This account is temporarily locked. Try again later.'], 429);
    }
    
    // Check override admin auth record first (if created via reset flow)
    $adminAuthRows = $db->getAll('admin_auth');
    $adminAuth = !empty($adminAuthRows) ? $adminAuthRows[0] : null;

    if ($adminAuth && isset($adminAuth['username']) && isset($adminAuth['password_hash'])) {
        if ($username === $adminAuth['username'] && password_verify($password, $adminAuth['password_hash'])) {
            loginSuccessResponse('admin', $adminAuth['username'], 'admin', $rememberMe);
        }
    } else {
        // Fallback to config credentials (ADMIN_PASS must be a password hash)
        if ($username === ADMIN_USER && verifyConfigAdminPassword($password)) {
            loginSuccessResponse('admin', $username, 'admin', $rememberMe);
        }
    }
    
    // Check against database users
    $users = $db->query('users', ['username' => $username]);
    
    if (!empty($users)) {
        $user = array_values($users)[0];
        
        if (password_verify($password, $user['password'])) {
            loginSuccessResponse($user['id'], $user['username'], $user['role'] ?? 'admin', $rememberMe);
        }
    }
    
    recordFailedLogin();
    auditLog('login_failure', ['username' => $username]);
    jsonResponse(['success' => false, 'message' => 'Invalid credentials'], 401);
}

function recordFailedLogin() {
    global $db;

    $ip = $_SERVER['REMOTE_ADDR'] ?? '0.0.0.0';
    $body = getRequestBody();
    $db->insert('login_attempts', [
        'ip_address' => $ip,
        'username' => sanitize($body['username'] ?? ''),
        'attempted_at' => date('Y-m-d H:i:s')
    ]);

    cleanupOldFailedLogins();
}

function countRecentFailedLogins($ip, $windowSeconds) {
    global $db;

    $cutoffDate = date('Y-m-d H:i:s', time() - (int)$windowSeconds);
    $now = date('Y-m-d H:i:s');

    return $db->countByDateRange('login_attempts', 'attempted_at', $cutoffDate, $now, [
        'ip_address' => $ip
    ]);
}

function countRecentFailedLoginsByUsername($username, $windowSeconds) {
    global $db;

    if (trim($username) === '') return 0;

    $cutoffDate = date('Y-m-d H:i:s', time() - (int)$windowSeconds);
    $now = date('Y-m-d H:i:s');

    return $db->countByDateRange('login_attempts', 'attempted_at', $cutoffDate, $now, [
        'username' => $username
    ]);
}

function cleanupOldFailedLogins() {
    global $db;

    $attempts = $db->getAll('login_attempts');
    $cutoff = time() - (24 * 60 * 60);

    foreach ($attempts as $attempt) {
        $attemptedAt = strtotime((string)($attempt['attempted_at'] ?? '1970-01-01 00:00:00'));
        if ($attemptedAt < $cutoff && !empty($attempt['id'])) {
            $db->delete('login_attempts', $attempt['id']);
        }
    }
}

function handleLogout() {
    clearAdminAuthCookie();
    jsonResponse(['success' => true, 'message' => 'Logged out successfully']);
}

function handleVerify() {
    $user = checkAuth();
    
    if ($user) {
        jsonResponse([
            'success' => true,
            'user' => [
                'id' => $user['user_id'],
                'username' => $user['username'],
                'role' => $user['role']
            ]
        ]);
    }
    
    jsonResponse(['success' => false, 'message' => 'Invalid token'], 401);
}

function loginSuccessResponse($id, $username, $role, $rememberMe = false) {
    $ttlSeconds = $rememberMe ? (30 * 24 * 60 * 60) : (12 * 60 * 60);
    $token = JWT::encode([
        'user_id' => $id,
        'username' => $username,
        'role' => $role
    ], $ttlSeconds);

    setAdminAuthCookie($token, $ttlSeconds);

    auditLog('login_success', ['username' => $username, 'role' => $role, 'remember_me' => $rememberMe], $id);

    jsonResponse([
        'success' => true,
        'message' => 'Login successful',
        'user' => [
            'id' => $id,
            'username' => $username,
            'role' => $role
        ]
    ]);
}

function verifyConfigAdminPassword($submittedPassword) {
    if (!is_string(ADMIN_PASS) || ADMIN_PASS === '') {
        return false;
    }

    $info = password_get_info(ADMIN_PASS);
    if (!empty($info['algo'])) {
        return password_verify($submittedPassword, ADMIN_PASS);
    }

    error_log('MSPE security warning: ADMIN_PASS is not a password hash. Configure a password_hash() value in .env.');
    return false;
}

function setAdminAuthCookie($token, $ttlSeconds) {
    $isHttps = (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off')
        || (($_SERVER['SERVER_PORT'] ?? '') == 443)
        || (($_SERVER['HTTP_X_FORWARDED_PROTO'] ?? '') === 'https');

    setcookie('mspe_admin_token', $token, [
        'expires' => time() + max(300, (int)$ttlSeconds),
        'path' => '/',
        'secure' => $isHttps,
        'httponly' => true,
        'samesite' => 'Strict'
    ]);
}

function clearAdminAuthCookie() {
    $isHttps = (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off')
        || (($_SERVER['SERVER_PORT'] ?? '') == 443)
        || (($_SERVER['HTTP_X_FORWARDED_PROTO'] ?? '') === 'https');

    setcookie('mspe_admin_token', '', [
        'expires' => time() - 3600,
        'path' => '/',
        'secure' => $isHttps,
        'httponly' => true,
        'samesite' => 'Strict'
    ]);
}

function requestPasswordReset($body) {
    global $db;

    cleanupExpiredPasswordResets();

    $identity = trim((string)($body['identity'] ?? $body['email'] ?? ''));
    if ($identity === '') {
        jsonResponse(['success' => false, 'message' => 'Email or username is required'], 400);
    }

    $matched = findUserForPasswordReset($identity);

    // Always return generic success to avoid username/email enumeration
    if (!$matched) {
        jsonResponse(['success' => true, 'message' => 'If an account exists, a reset email has been sent.']);
    }

    $token = bin2hex(random_bytes(32));
    $expiresAt = date('Y-m-d H:i:s', time() + (30 * 60));

    $db->insert('password_resets', [
        'token' => $token,
        'user_type' => $matched['user_type'],
        'user_id' => $matched['user_id'],
        'email' => $matched['email'],
        'used' => false,
        'expires_at' => $expiresAt
    ]);

    $resetUrl = trim((string)getSetting('password_reset_url', SITE_URL . '/admin/reset-password.html'));
    if ($resetUrl === '') {
        $resetUrl = SITE_URL . '/admin/reset-password.html';
    }
    $separator = strpos($resetUrl, '?') === false ? '?' : '&';
    $resetLink = $resetUrl . $separator . 'token=' . urlencode($token);

    $subject = 'Reset your admin password';
    $plain = "A password reset was requested for your admin account.\n\n";
    $plain .= "Reset link (valid for 30 minutes):\n{$resetLink}\n\n";
    $plain .= "If you did not request this, you can ignore this email.";

    $html = '<h2>Password Reset</h2>'
        . '<p>A password reset was requested for your admin account.</p>'
        . '<p><a href="' . htmlspecialchars($resetLink) . '" style="display:inline-block;padding:10px 16px;background:#0ea5e9;color:#fff;text-decoration:none;border-radius:8px;">Reset Password</a></p>'
        . '<p style="font-size:13px;color:#64748b;">This link is valid for 30 minutes.</p>';

    $send = sendEmail($matched['email'], $subject, $html, $plain);

    if (!$send['success']) {
        error_log('MSPE password reset email failed: ' . ($send['message'] ?? 'unknown'));
    }

    jsonResponse(['success' => true, 'message' => 'If an account exists, a reset email has been sent.']);
}

function resetPassword($body) {
    global $db;

    cleanupExpiredPasswordResets();

    $token = trim((string)($body['token'] ?? ''));
    $newPassword = (string)($body['new_password'] ?? '');

    if ($token === '' || $newPassword === '') {
        jsonResponse(['success' => false, 'message' => 'Token and new password are required'], 400);
    }

    if (strlen($newPassword) < 12) {
        jsonResponse(['success' => false, 'message' => 'Password must be at least 12 characters'], 400);
    }

    $resets = $db->query('password_resets', ['token' => $token]);
    if (empty($resets)) {
        jsonResponse(['success' => false, 'message' => 'Invalid or expired reset token'], 400);
    }

    $reset = array_values($resets)[0];
    if (!empty($reset['used'])) {
        jsonResponse(['success' => false, 'message' => 'This reset token has already been used'], 400);
    }

    if (strtotime($reset['expires_at'] ?? '1970-01-01 00:00:00') < time()) {
        jsonResponse(['success' => false, 'message' => 'Reset token has expired'], 400);
    }

    $hash = password_hash($newPassword, PASSWORD_DEFAULT);

    if (($reset['user_type'] ?? '') === 'default_admin') {
        $existingRows = $db->getAll('admin_auth');
        if (!empty($existingRows)) {
            $first = $existingRows[0];
            $db->update('admin_auth', $first['id'], [
                'username' => ADMIN_USER,
                'email' => $reset['email'] ?? getSetting('admin_email', ADMIN_EMAIL),
                'password_hash' => $hash
            ]);
        } else {
            $db->insert('admin_auth', [
                'username' => ADMIN_USER,
                'email' => $reset['email'] ?? getSetting('admin_email', ADMIN_EMAIL),
                'password_hash' => $hash
            ]);
        }
    } else {
        $userId = $reset['user_id'] ?? '';
        $user = $db->get('users', $userId);
        if (!$user) {
            jsonResponse(['success' => false, 'message' => 'User no longer exists'], 400);
        }
        $db->update('users', $userId, ['password' => $hash]);
    }

    $db->update('password_resets', $reset['id'], ['used' => true]);

    jsonResponse(['success' => true, 'message' => 'Password updated successfully']);
}

function findUserForPasswordReset($identity) {
    global $db;

    // Check DB users by email or username
    $users = $db->getAll('users');
    foreach ($users as $user) {
        if (($user['email'] ?? '') === $identity || ($user['username'] ?? '') === $identity) {
            return [
                'user_type' => 'db_user',
                'user_id' => $user['id'],
                'email' => $user['email']
            ];
        }
    }

    // Fallback default admin account
    if ($identity === ADMIN_USER || $identity === ADMIN_EMAIL || $identity === getSetting('admin_email', ADMIN_EMAIL)) {
        return [
            'user_type' => 'default_admin',
            'user_id' => 'admin',
            'email' => getSetting('admin_email', ADMIN_EMAIL)
        ];
    }

    return null;
}

function cleanupExpiredPasswordResets() {
    global $db;

    $rows = $db->getAll('password_resets');
    $now = time();
    $retentionCutoff = $now - (30 * 24 * 60 * 60);

    foreach ($rows as $row) {
        $expiresAt = strtotime((string)($row['expires_at'] ?? '1970-01-01 00:00:00'));
        $createdAt = strtotime((string)($row['created_at'] ?? '1970-01-01 00:00:00'));
        $isUsed = !empty($row['used']);

        if (($expiresAt > 0 && $expiresAt < $now) || ($isUsed && $createdAt < $retentionCutoff)) {
            if (!empty($row['id'])) {
                $db->delete('password_resets', $row['id']);
            }
        }
    }
}
