<?php
/**
 * Shared utility functions for the MSP Quote Wizard API.
 *
 * Provides UUID generation, token generation, JSON response helpers,
 * input validation, CORS headers, and logging.
 */

// Deny direct access
if (basename($_SERVER['SCRIPT_FILENAME'] ?? '') === basename(__FILE__)) {
    http_response_code(403);
    exit('Direct access denied.');
}

require_once __DIR__ . '/config.php';

// --------------------------------------------------------------------------
// UUID / Token generation
// --------------------------------------------------------------------------

/**
 * Generate a UUID v4 string (lowercase, 36 chars with hyphens).
 *
 * Format: xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx
 * where y is one of 8, 9, a, b.
 *
 * @return string
 */
function generate_uuid(): string
{
    $bytes = random_bytes(16);

    // Set version to 4 (0100 in binary)
    $bytes[6] = chr(ord($bytes[6]) & 0x0f | 0x40);
    // Set variant to RFC 4122 (10xx in binary)
    $bytes[8] = chr(ord($bytes[8]) & 0x3f | 0x80);

    return sprintf(
        '%s-%s-%s-%s-%s',
        bin2hex(substr($bytes, 0, 4)),
        bin2hex(substr($bytes, 4, 2)),
        bin2hex(substr($bytes, 6, 2)),
        bin2hex(substr($bytes, 8, 2)),
        bin2hex(substr($bytes, 10, 6))
    );
}

/**
 * Generate a URL-safe access token matching Python's secrets.token_urlsafe(32).
 *
 * Produces a 43-character base64url-encoded string (no padding) from 32
 * random bytes, exactly matching the Python implementation.
 *
 * @return string 43-character URL-safe token
 */
function generate_access_token(): string
{
    $bytes = random_bytes(32);
    // base64url encode: replace +/ with -_, strip padding =
    return rtrim(strtr(base64_encode($bytes), '+/', '-_'), '=');
}

// --------------------------------------------------------------------------
// JSON response helpers
// --------------------------------------------------------------------------

/**
 * Send a JSON response with the given data and HTTP status code.
 *
 * Sets Content-Type header, outputs JSON, and terminates the script.
 *
 * @param mixed $data   Data to encode as JSON.
 * @param int   $status HTTP status code (default 200).
 * @return never
 */
function json_response($data, int $status = 200): void
{
    http_response_code($status);
    header('Content-Type: application/json; charset=utf-8');
    echo json_encode($data, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES | JSON_PRESERVE_ZERO_FRACTION);
    exit;
}

/**
 * Send a JSON error response.
 *
 * @param string      $message Error message.
 * @param int         $status  HTTP status code (default 400).
 * @param mixed|null  $details Additional error details.
 * @return never
 */
function error_response(string $message, int $status = 400, $details = null): void
{
    $body = ['detail' => $message];
    if ($details !== null) {
        $body['errors'] = $details;
    }
    json_response($body, $status);
}

// --------------------------------------------------------------------------
// Request parsing
// --------------------------------------------------------------------------

/**
 * Parse the JSON request body.
 *
 * @return array Decoded JSON as an associative array.
 */
function get_json_body(): array
{
    $raw = file_get_contents('php://input');
    if (empty($raw)) {
        return [];
    }

    $data = json_decode($raw, true);
    if (json_last_error() !== JSON_ERROR_NONE) {
        error_response('Invalid JSON in request body', 400);
    }

    return $data;
}

/**
 * Get the client IP address, accounting for reverse proxies.
 *
 * Checks X-Forwarded-For first, then X-Real-IP, then REMOTE_ADDR.
 *
 * @return string|null
 */
function get_client_ip(): ?string
{
    if (!empty($_SERVER['HTTP_X_FORWARDED_FOR'])) {
        $parts = explode(',', $_SERVER['HTTP_X_FORWARDED_FOR']);
        return trim($parts[0]);
    }

    if (!empty($_SERVER['HTTP_X_REAL_IP'])) {
        return trim($_SERVER['HTTP_X_REAL_IP']);
    }

    return $_SERVER['REMOTE_ADDR'] ?? null;
}

