<?php

declare(strict_types=1);

namespace App\Core;

use Throwable;

final class ExceptionHandler
{
    public static function register(bool $debug, string $logFile): void
    {
        set_exception_handler(function (Throwable $exception) use ($debug, $logFile): void {
            $directory = dirname($logFile);
            if (!is_dir($directory)) {
                mkdir($directory, 0750, true);
            }

            $entry = sprintf(
                "[%s] %s in %s:%d\n%s\n",
                date('c'),
                $exception->getMessage(),
                $exception->getFile(),
                $exception->getLine(),
                $exception->getTraceAsString()
            );
            error_log($entry, 3, $logFile);

            // Throwable::getCode() is not guaranteed to be an HTTP status code;
            // PDO exceptions commonly use SQLSTATE strings such as "42S22".
            $code = $exception->getCode();
            $status = is_int($code) && $code >= 400 && $code < 600 ? $code : 500;

            http_response_code($status);

            // Permission and other expected application errors should be shown as
            // friendly UI messages even while development/debug mode is enabled.
            // Never expose stack traces or filesystem paths to end users.
            if ($status === 403) {
                self::renderError($status, 'Access denied', $exception, false, true);
                return;
            }

            if ($debug) {
                self::renderError($status, 'Something went wrong', $exception, true);
                return;
            }

            $title = match ($status) {
                403 => 'Access denied',
                404 => 'Page not found',
                419 => 'Page expired',
                429 => 'Too many requests',
                default => 'Something went wrong',
            };

            self::renderError($status, $title, null, false);
        });
    }

    private static function renderError(
        int $status,
        string $title,
        ?Throwable $exception,
        bool $debug,
        bool $friendlyMessage = false
    ): void {
        if ($friendlyMessage && $exception !== null) {
            $detail = '<p class="error-message">' .
                htmlspecialchars($exception->getMessage(), ENT_QUOTES, 'UTF-8') .
                '</p>';
        } elseif ($debug && $exception !== null) {
            $detail = '<p class="error-message">Please try again. If the problem continues, contact an administrator.</p>';
        } else {
            $detail = '<p>Please try again or contact an administrator.</p>';
        }

        echo '<!doctype html><html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>'
            . htmlspecialchars($title, ENT_QUOTES, 'UTF-8')
            . '</title><link rel="stylesheet" href="/crm/public/assets/css/app.css"></head><body><main class="error-page"><div class="card"><span class="badge">'
            . $status
            . '</span><h1>'
            . htmlspecialchars($title, ENT_QUOTES, 'UTF-8')
            . '</h1>'
            . $detail
            . '<a class="btn btn-primary" href="/crm/public/">Return home</a></div></main></body></html>';
    }
}
