<?php

namespace Openguru\OpenCartFramework;

use Closure;
use Dotenv\Dotenv;
use InvalidArgumentException;
use Openguru\OpenCartFramework\Config\Settings;
use Openguru\OpenCartFramework\Container\Container;
use Openguru\OpenCartFramework\Http\Request;
use Openguru\OpenCartFramework\MaintenanceTasks\MaintenanceTasksService;
use Openguru\OpenCartFramework\MaintenanceTasks\MaintenanceTasksServiceProvider;
use Openguru\OpenCartFramework\Migrations\MigrationsServiceProvider;
use Openguru\OpenCartFramework\Router\Router;
use Openguru\OpenCartFramework\Support\ExecutionTimeProfiler;
use Psr\Log\LoggerAwareInterface;
use Psr\Log\LoggerInterface;
use Symfony\Component\HttpFoundation\Response;

class Application extends Container implements LoggerAwareInterface
{
    private static Application $instance;
    private static array $events = [];
    private array $serviceProviders = [];
    private array $middlewareStack = [];
    private array $routes = [];

    private LoggerInterface $logger;

    /**
     * @var ExecutionTimeProfiler
     */
    private ExecutionTimeProfiler $profiler;

    public static function getInstance(): Application
    {
        return static::$instance;
    }

    private function bootKernelServices(): void
    {
        $this->singleton(ExecutionTimeProfiler::class, function () {
            return new ExecutionTimeProfiler();
        });

        $this->profiler = $this->get(ExecutionTimeProfiler::class);
        $this->profiler->start();

        $this->singleton(Container::class, function (Container $container) {
            return $container;
        });

        $this->singleton(LoggerInterface::class, fn() => $this->logger);

        $this->singleton(Settings::class, function (Container $container) {
            return new Settings($container->getConfigValue());
        });

        $this->loadEnvironmentVariables();

        $errorHandler = new ErrorHandler(
            $this->get(LoggerInterface::class),
            $this,
        );

        $errorHandler->register();
    }

    public function boot(): Application
    {
        $this->bootKernelServices();

        $this->initializeEventDispatcher(static::$events);

        DependencyRegistration::register($this, $this->serviceProviders);

        static::$instance = $this;

        $this->profiler->addCheckpoint('Bootstrap Application');

        return $this;
    }

    public function handleRequest(Request $request): Response
    {
        $this->bind(Request::class, function () use ($request) {
            return $request;
        });

        $router = $this->get(Router::class);
        $apiAction = $request->get('api_action');
        $action = $router->resolve($apiAction);

        [$controller, $method] = $action;

        if (! class_exists($controller) || ! method_exists($controller, $method)) {
            throw new InvalidArgumentException('Invalid action: ' . $controller . '->' . $method);
        }

        $this->profiler->addCheckpoint('Handle Middlewares.');

        $next = fn($req) => $this->call($controller, $method);

        foreach (array_reverse($this->middlewareStack) as $class) {
            $instance = $this->get($class);
            $next = static fn($req) => $instance->handle($req, $next);
        }

        $response = $next($request);

        $this->profiler->addCheckpoint('Handle HTTP request.');

        return $response;
    }

    public function bootAndHandleRequest(): void
    {
        $this->boot();

        $request = Request::createFromGlobals();
        $response = $this->handleRequest($request);
        $response->send();
    }

    public function withServiceProviders(array $serviceProviders): Application
    {
        $this->serviceProviders = $serviceProviders;

        return $this;
    }

    public function withMiddlewares(array $middlewares): Application
    {
        $this->middlewareStack = $middlewares;

        return $this;
    }

    public function withLogger(Closure $closure): Application
    {
        $this->setLogger($closure($this));

        return $this;
    }

    public function withRoutes(callable $callback): Application
    {
        $this->routes = $callback();

        return $this;
    }

    public function getRoutes(): array
    {
        return $this->routes;
    }

    public function runMaintenanceTasks(): void
    {
        $this->serviceProviders = array_merge($this->serviceProviders, [
            MigrationsServiceProvider::class,
            MaintenanceTasksServiceProvider::class,
        ]);

        $this->boot();

        $this->get(MaintenanceTasksService::class)->run();
    }

    public function setLogger(LoggerInterface $logger): void
    {
        $this->logger = $logger;
    }

    /**
     * Loads environment variables from .env files.
     * First loads internal .env (from phar or module directory),
     * then external .env (if exists next to phar/directory) which overrides values.
     */
    private function loadEnvironmentVariables(): void
    {
        if (!defined('BP_PHAR_BASE_PATH') || !defined('BP_REAL_BASE_PATH')) {
            $dotenv = Dotenv::createMutable(__DIR__ . '/../');
            $dotenv->load();
            return;
        }

        $internalEnvPath = BP_PHAR_BASE_PATH;
        if (is_readable($internalEnvPath . '/.env')) {
            $dotenv = Dotenv::createImmutable($internalEnvPath);
            $dotenv->load();
        }

        $externalEnvPath = BP_REAL_BASE_PATH;
        if (is_readable($externalEnvPath . '/.env')) {
            $dotenv = Dotenv::createMutable($externalEnvPath);
            $dotenv->load();
        }
    }
}