/**
 * Get the User-Agent header value.
 *
 * @return string|null
 */
function get_user_agent(): ?string
{
    return $_SERVER['HTTP_USER_AGENT'] ?? null;
}

// --------------------------------------------------------------------------
// CORS
// --------------------------------------------------------------------------

/**
 * Emit CORS headers based on the configured allowed origins.
 *
 * For preflight (OPTIONS) requests, this also sets the allowed methods
 * and headers, then terminates the script with 204.
 */
function cors_headers(): void
{
    $origin = $_SERVER['HTTP_ORIGIN'] ?? '';
    $allowed = array_map('trim', explode(',', CORS_ALLOWED_ORIGINS));

    // Allow the origin if it matches our whitelist, or allow all if '*'
    if (in_array('*', $allowed, true) || in_array($origin, $allowed, true)) {
        $send_origin = in_array('*', $allowed, true) ? '*' : $origin;
        header("Access-Control-Allow-Origin: {$send_origin}");
    }

    header('Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS');
    header('Access-Control-Allow-Headers: Content-Type, Authorization, X-Requested-With');
    header('Access-Control-Max-Age: 86400');

    // Handle preflight
    if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') {
        http_response_code(204);
        exit;
    }
}

// --------------------------------------------------------------------------
// Validation
// --------------------------------------------------------------------------

/**
 * Validate that all required fields are present and non-empty in the data.
 *
 * @param array    $data   Associative array of input data.
 * @param string[] $fields List of required field names.
 * @return string[] Array of error messages (empty if valid).
 */
function validate_required(array $data, array $fields): array
{
    $errors = [];
    foreach ($fields as $field) {
        if (!isset($data[$field]) || (is_string($data[$field]) && trim($data[$field]) === '')) {
            $errors[] = "Field '{$field}' is required.";
        }
    }
    return $errors;
}

/**
 * Validate an email address.
 *
 * @param string $email Email address to validate.
 * @return bool True if valid.
 */
function validate_email(string $email): bool
{
    return filter_var($email, FILTER_VALIDATE_EMAIL) !== false;
}

// --------------------------------------------------------------------------
// Logging
// --------------------------------------------------------------------------

/**
 * Append a message to the application log file.
 *
 * @param string $level   Log level (INFO, WARNING, ERROR).
 * @param string $message Log message.
 */
function app_log(string $level, string $message): void
{
    $dir = dirname(LOG_FILE);
    if (!is_dir($dir)) {
        @mkdir($dir, 0750, true);
    }

    $timestamp = gmdate('Y-m-d\TH:i:s\Z');
    $line = "[{$timestamp}] [{$level}] {$message}" . PHP_EOL;
    @file_put_contents(LOG_FILE, $line, FILE_APPEND | LOCK_EX);
}

// --------------------------------------------------------------------------
// Datetime helpers
// --------------------------------------------------------------------------

/**
 * Format a datetime value for JSON output (ISO 8601 format).
 *
 * Accepts a datetime string from MySQL (Y-m-d H:i:s) and returns
 * an ISO 8601 string, or null if input is null/empty.
 *
 * @param string|null $dt MySQL datetime string.
 * @return string|null ISO 8601 formatted string.
 */
function format_datetime(?string $dt): ?string
{
    if ($dt === null || $dt === '' || $dt === '0000-00-00 00:00:00') {
        return null;
    }
    // MySQL DATETIME is already in UTC for this application
    $ts = strtotime($dt);
    if ($ts === false) {
        return null;
    }
    return gmdate('Y-m-d\TH:i:s\Z', $ts);
}

/**
 * Get the current UTC datetime in MySQL format.
 *
 * @return string Y-m-d H:i:s
 */
function utc_now(): string
{
    return gmdate('Y-m-d H:i:s');
}
