<?php

namespace App\Core;

/**
 * Logger — File-Based Application Logger
 *
 * Writes dated log entries to storage/logs/app-YYYY-MM-DD.log.
 * One log file per calendar day (UTC). Files are appended — no rotation.
 *
 * Log entry format (DBP v1.0 Ch.7):
 *   [YYYY-MM-DD HH:MM:SS UTC] [LEVEL] [METHOD /url] [user-or-guest] Message {json}
 *
 * Usage (called by ErrorHandler, available to all app layers):
 *   Logger::error('Something failed', ['key' => 'value']);
 *   Logger::warning('Token close to expiry');
 *   Logger::info('Guest checked in', ['guest_id' => 42]);
 *
 * Design constraints:
 *   - Logger MUST NOT throw. If writing fails, it silently falls back
 *     to PHP's built-in error_log().
 *   - Logger MUST NOT depend on the database or session.
 *   - All timestamps are UTC (DBP v1.0 Ch.3).
 *   - User/role context is populated after Auth (Sprint 1).
 *     Pre-auth requests log user as '-'.
 *
 * Log path source: config('storage.logs') from config/storage.php (INIT-002).
 *
 * @package Inveetaire
 * @ref     DBP v1.0 Ch.7, SAD v1.1 Ch.13, INIT-006
 */
class Logger
{
    // ─────────────────────────────────────────────────────────────────────────
    // LOG LEVEL CONSTANTS
    // ─────────────────────────────────────────────────────────────────────────

    const ERROR   = 'ERROR';
    const WARNING = 'WARNING';
    const INFO    = 'INFO';

    // ─────────────────────────────────────────────────────────────────────────
    // PUBLIC API
    // ─────────────────────────────────────────────────────────────────────────

    /**
     * Log an error-level event.
     * Use for: exceptions, unhandled errors, critical failures.
     *
     * @param string               $message Human-readable description.
     * @param array<string, mixed> $context Optional key-value context data.
     * @return void
     */
    public static function error(string $message, array $context = []): void
    {
        self::write(self::ERROR, $message, $context);
    }

    /**
     * Log a warning-level event.
     * Use for: recoverable anomalies, rate limit approaches, config mismatches.
     *
     * @param string               $message Human-readable description.
     * @param array<string, mixed> $context Optional key-value context data.
     * @return void
     */
    public static function warning(string $message, array $context = []): void
    {
        self::write(self::WARNING, $message, $context);
    }

    /**
     * Log an informational event.
     * Use for: guest check-in, invitation sent, workspace created, etc.
     *
     * @param string               $message Human-readable description.
     * @param array<string, mixed> $context Optional key-value context data.
     * @return void
     */
    public static function info(string $message, array $context = []): void
    {
        self::write(self::INFO, $message, $context);
    }

    // ─────────────────────────────────────────────────────────────────────────
    // PRIVATE — Core Write Logic
    // ─────────────────────────────────────────────────────────────────────────

    /**
     * Format and append a log entry to the daily log file.
     *
     * Entry format:
     *   [2026-06-30 08:00:00 UTC] [ERROR] [GET /app/couple/guests] [admin:1] Message {"key":"val"}
     *
     * @param string               $level   One of Logger::ERROR, WARNING, INFO.
     * @param string               $message The log message.
     * @param array<string, mixed> $context Additional structured data.
     * @return void
     */
    private static function write(string $level, string $message, array $context): void
    {
        try {
            $logDir  = self::resolveLogDirectory();
            $logFile = $logDir . DIRECTORY_SEPARATOR . 'app-' . gmdate('Y-m-d') . '.log';

            // Build the log entry components.
            $timestamp = gmdate('Y-m-d H:i:s') . ' UTC';
            $request   = self::formatRequest();
            $user      = self::formatUser();

            $entry = sprintf(
                '[%s] [%s] [%s] [%s] %s',
                $timestamp,
                str_pad($level, 7),   // right-pad for alignment: ERROR(5), WARNING(7), INFO(4)
                $request,
                $user,
                $message
            );

            // Append structured context as JSON on the same line.
            if (!empty($context)) {
                $entry .= ' ' . json_encode(
                    $context,
                    JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE
                );
            }

            $entry .= PHP_EOL;

            // Create the log directory if it doesn't exist.
            if (!is_dir($logDir)) {
                @mkdir($logDir, 0755, true);
            }

            // Append to today's log file. LOCK_EX prevents corruption under
            // concurrent requests (Apache + PHP-FPM workers).
            @file_put_contents($logFile, $entry, FILE_APPEND | LOCK_EX);

        } catch (\Throwable $e) {
            // Logger must never throw. Fall back to PHP's built-in error_log().
            @error_log('[Inveetaire Logger] Write failed: ' . $e->getMessage());
        }
    }

    // ─────────────────────────────────────────────────────────────────────────
    // PRIVATE — Helpers
    // ─────────────────────────────────────────────────────────────────────────

    /**
     * Resolve the absolute path to the log directory.
     *
     * Priority:
     *   1. config('storage.logs') — populated from .env (INIT-002)
     *   2. ROOT_PATH . '/storage/logs' — fallback if config is unavailable
     *
     * @return string Absolute directory path without trailing separator.
     */
    private static function resolveLogDirectory(): string
    {
        // config() is available once Bootstrap has run.
        // During very early boot (before Bootstrap), fall back to ROOT_PATH.
        if (function_exists('config')) {
            $dir = config('storage.logs', '');
            if (is_string($dir) && $dir !== '') {
                return rtrim($dir, '/\\');
            }
        }

        return ROOT_PATH . DIRECTORY_SEPARATOR . 'storage' . DIRECTORY_SEPARATOR . 'logs';
    }

    /**
     * Format the HTTP request method + URI for the log entry.
     * Returns '-' for CLI invocations (e.g. migration scripts).
     *
     * @return string e.g. "GET /app/couple/guests"
     */
    private static function formatRequest(): string
    {
        if (php_sapi_name() === 'cli') {
            return 'CLI';
        }

        $method = $_SERVER['REQUEST_METHOD'] ?? '-';
        $uri    = $_SERVER['REQUEST_URI']    ?? '-';

        // Strip query string from URI for cleaner log grouping.
        $path = strtok($uri, '?') ?: '-';

        return $method . ' ' . $path;
    }

    /**
     * Format the authenticated user identifier for the log entry.
     *
     * Returns '-' until the Auth module (Sprint 1) populates session data.
     * After Sprint 1, this will return e.g. "admin:7" or "couple:3".
     *
     * @return string e.g. "admin:7" or "-"
     */
    private static function formatUser(): string
    {
        // Auth session populated in Sprint 1 (AUTH module).
        // Placeholder until then.
        if (isset($_SESSION['user_id'], $_SESSION['role'])) {
            return $_SESSION['role'] . ':' . $_SESSION['user_id'];
        }

        return '-';
    }
}
