<?php
/**
 * TraderAlign — Forgot Password API Endpoint
 * Handles password reset requests with rate limiting, CSRF protection, and security measures
 */

require_once __DIR__ . '/../../config/config.php';
require_once __DIR__ . '/../../src/ScopedDb.php';

// Start session for CSRF validation
if (session_status() === PHP_SESSION_NONE) {
    session_start();
}

// Validate request method
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
    header('Location: /traderalign/sign-in');
    exit;
}

// CSRF Protection
if (!isset($_POST['csrf_token']) || !isset($_SESSION['csrf_token']) || 
    !hash_equals($_SESSION['csrf_token'], $_POST['csrf_token'])) {
    error_log("CSRF token validation failed for password reset attempt from IP: " . ($_SERVER['REMOTE_ADDR'] ?? 'unknown'));
    header('Location: /traderalign/forgot-password?error=invalid_token');
    exit;
}

// Regenerate CSRF token after use
$_SESSION['csrf_token'] = bin2hex(random_bytes(32));

// Rate limiting by IP
$ipAddress = $_SERVER['REMOTE_ADDR'] ?? 'unknown';
$rateLimitKey = 'password_reset_' . $ipAddress;
$maxAttempts = 5;
$rateLimitWindow = 900; // 15 minutes in seconds

// Simple file-based rate limiting (upgrade to Redis/memcached in production)
$rateLimitDir = sys_get_temp_dir() . '/traderalign_rate_limits';
if (!is_dir($rateLimitDir)) {
    mkdir($rateLimitDir, 0755, true);
}

$rateLimitFile = $rateLimitDir . '/' . md5($rateLimitKey) . '.json';
$rateLimitData = [];

if (file_exists($rateLimitFile)) {
    $rateLimitData = json_decode(file_get_contents($rateLimitFile), true) ?: [];
}

// Clean old entries
$rateLimitData = array_filter($rateLimitData, function($timestamp) use ($rateLimitWindow) {
    return $timestamp > (time() - $rateLimitWindow);
});

// Check rate limit
if (count($rateLimitData) >= $maxAttempts) {
    $oldestAttempt = min($rateLimitData);
    $waitTime = ceil(($oldestAttempt + $rateLimitWindow - time()) / 60);
    
    error_log("Rate limit exceeded for password reset from IP: {$ipAddress}");
    header('Location: /traderalign/forgot-password?error=rate_limit&type=warning');
    exit;
}

// Database connection
try {
    $dsn = "mysql:host=" . DB_HOST . ";port=" . DB_PORT . ";dbname=" . DB_NAME . ";charset=utf8mb4";
    $pdo = new PDO($dsn, DB_USER, DB_PASS, [
        PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
        PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
        PDO::MYSQL_ATTR_INIT_COMMAND => "SET NAMES utf8mb4",
        PDO::ATTR_EMULATE_PREPARES => false, // Use native prepared statements
    ]);
} catch (PDOException $e) {
    error_log("Database connection failed in forgot-password: " . $e->getMessage());
    header('Location: /traderalign/forgot-password?error=system_error');
    exit;
}

