<?php
declare(strict_types=1);

namespace App\Core;

final class App
{
    private static ?App $instance = null;

    private ?Database $db = null;
    private ?Session $session = null;
    private ?Router $router = null;
    private array $config = [];
    private ?object $currentEmployee = null;
    private ?array $currentBranch = null;
    private array $bindings = [];
    private array $factories = [];
    private string $basePath;
    private bool $booted = false;

    private function __construct()
    {
        $this->basePath = dirname(__DIR__, 2);
    }

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

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

        date_default_timezone_set('Africa/Cairo');

        $this->loadEnv();
        $this->loadConfig();

        ExceptionHandler::register();

        $this->initDatabase();
        $this->initSession();
        $this->loadDbConfigOverrides();
        $this->loadModuleBootstraps();
        $this->registerServiceProviders();

        $this->router = new Router();
        $this->loadModuleRoutes();
        $this->loadModuleApiRoutes();

        $this->booted = true;
        return $this;
    }

    private function loadEnv(): void
    {
        $envFile = $this->basePath . '/.env';
        if (!file_exists($envFile)) {
            return;
        }

        $lines = file($envFile, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
        foreach ($lines as $line) {
            $line = trim($line);
            if ($line === '' || $line[0] === '#') {
                continue;
            }
            if (str_contains($line, '=')) {
                [$key, $value] = explode('=', $line, 2);
                $key = trim($key);
                $value = trim($value);
                if (!isset($_ENV[$key])) {
                    $_ENV[$key] = $value;
                    putenv("{$key}={$value}");
                }
            }
        }
    }

    private function loadConfig(): void
    {
        $configDir = $this->basePath . '/config';
        if (!is_dir($configDir)) {
            return;
        }

        $files = glob($configDir . '/*.php');
        if ($files === false) {
            return;
        }

        foreach ($files as $file) {
            $key = basename($file, '.php');
            $this->config[$key] = require $file;
        }
    }

    private function initDatabase(): void
    {
        $this->db = new Database(
            (string) ($this->config['database']['host'] ?? env('DB_HOST', '127.0.0.1')),
            (int) ($this->config['database']['port'] ?? env('DB_PORT', 3306)),
            (string) ($this->config['database']['name'] ?? env('DB_NAME', 'the_club_erp')),
            (string) ($this->config['database']['user'] ?? env('DB_USER', 'root')),
            (string) ($this->config['database']['pass'] ?? env('DB_PASS', '')),
            (string) ($this->config['database']['charset'] ?? 'utf8mb4')
        );
    }

    private function initSession(): void
    {
        $this->session = new Session();
    }

    private function loadDbConfigOverrides(): void
    {
        try {
            if (!$this->db->tableExists('system_config')) {
                return;
            }
            $rows = $this->db->select("SELECT config_key, config_value, config_type FROM system_config");
            foreach ($rows as $row) {
                $value = $row['config_value'];
                switch ($row['config_type']) {
                    case 'integer':
                        $value = (int) $value;
                        break;
                    case 'float':
                        $value = (float) $value;
                        break;
                    case 'boolean':
                        $value = in_array(strtolower((string) $value), ['1', 'true', 'yes']);
                        break;
                    case 'json':
                        $value = json_decode($value, true) ?? $value;
                        break;
                }
                $this->config['db_overrides'][$row['config_key']] = $value;
            }
        } catch (\Throwable $e) {
            // DB might not be ready yet
        }
    }

    // Load all module bootstrap files
    private function loadModuleBootstraps(): void
    {
        $modulesDir = $this->basePath . '/app/Modules';
        if (!is_dir($modulesDir)) {
            return;
        }

        $pattern = $modulesDir . DIRECTORY_SEPARATOR . '*' . DIRECTORY_SEPARATOR . 'bootstrap.php';
        $bootstrapFiles = glob($pattern);
        if ($bootstrapFiles === false) {
            return;
        }

        sort($bootstrapFiles);

        foreach ($bootstrapFiles as $file) {
            try {
                require_once $file;
            } catch (\Throwable $e) {
                Logger::warning('Failed to load bootstrap: ' . $file . ' — ' . $e->getMessage());
            }
        }

        $this->loadModuleProviders($modulesDir);
    }

    private function loadModuleProviders(string $modulesDir): void
    {
        $pattern = $modulesDir . DIRECTORY_SEPARATOR . '*' . DIRECTORY_SEPARATOR . 'provides.php';
        $providerFiles = glob($pattern);
        if ($providerFiles === false) {
            return;
        }

        sort($providerFiles);

        foreach ($providerFiles as $file) {
            try {
                require_once $file;
            } catch (\Throwable $e) {
                Logger::warning('Failed to load provides: ' . $file . ' — ' . $e->getMessage());
            }
        }
    }

    // Load all module route files
    private function loadModuleRoutes(): void
    {
        $modulesDir = $this->basePath . '/app/Modules';
        if (!is_dir($modulesDir)) {
            return;
        }

        $pattern = $modulesDir . DIRECTORY_SEPARATOR . '*' . DIRECTORY_SEPARATOR . 'Routes.php';
        $routeFiles = glob($pattern);
        if ($routeFiles === false) {
            return;
        }

        sort($routeFiles);

        foreach ($routeFiles as $file) {
            try {
                $routes = require $file;
                if (is_array($routes)) {
                    foreach ($routes as $route) {
                        if (is_array($route) && count($route) >= 3) {
                            $this->router->addRoute(
                                $route[0],
                                $route[1],
                                $route[2],
                                $route[3] ?? [],
                                $route[4] ?? null
                            );
                        }
                    }
                }
            } catch (\Throwable $e) {
                Logger::warning('Failed to load routes: ' . $file . ' — ' . $e->getMessage());
            }
        }
    }

    private function loadModuleApiRoutes(): void
    {
        $modulesDir = $this->basePath . '/app/Modules';
        if (!is_dir($modulesDir)) {
            return;
        }

        $pattern = $modulesDir . DIRECTORY_SEPARATOR . '*' . DIRECTORY_SEPARATOR . 'ApiRoutes.php';
        $routeFiles = glob($pattern);
        if ($routeFiles === false) {
            return;
        }

        sort($routeFiles);

        foreach ($routeFiles as $file) {
            try {
                $routes = require $file;
                if (is_array($routes)) {
                    foreach ($routes as $route) {
                        if (is_array($route) && count($route) >= 3) {
                            $this->router->addRoute(
                                $route[0],
                                $route[1],
                                $route[2],
                                $route[3] ?? [],
                                $route[4] ?? null
                            );
                        }
                    }
                }
            } catch (\Throwable $e) {
                Logger::warning('Failed to load API routes: ' . $file . ' — ' . $e->getMessage());
            }
        }
    }

    private function registerServiceProviders(): void
    {
        $providers = [
            \App\Core\Providers\CoreServiceProvider::class,
        ];

        foreach ($providers as $providerClass) {
            $provider = new $providerClass();
            $provider->register($this);
        }

        foreach ($providers as $providerClass) {
            (new $providerClass())->boot($this);
        }
    }

    public function db(): Database
    {
        return $this->db;
    }

    /**
     * Set the database instance (used by CLI seeds that need App context).
     */
    public function setDb(Database $db): void
    {
        $this->db = $db;
    }

    public function session(): Session
    {
        return $this->session;
    }

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

    public function config(?string $key = null, $default = null)
    {
        if ($key === null) {
            return $this->config;
        }

        if (isset($this->config['db_overrides'][$key])) {
            return $this->config['db_overrides'][$key];
        }

        $parts = explode('.', $key);
        $value = $this->config;

        foreach ($parts as $part) {
            if (is_array($value) && array_key_exists($part, $value)) {
                $value = $value[$part];
            } else {
                return $default;
            }
        }

        return $value;
    }

    public function basePath(): string
    {
        return $this->basePath;
    }

    public function publicPath(): string
    {
        return $this->basePath . '/public';
    }

    public function storagePath(): string
    {
        return $this->basePath . '/storage';
    }

    public function isDebug(): bool
    {
        return (bool) $this->config('app.debug', false);
    }

    public function setCurrentEmployee(object $employee): void
    {
        $this->currentEmployee = $employee;
    }

    public function currentEmployee(): ?object
    {
        return $this->currentEmployee;
    }

    public function setCurrentBranch(?array $branch): void
    {
        $this->currentBranch = $branch;
    }

    public function currentBranch(): ?array
    {
        return $this->currentBranch;
    }

    public function bind(string $key, $value): void
    {
        $this->bindings[$key] = $value;
    }

    public function singleton(string $key, callable $factory): void
    {
        $this->factories[$key] = $factory;
        unset($this->bindings[$key]);
    }

    public function resolve(string $key, $default = null)
    {
        if (isset($this->bindings[$key])) {
            return $this->bindings[$key];
        }
        if (isset($this->factories[$key])) {
            $this->bindings[$key] = ($this->factories[$key])($this);
            return $this->bindings[$key];
        }
        return $default;
    }
}