<?php

declare(strict_types=1);

namespace ServerManager\Core;

use Dotenv\Dotenv;

class App
{
    private static ?App $instance = null;
    private Router $router;
    private Security $security;
    private View $view;
    private array $config;
    private Encryption $encryption;
    private bool $booted = false;

    private function __construct()
    {
    }

    public static function getInstance(): self
    {
        if (self::$instance === null) {
            self::$instance = new self();
        }
        return self::$instance;
    }

    public static function getConfig(): array
    {
        return self::getInstance()->config;
    }

    public static function getEncryption(): Encryption
    {
        return self::getInstance()->encryption;
    }

    public function boot(string $basePath): void
    {
        if ($this->booted) {
            return;
        }

        $this->loadEnvironment($basePath);
        $this->setErrorHandling();
        $this->config = require $basePath . '/config/config.php';

        date_default_timezone_set($this->config['app']['timezone'] ?? 'UTC');

        $dbConfig = $this->config['database'];
        $dbConfig['timezone'] = $this->config['app']['timezone'] ?? 'UTC';
        Database::getInstance($dbConfig);

        Session::init($this->config['session']);

        Logger::init(
            $basePath . '/' . ($this->config['log']['path'] ?? 'logs/'),
            $this->config['log']['level'] ?? 'warning'
        );

        $this->security = new Security();
        $this->encryption = new Encryption($this->config['encryption']);
        $this->view = new View($this->security);
        $this->router = new Router();

        $this->loadRoutes($basePath . '/routes/');
        $this->booted = true;
    }

    public function run(): void
    {
        $method = $_SERVER['REQUEST_METHOD'] ?? 'GET';
        $uri = $_SERVER['REQUEST_URI'] ?? '/';

        try {
            $this->router->dispatch($method, $uri);
        } catch (\Throwable $e) {
            Logger::getInstance()->error($e->getMessage(), [
                'file' => $e->getFile(),
                'line' => $e->getLine(),
                'trace' => $e->getTraceAsString(),
            ]);

            if ($this->config['app']['debug'] ?? false) {
                throw $e;
            }

            $this->view->error(500, 'An internal server error occurred.');
        }
    }

    public function getRouter(): Router
    {
        return $this->router;
    }

    public function getSecurity(): Security
    {
        return $this->security;
    }

    public function getView(): View
    {
        return $this->view;
    }

    private function loadEnvironment(string $basePath): void
    {
        if (file_exists($basePath . '/.env')) {
            $dotenv = Dotenv::createImmutable($basePath);
            $dotenv->load();
            $dotenv->required([
                'APP_NAME',
                'DB_HOST',
                'DB_DATABASE',
                'DB_USERNAME',
            ]);
        }
    }

    private function setErrorHandling(): void
    {
        error_reporting(E_ALL);
        ini_set('display_errors', '0');
        ini_set('log_errors', '1');

        set_exception_handler(function (\Throwable $e) {
            Logger::getInstance()->critical($e->getMessage(), [
                'file' => $e->getFile(),
                'line' => $e->getLine(),
                'code' => $e->getCode(),
            ]);
            http_response_code(500);
            echo json_encode(['error' => 'Internal Server Error']);
            exit;
        });

        set_error_handler(function ($severity, $message, $file, $line) {
            if (!(error_reporting() & $severity)) {
                return false;
            }
            if ($severity === E_DEPRECATED || $severity === E_USER_DEPRECATED) {
                return false;
            }
            throw new \ErrorException($message, 0, $severity, $file, $line);
        });
    }

    private function loadRoutes(string $routesPath): void
    {
        if (is_dir($routesPath)) {
            foreach (glob($routesPath . '*.php') as $routeFile) {
                $route = $this->router;
                require $routeFile;
            }
        }
    }
}