// Schema bootstrap — deliberately OUTSIDE any transaction. On
// MySQL/MariaDB, a DDL statement (CREATE TABLE, even IF NOT EXISTS —
// even when it's a no-op because the table already exists) triggers an
// implicit COMMIT of whatever transaction is currently active. Running
// this inside the request's beginTransaction()/commit() block below
// silently ended that transaction the moment this ran, so the later
// explicit commit() always threw "There is no active transaction" —
// aborting the request before the success redirect or the reset email
// was ever sent, even though the token itself had already been written.
try {
    $pdo->exec("CREATE TABLE IF NOT EXISTS auth_tokens (
        id INT AUTO_INCREMENT PRIMARY KEY,
        user_id CHAR(36) NOT NULL,
        token VARCHAR(128) NOT NULL UNIQUE,
        type VARCHAR(20) NOT NULL DEFAULT 'password_reset',
        expires_at DATETIME NOT NULL,
        used_at DATETIME DEFAULT NULL,
        created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
        ip_address VARCHAR(45) DEFAULT NULL,
        user_agent VARCHAR(255) DEFAULT NULL,
        INDEX idx_auth_tokens_token (token),
        INDEX idx_auth_tokens_user_type (user_id, type),
        INDEX idx_auth_tokens_expiry (expires_at),
        INDEX idx_auth_tokens_created (created_at),
        FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci");
} catch (PDOException $e) {
    error_log("Failed to ensure auth_tokens table exists: " . $e->getMessage());
    header('Location: /traderalign/forgot-password?error=system_error');
    exit;
}

// Validate and sanitize email
$email = trim($_POST['email'] ?? '');

if (empty($email)) {
    // Add attempt to rate limit
    $rateLimitData[] = time();
    file_put_contents($rateLimitFile, json_encode($rateLimitData));
    
    header('Location: /traderalign/forgot-password?error=invalid_email');
    exit;
}

if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
    // Add attempt to rate limit
    $rateLimitData[] = time();
    file_put_contents($rateLimitFile, json_encode($rateLimitData));
    
    header('Location: /traderalign/forgot-password?error=invalid_email');
    exit;
}

// Normalize email (lowercase for case-insensitive comparison)
$email = strtolower($email);

try {
    // Begin transaction for atomic operations
    $pdo->beginTransaction();
    
    // Find user with additional security checks
    $stmt = $pdo->prepare(
        'SELECT id, email, deleted_at, failed_login_attempts 
         FROM users 
         WHERE email = :email 
         AND deleted_at IS NULL 
         LIMIT 1'
    );
    $stmt->execute([':email' => $email]);
    $user = $stmt->fetch();

    if (!$user) {
        // Don't reveal whether email exists — always show success
        // But still add to rate limit to prevent enumeration
        $rateLimitData[] = time();
        file_put_contents($rateLimitFile, json_encode($rateLimitData));
        
        // Simulate processing time to prevent timing attacks
        usleep(random_int(100000, 300000)); // 100-300ms random delay
        
        $pdo->commit();
        header('Location: /traderalign/forgot-password?sent=1');
        exit;
    }

    // Check if user account is locked or has too many failed attempts
    if (($user['failed_login_attempts'] ?? 0) > 10) {
        error_log("Password reset attempted for locked account: {$user['id']}");
        // Still show success to prevent user enumeration
        $pdo->commit();
        header('Location: /traderalign/forgot-password?sent=1');
        exit;
    }

    // (auth_tokens table is ensured to exist above, outside this transaction.)

    // Invalidate any existing unused reset tokens for this user
    $stmt = $pdo->prepare(
        'UPDATE auth_tokens 
         SET used_at = NOW() 
         WHERE user_id = :uid 
         AND type = :type 
         AND used_at IS NULL 
         AND expires_at > NOW()'
    );
    $stmt->execute([':uid' => $user['id'], ':type' => 'password_reset']);

    // Generate cryptographically secure token
    $token = bin2hex(random_bytes(32));
    $expiresAt = date('Y-m-d H:i:s', strtotime('+1 hour'));

    // Store token with additional security metadata
    $stmt = $pdo->prepare(
        'INSERT INTO auth_tokens (user_id, token, type, expires_at, ip_address, user_agent) 
         VALUES (:uid, :token, :type, :expires, :ip, :ua)'
    );
    $stmt->execute([
        ':uid' => $user['id'],
        ':token' => hash('sha256', $token), // Store hashed token for additional security
        ':type' => 'password_reset',
        ':expires' => $expiresAt,
        ':ip' => $ipAddress,
        ':ua' => $_SERVER['HTTP_USER_AGENT'] ?? 'unknown'
    ]);

    // Commit transaction
    $pdo->commit();

    // Build reset URL
    $resetUrl = APP_URL . '/reset-password?token=' . urlencode($token);

    // Try to send email (implement based on your email service)
    $emailSent = false;
    try {
        // Example using Resend API
        if (defined('RESEND_API_KEY') && RESEND_API_KEY) {
            $emailSent = sendPasswordResetEmail($email, $resetUrl, $user['id']);
        } else {
            // Development: log the reset link
            error_log("Password reset link for {$email}: " . $resetUrl);
            $emailSent = true; // Consider it "sent" in development
        }
    } catch (\Throwable $e) {
        error_log("Failed to send password reset email: " . $e->getMessage());
        // Don't reveal email failure to user, but log it
    }

    // Log the password reset request for audit
    error_log("Password reset requested for user {$user['id']} from IP {$ipAddress}");

} catch (PDOException $e) {
    // Rollback transaction on error
    if ($pdo->inTransaction()) {
        $pdo->rollBack();
    }
    
    error_log("Password reset process failed: " . $e->getMessage());
    
    // Add attempt to rate limit
    $rateLimitData[] = time();
    file_put_contents($rateLimitFile, json_encode($rateLimitData));
    
    header('Location: /traderalign/forgot-password?error=system_error');
    exit;
}

