<?php

namespace Core\System;

use Core\App\Models\SecurityLog;

class SecurityMiddleware
{
    protected array $settings;
    protected string $ip;
    protected string $url;
    protected ?string $userAgent;

    public function __construct()
    {
        $this->settings  = $this->loadSettings();
        $this->ip        = $_SERVER['REMOTE_ADDR'] ?? 'unknown';
        $this->url       = $_SERVER['REQUEST_URI'] ?? '';
        $this->userAgent = $_SERVER['HTTP_USER_AGENT'] ?? null;
    }

    public function handle()
    {
        $this->checkMaintenanceMode();
        $this->checkWhitelist();
        $this->checkBanList();
        $this->checkRateLimit();
        $this->registerErrorHandler();
    }

    protected function loadSettings(): array
    {
        $file = ROOT_PATH . '/storage/settings/settings_security.json';

        if (!file_exists($file)) {
            return [];
        }

        return json_decode(file_get_contents($file), true) ?: [];
    }

    /**
     * Maintenance Mode
     */
    protected function checkMaintenanceMode()
    {
        if ((int)($this->settings['maintenance_mode'] ?? 0) !== 1) {
            return;
        }

        if (str_starts_with($this->url, '/admin')) {
            return;
        }

        if (!empty($_SESSION['admin_logged_in'])) {
            return;
        }

        $message = trim($this->settings['maintenance_msg'] ?? '');

        if ($message === '') {
            $message = __('maintenance.heading', 'We\'ll be back soon');
        }

        http_response_code(503);
        require ROOT_PATH . '/admin/templates/maintenance.php';
        exit;
    }

    protected function checkWhitelist()
    {
        $whitelist = $this->settings['whitelist'] ?? [];

        if (in_array($this->ip, $whitelist, true)) {
            return;
        }
    }

    protected function checkBanList()
    {
        $banList = $this->settings['ban_list'] ?? [];

        if (in_array($this->ip, $banList, true)) {
            $this->log('ban', 'Access blocked (banned IP)');
            http_response_code(403);
            exit('403 Forbidden');
        }
    }

