<?php
/**
 * Token-based Authentication for Admin API
 * 
 * Generates a secure token on login, validates on subsequent requests.
 * Tokens expire after 30 minutes of inactivity (sliding window).
 * 
 * Usage in protected endpoints:
 *   require_once __DIR__ . '/includes/auth.php';
 *   requireAuth(); // Will return 401 if token is invalid/expired
 */

require_once __DIR__ . '/db.php';

// Token expiry in seconds (30 minutes)
define('TOKEN_EXPIRY_SECONDS', 1800);

/**
 * Ensure admin_tokens table exists (auto-creates on first use)
 */
function ensureTokenTable() {
    global $pdo;
    static $checked = false;
    if ($checked) return;
    
    try {
        $pdo->exec("
            CREATE TABLE IF NOT EXISTS admin_tokens (
                id INT AUTO_INCREMENT PRIMARY KEY,
                admin_id INT NOT NULL,
                token VARCHAR(128) NOT NULL UNIQUE,
                created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
                last_activity DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
                ip_address VARCHAR(45) DEFAULT NULL,
                user_agent VARCHAR(255) DEFAULT NULL,
                device_name VARCHAR(100) DEFAULT NULL,
                INDEX idx_token (token),
                INDEX idx_expiry (last_activity)
            ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4
        ");
        
        // Add device_name column if table already existed without it
        try {
            $pdo->exec("ALTER TABLE admin_tokens ADD COLUMN device_name VARCHAR(100) DEFAULT NULL");
        } catch (Exception $e) {
            // Column already exists - ignore
        }
        
        $checked = true;
    } catch (Exception $e) {
        error_log("Token table creation error: " . $e->getMessage());
    }
}

/**
 * Generate a new auth token for an admin user
 * 
 * @param int $adminId
 * @param string $deviceName - Phone model name (e.g. "Samsung Galaxy S23")
 * @return string The generated token
 */
function generateAuthToken($adminId, $deviceName = '') {
    global $pdo;
    ensureTokenTable();
    
    // Generate secure random token
    $token = bin2hex(random_bytes(48)); // 96 char hex string
    
    // Clean up expired tokens for this admin (housekeeping)
    try {
        $pdo->prepare("DELETE FROM admin_tokens WHERE admin_id = ? AND last_activity < DATE_SUB(NOW(), INTERVAL ? SECOND)")
            ->execute([$adminId, TOKEN_EXPIRY_SECONDS]);
    } catch (Exception $e) {
        error_log("Token cleanup error: " . $e->getMessage());
    }
    
    // Insert new token
    $stmt = $pdo->prepare("
        INSERT INTO admin_tokens (admin_id, token, created_at, last_activity, ip_address, user_agent, device_name)
        VALUES (?, ?, NOW(), NOW(), ?, ?, ?)
    ");
    $stmt->execute([
        $adminId,
        $token,
        $_SERVER['REMOTE_ADDR'] ?? '',
        substr($_SERVER['HTTP_USER_AGENT'] ?? '', 0, 255),
        substr($deviceName, 0, 100)
    ]);
    
    return $token;
}

/**
 * Validate a token and return admin_id if valid
 * Also refreshes the token's last_activity (sliding window)
 * 
 * @param string $token
 * @return int|false Admin ID if valid, false if invalid/expired
 */
function validateAuthToken($token) {
    global $pdo;
    ensureTokenTable();
    
    if (empty($token)) return false;
    
    try {
        $stmt = $pdo->prepare("
            SELECT admin_id, last_activity 
            FROM admin_tokens 
            WHERE token = ? 
            LIMIT 1
        ");
        $stmt->execute([$token]);
        $row = $stmt->fetch(PDO::FETCH_ASSOC);
        
        if (!$row) return false;
        
        // Check if token has expired
        $lastActivity = strtotime($row['last_activity']);
        if ((time() - $lastActivity) > TOKEN_EXPIRY_SECONDS) {
            // Token expired - delete it
            $pdo->prepare("DELETE FROM admin_tokens WHERE token = ?")->execute([$token]);
            return false;
        }
        
        // Token is valid - refresh last_activity (sliding window)
        $pdo->prepare("UPDATE admin_tokens SET last_activity = NOW() WHERE token = ?")->execute([$token]);
        
        return (int)$row['admin_id'];
        
    } catch (Exception $e) {
        error_log("Token validation error: " . $e->getMessage());
        return false;
    }
}

/**
 * Invalidate/delete a token (logout)
 * 
 * @param string $token
 * @return bool
 */
function invalidateToken($token) {
    global $pdo;
    ensureTokenTable();
    
    try {
        $stmt = $pdo->prepare("DELETE FROM admin_tokens WHERE token = ?");
        $stmt->execute([$token]);
        return true;
    } catch (Exception $e) {
        return false;
    }
}

/**
 * Invalidate all tokens for an admin (force logout everywhere)
 * 
 * @param int $adminId
 * @return bool
 */
function invalidateAllTokens($adminId) {
    global $pdo;
    ensureTokenTable();
    
    try {
        $stmt = $pdo->prepare("DELETE FROM admin_tokens WHERE admin_id = ?");
        $stmt->execute([$adminId]);
        return true;
    } catch (Exception $e) {
        return false;
    }
}

/**
 * Extract token from request headers or query parameter
 * Supports: Authorization: Bearer <token> OR ?token=<token>
 * 
 * @return string|null
 */
function extractToken() {
    // Check Authorization header first
    $headers = [];
    if (function_exists('getallheaders')) {
        $headers = getallheaders();
    } else {
        // Fallback for servers that don't support getallheaders()
        foreach ($_SERVER as $key => $value) {
            if (substr($key, 0, 5) === 'HTTP_') {
                $headerName = str_replace('_', '-', substr($key, 5));
                $headers[$headerName] = $value;
            }
        }
    }
    
    // Case-insensitive header check
    foreach ($headers as $key => $value) {
        if (strtolower($key) === 'authorization') {
            if (preg_match('/^Bearer\s+(.+)$/i', $value, $matches)) {
                return $matches[1];
            }
        }
    }
    
    // Fallback: check query parameter (for backward compat)
    if (isset($_GET['token'])) {
        return $_GET['token'];
    }
    
    // Fallback: check POST body
    if (isset($_POST['token'])) {
        return $_POST['token'];
    }
    
    return null;
}

/**
 * Require authentication - sends 401 if invalid
 * Call this at the top of any protected endpoint.
 * 
 * @return int The authenticated admin's ID
 */
function requireAuth() {
    $token = extractToken();
    
    if (!$token) {
        http_response_code(401);
        echo json_encode(['success' => false, 'error' => 'Authentication required', 'code' => 'NO_TOKEN']);
        exit;
    }
    
    $adminId = validateAuthToken($token);
    
    if ($adminId === false) {
        http_response_code(401);
        echo json_encode(['success' => false, 'error' => 'Token expired or invalid', 'code' => 'TOKEN_EXPIRED']);
        exit;
    }
    
    return $adminId;
}

/**
 * Clean up all expired tokens (call periodically)
 */
function cleanExpiredTokens() {
    global $pdo;
    ensureTokenTable();
    
    try {
        $pdo->prepare("DELETE FROM admin_tokens WHERE last_activity < DATE_SUB(NOW(), INTERVAL ? SECOND)")
            ->execute([TOKEN_EXPIRY_SECONDS]);
    } catch (Exception $e) {
        error_log("Token cleanup error: " . $e->getMessage());
    }
}