// Always redirect with success (prevents email enumeration)
header('Location: /traderalign/forgot-password?sent=1');
exit;

/**
 * Send password reset email using Resend API
 * 
 * @param string $email User's email address
 * @param string $resetUrl Password reset URL
 * @param string $userId User ID for tracking
 * @return bool Whether email was sent successfully
 */
function sendPasswordResetEmail(string $email, string $resetUrl, string $userId): bool
{
    // Example Resend API implementation
    $apiKey = RESEND_API_KEY;
    $fromEmail = 'TraderAlign <noreply@yourdomain.com>';
    
    $html = <<<HTML
    <!DOCTYPE html>
    <html>
    <head>
        <meta charset="utf-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>Reset Your Password</title>
    </head>
    <body style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif; line-height: 1.6; color: #333; max-width: 600px; margin: 0 auto; padding: 20px;">
        <div style="text-align: center; margin-bottom: 30px;">
            <h1 style="color: #1a1a1a; font-size: 24px; margin-bottom: 10px;">TraderAlign</h1>
        </div>
        
        <h2 style="color: #1a1a1a; font-size: 20px;">Reset Your Password</h2>
        
        <p>We received a request to reset your password. Click the button below to create a new password:</p>
        
        <div style="text-align: center; margin: 30px 0;">
            <a href="{$resetUrl}" style="display: inline-block; background-color: #2563eb; color: #ffffff; text-decoration: none; padding: 12px 30px; border-radius: 6px; font-weight: 600; font-size: 16px;">Reset Password</a>
        </div>
        
        <p>This link will expire in 1 hour for security reasons.</p>
        
        <p>If you didn't request this, you can safely ignore this email. Your password will remain unchanged.</p>
        
        <hr style="border: none; border-top: 1px solid #e5e7eb; margin: 30px 0;">
        
        <p style="font-size: 14px; color: #6b7280;">Security notice: Never share this link with anyone. TraderAlign will never ask for your password via email.</p>
        
        <p style="font-size: 12px; color: #9ca3af;">Request ID: {$userId} | IP: {$_SERVER['REMOTE_ADDR']}</p>
    </body>
    </html>
    HTML;

    $data = [
        'from' => $fromEmail,
        'to' => $email,
        'subject' => 'Reset Your TraderAlign Password',
        'html' => $html,
        'headers' => [
            'X-Entity-Ref-ID' => $userId . '-' . time(),
        ],
    ];

    $ch = curl_init('https://api.resend.com/emails');
    curl_setopt_array($ch, [
        CURLOPT_POST => true,
        CURLOPT_HTTPHEADER => [
            'Authorization: Bearer ' . $apiKey,
            'Content-Type: application/json',
        ],
        CURLOPT_POSTFIELDS => json_encode($data),
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_TIMEOUT => 10,
        CURLOPT_CONNECTTIMEOUT => 5,
    ]);

    $response = curl_exec($ch);
    $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
    $curlError = curl_error($ch);
    curl_close($ch);

    if ($curlError) {
        error_log("Resend API curl error: {$curlError}");
        return false;
    }

    if ($httpCode !== 200) {
        error_log("Resend API error (HTTP {$httpCode}): {$response}");
        return false;
    }

    return true;
}