    protected function checkRateLimit()
    {
        // If db() is not available yet → skip
        if (!function_exists('db')) {
            return;
        }

        if (($this->settings['ddos_enabled'] ?? 0) != 1) {
            return;
        }

        // Whitelisted IPs are exempt from rate limiting
        if (in_array($this->ip, $this->settings['whitelist'] ?? [], true)) {
            return;
        }

        $limit  = (int)($this->settings['ddos_limit'] ?? 60);
        $window = 60;
        $now    = time();

        try {
            $db = db();

            // security_rate_limits is defined in arscript_A.sql (install schema).
            // No runtime CREATE TABLE needed for production deployments.

            $stmt = $db->prepare("
                SELECT requests, window_start
                FROM security_rate_limits
                WHERE ip = :ip
            ");
            $stmt->execute(['ip' => $this->ip]);
            $row = $stmt->fetch(\PDO::FETCH_ASSOC);

            if (!$row) {
                $stmt = $db->prepare("
                    INSERT INTO security_rate_limits (ip, requests, window_start)
                    VALUES (:ip, 1, :start)
                ");
                $stmt->execute([
                    'ip'    => $this->ip,
                    'start' => $now,
                ]);
                return;
            }

            if (($now - $row['window_start']) > $window) {
                $stmt = $db->prepare("
                    UPDATE security_rate_limits
                    SET requests = 1, window_start = :start
                    WHERE ip = :ip
                ");
                $stmt->execute([
                    'ip'    => $this->ip,
                    'start' => $now,
                ]);
                return;
            }

            if ($row['requests'] >= $limit) {
                $this->log('ddos', 'Rate limit exceeded');
                http_response_code(429);
                exit('Too Many Requests');
            }

            $stmt = $db->prepare("
                UPDATE security_rate_limits
                SET requests = requests + 1
                WHERE ip = :ip
            ");
            $stmt->execute(['ip' => $this->ip]);

        } catch (\Throwable $e) {
            // Table missing or DB error — fail silently, never crash the app
            // Rate limiting is a non-critical feature
        }
    }

    protected function registerErrorHandler()
    {
        set_error_handler(function ($severity, $message, $file, $line) {
            $warningLevels = [
                E_NOTICE, E_USER_NOTICE, E_DEPRECATED, E_USER_DEPRECATED,
                E_WARNING, E_USER_WARNING, E_CORE_WARNING, E_COMPILE_WARNING,
                E_RECOVERABLE_ERROR,
            ];

            if (in_array($severity, $warningLevels, true)) {
                try {
                    $label = $this->phpErrorLabel($severity);
                    $this->log('warning', "[$label] $message in $file:$line");
                } catch (\Throwable $e) {
                    // never crash on logging
                }
                return false;
            }

            return false;
        });

        set_exception_handler(function (\Throwable $e) {
            try {
                $msg = sprintf(
                    '%s: %s in %s:%d',
                    get_class($e),
                    $e->getMessage(),
                    $e->getFile(),
                    $e->getLine()
                );
                $this->log('error', $msg);
            } catch (\Throwable $ignore) {
            }

            if (!headers_sent()) {
                http_response_code(500);
            }

            $accept = strtolower((string)($_SERVER['HTTP_ACCEPT'] ?? ''));
            $xhr = strtolower((string)($_SERVER['HTTP_X_REQUESTED_WITH'] ?? '')) === 'xmlhttprequest';
            if ($xhr || str_contains($accept, 'application/json')) {
                header('Content-Type: application/json; charset=utf-8');
                echo json_encode([
                    'success' => false,
                    'error'   => APP_DEBUG ? $e->getMessage() : __('errors.server_error', 'Server error'),
                ], JSON_UNESCAPED_UNICODE);
                exit;
            }

            if (defined('APP_DEBUG') && APP_DEBUG) {
                echo '<pre style="padding:20px;font-family:monospace">' . htmlspecialchars((string)$e) . '</pre>';
                exit;
            }

            $adminUri = str_starts_with((string)($_SERVER['REQUEST_URI'] ?? ''), '/admin');
            if ($adminUri && file_exists(ROOT_PATH . '/admin/templates/errors/500.php')) {
                require ROOT_PATH . '/admin/templates/errors/500.php';
                exit;
            }

            if (class_exists(ThemeManager::class)) {
                $activeTheme = ThemeManager::active();
                $theme500 = ROOT_PATH . '/themes/' . $activeTheme . '/templates/500.php';
                if (file_exists($theme500)) {
                    require $theme500;
                    exit;
                }
            }

            echo '500 Server Error';
            exit;
        });

        register_shutdown_function(function () {
            $error = error_get_last();
            if (!$error) {
                return;
            }

            $fatalLevels = [E_ERROR, E_PARSE, E_CORE_ERROR, E_COMPILE_ERROR, E_USER_ERROR];
            if (!in_array((int)$error['type'], $fatalLevels, true)) {
                return;
            }

            try {
                $label = $this->phpErrorLabel((int)$error['type']);
                $this->log('error', "[$label] {$error['message']} in {$error['file']}:{$error['line']}");
            } catch (\Throwable $e) {
                // never crash on logging
            }
        });
    }

    protected function phpErrorLabel(int $severity): string
    {
        return match ($severity) {
            E_ERROR => 'E_ERROR',
            E_WARNING => 'E_WARNING',
            E_PARSE => 'E_PARSE',
            E_NOTICE => 'E_NOTICE',
            E_CORE_ERROR => 'E_CORE_ERROR',
            E_CORE_WARNING => 'E_CORE_WARNING',
            E_COMPILE_ERROR => 'E_COMPILE_ERROR',
            E_COMPILE_WARNING => 'E_COMPILE_WARNING',
            E_USER_ERROR => 'E_USER_ERROR',
            E_USER_WARNING => 'E_USER_WARNING',
            E_USER_NOTICE => 'E_USER_NOTICE',
            E_RECOVERABLE_ERROR => 'E_RECOVERABLE_ERROR',
            E_DEPRECATED => 'E_DEPRECATED',
            E_USER_DEPRECATED => 'E_USER_DEPRECATED',
            default => 'E_UNKNOWN',
        };
    }

    protected function log(string $type, string $message)
    {
        try {
            $db = db();

            $stmt = $db->prepare(
                "INSERT INTO security_logs
                    (type, ip, message, user_agent, url, created_at)
                 VALUES
                    (:type, :ip, :message, :user_agent, :url, NOW())"
            );

            $stmt->execute([
                'type'       => $type,
                'ip'         => $this->ip,
                'message'    => $message,
                'user_agent' => $this->userAgent,
                'url'        => $this->url,
            ]);
        } catch (\Throwable $e) {
            // Never crash on logging failure
        }
    }
}
