<?php

declare(strict_types=1);

namespace App\App;

use App\ApiClient\AdminClient;
use App\ApiClient\ApiClient;
use App\ApiClient\ApiHealth;
use App\ApiClient\AuthClient;
use App\Auth\FileThrottleStore;
use App\Auth\JumbojettOidcAuthenticator;
use App\Auth\LocalLoginController;
use App\Auth\LoginThrottle;
use App\Auth\LogoutController;
use App\Auth\OidcAuthenticator;
use App\Auth\OidcController;
use App\Auth\SessionManager;
use App\Auth\ThrottleStore;
use App\Controllers\AllowlistController;
use App\Controllers\AuditController;
use App\Controllers\CategoriesController;
use App\Controllers\ConsumersController;
use App\Controllers\DashboardController;
use App\Controllers\HealthzController;
use App\Controllers\HomeController;
use App\Controllers\IpsController;
use App\Controllers\ManualBlocksController;
use App\Controllers\MeController;
use App\Controllers\NoAccessController;
use App\Controllers\PoliciesController;
use App\Controllers\ReportersController;
use App\Controllers\SearchController;
use App\Controllers\SettingsController;
use App\Controllers\TokensController;
use App\Controllers\UsersController;
use App\Http\AuthRequiredMiddleware;
use App\Http\CspMiddleware;
use App\Http\CsrfMiddleware;
use App\Http\JsonExceptionHandler;
use App\Http\SessionMiddleware;
use App\Http\TwigGlobalsMiddleware;

use function DI\autowire;

use DI\ContainerBuilder;

use function DI\factory;

use GuzzleHttp\Client as GuzzleClient;
use GuzzleHttp\ClientInterface as GuzzleClientInterface;
use Monolog\Formatter\JsonFormatter;
use Monolog\Handler\StreamHandler;
use Monolog\Logger;
use Psr\Container\ContainerInterface;
use Psr\Http\Message\ResponseFactoryInterface;
use Psr\Log\LoggerInterface;
use Slim\Psr7\Factory\ResponseFactory;
use Slim\Views\Twig;

/**
 * Builds the UI's DI container. UI-side; no DB. Everything that talks
 * to the api goes through `ApiClient` / its subclients.
 */
final class Container
{
    /**
     * @param array<string, mixed> $settings
     */
    public static function build(array $settings): ContainerInterface
    {
        $builder = new ContainerBuilder();
        $builder->useAutowiring(true);
        $builder->addDefinitions([
            'settings' => $settings,
            'settings.app_env' => $settings['app_env'] ?? 'production',
            'settings.is_dev' => ($settings['app_env'] ?? 'production') === 'development',
            'settings.log_level' => $settings['log_level'] ?? \Monolog\Level::Info,
            'settings.api_base_url' => (string) ($settings['api_base_url'] ?? ''),
            'settings.api_timeout' => (float) ($settings['api_timeout_seconds'] ?? 5.0),
            'settings.ui_service_token' => (string) ($settings['ui_service_token'] ?? ''),
            'settings.oidc_enabled' => (bool) ($settings['oidc_enabled'] ?? false),
            'settings.oidc_issuer' => (string) ($settings['oidc_issuer'] ?? ''),
            'settings.oidc_client_id' => (string) ($settings['oidc_client_id'] ?? ''),
            'settings.oidc_client_secret' => (string) ($settings['oidc_client_secret'] ?? ''),
            'settings.oidc_redirect_uri' => (string) ($settings['oidc_redirect_uri'] ?? ''),
            'settings.local_admin_enabled' => (bool) ($settings['local_admin_enabled'] ?? false),
            'settings.local_admin_username' => (string) ($settings['local_admin_username'] ?? ''),
            'settings.local_admin_password_hash' => (string) ($settings['local_admin_password_hash'] ?? ''),
            'settings.session_idle' => (int) ($settings['session_idle_seconds'] ?? 28800),
            'settings.session_absolute' => (int) ($settings['session_absolute_seconds'] ?? 86400),
            'settings.session_revalidate_seconds' => (int) ($settings['session_revalidate_seconds'] ?? 300),
            'settings.login_throttle_path' => (string) ($settings['login_throttle_path']
                ?? sys_get_temp_dir() . '/irdb_login_throttle.json'),
            'settings.geoip_provider' => strtolower((string) ($settings['geoip_provider'] ?? 'dbip')),
            'settings.ui_locale' => trim((string) ($settings['ui_locale'] ?? '')),

            LoggerInterface::class => factory(static function (ContainerInterface $c): LoggerInterface {
                $logger = new Logger('ui');
                /** @var \Monolog\Level $level */
                $level = $c->get('settings.log_level');
                $handler = new StreamHandler('php://stdout', $level);
                $handler->setFormatter(new JsonFormatter());
                $logger->pushHandler($handler);
                $logger->pushProcessor(new \App\Logging\SecretScrubbingProcessor());

                return $logger;
            }),

            ResponseFactoryInterface::class => autowire(ResponseFactory::class),

            Twig::class => factory(static function (ContainerInterface $c): Twig {
                /** @var bool $isDev */
                $isDev = $c->get('settings.is_dev');

                $twig = Twig::create(__DIR__ . '/../../resources/views', [
                    'cache' => false,
                    'auto_reload' => $isDev,
                    'strict_variables' => false,
                    // SEC_REVIEW F63: pin the autoescape strategy to
                    // `'html'` instead of relying on the Twig default.
                    // The current major (Twig 3) defaults to `'html'`,
                    // but a future major bump or a Slim-twig wrapper
                    // change could silently flip it; an explicit pin
                    // means a regression in the default would surface
                    // as a build-time error (Twig refuses an unknown
                    // strategy name) rather than as un-escaped output.
                    'autoescape' => 'html',
                ]);
                $twig->getEnvironment()->addFunction(
                    new \Twig\TwigFunction('flag_emoji', static function (mixed $code): string {
                        if (!is_string($code)) {
                            return '';
                        }
                        $code = strtoupper(trim($code));
                        if (strlen($code) !== 2 || !ctype_alpha($code)) {
                            return '';
                        }
                        $a = mb_chr(0x1F1E6 + (ord($code[0]) - ord('A')), 'UTF-8');
                        $b = mb_chr(0x1F1E6 + (ord($code[1]) - ord('A')), 'UTF-8');

                        return $a . $b;
                    })
                );

                return $twig;
            }),

            SessionManager::class => factory(static function (ContainerInterface $c): SessionManager {
                /** @var bool $isDev */
                $isDev = $c->get('settings.is_dev');
                /** @var int $idle */
                $idle = $c->get('settings.session_idle');
                /** @var int $absolute */
                $absolute = $c->get('settings.session_absolute');

                return new SessionManager(
                    secureCookie: !$isDev,
                    idleSeconds: $idle,
                    absoluteSeconds: $absolute,
                );
            }),

            ApiHealth::class => factory(static fn (): ApiHealth => new ApiHealth()),

            GuzzleClientInterface::class => factory(static function (ContainerInterface $c): GuzzleClientInterface {
                /** @var string $base */
                $base = $c->get('settings.api_base_url');
                /** @var float $timeout */
                $timeout = $c->get('settings.api_timeout');

                return new GuzzleClient([
                    'base_uri' => $base,
                    'timeout' => $timeout,
                    'connect_timeout' => $timeout,
                    'http_errors' => false,
                ]);
            }),

            ApiClient::class => factory(static function (ContainerInterface $c): ApiClient {
                /** @var GuzzleClientInterface $http */
                $http = $c->get(GuzzleClientInterface::class);
                /** @var string $token */
                $token = $c->get('settings.ui_service_token');
                /** @var ApiHealth $health */
                $health = $c->get(ApiHealth::class);
                /** @var LoggerInterface $logger */
                $logger = $c->get(LoggerInterface::class);

                return new ApiClient($http, $token, $health, $logger);
            }),
            AuthClient::class => autowire(),
            AdminClient::class => autowire(),

            OidcAuthenticator::class => factory(static function (ContainerInterface $c): OidcAuthenticator {
                /** @var string $issuer */
                $issuer = $c->get('settings.oidc_issuer');
                /** @var string $clientId */
                $clientId = $c->get('settings.oidc_client_id');
                /** @var string $clientSecret */
                $clientSecret = $c->get('settings.oidc_client_secret');
                /** @var string $redirectUri */
                $redirectUri = $c->get('settings.oidc_redirect_uri');

                return new JumbojettOidcAuthenticator($issuer, $clientId, $clientSecret, $redirectUri);
            }),

            // Middlewares — autowire works directly.
            SessionMiddleware::class => autowire(),
            CsrfMiddleware::class => autowire(),
            CspMiddleware::class => autowire(),
            AuthRequiredMiddleware::class => factory(static function (ContainerInterface $c): AuthRequiredMiddleware {
                /** @var SessionManager $sessions */
                $sessions = $c->get(SessionManager::class);
                /** @var \Psr\Http\Message\ResponseFactoryInterface $rf */
                $rf = $c->get(ResponseFactoryInterface::class);
                /** @var AdminClient $admin */
                $admin = $c->get(AdminClient::class);
                /** @var LoggerInterface $logger */
                $logger = $c->get(LoggerInterface::class);

                return new AuthRequiredMiddleware(
                    $sessions,
                    $rf,
                    $admin,
                    $logger,
                    (int) $c->get('settings.session_revalidate_seconds'),
                );
            }),
            TwigGlobalsMiddleware::class => factory(static function (ContainerInterface $c): TwigGlobalsMiddleware {
                /** @var Twig $twig */
                $twig = $c->get(Twig::class);
                /** @var SessionManager $sessions */
                $sessions = $c->get(SessionManager::class);

                return new TwigGlobalsMiddleware($twig, $sessions, [
                    'oidc_enabled' => (bool) $c->get('settings.oidc_enabled'),
                    'local_admin_enabled' => (bool) $c->get('settings.local_admin_enabled'),
                    'app_env' => (string) $c->get('settings.app_env'),
                    'geoip_provider' => (string) $c->get('settings.geoip_provider'),
                    'ui_locale_fallback' => (string) $c->get('settings.ui_locale'),
                ]);
            }),

            JsonExceptionHandler::class => factory(static function (ContainerInterface $c): JsonExceptionHandler {
                /** @var Twig $twig */
                $twig = $c->get(Twig::class);
                /** @var ResponseFactoryInterface $rf */
                $rf = $c->get(ResponseFactoryInterface::class);
                /** @var LoggerInterface $logger */
                $logger = $c->get(LoggerInterface::class);

                return new JsonExceptionHandler($twig, $rf, $logger, (bool) $c->get('settings.is_dev'));
            }),

            // Controllers
            HomeController::class => autowire(),
            HealthzController::class => autowire(),
            MeController::class => autowire(),
            NoAccessController::class => autowire(),
            LogoutController::class => autowire(),
            DashboardController::class => autowire(),
            IpsController::class => autowire(),
            ManualBlocksController::class => autowire(),
            AllowlistController::class => autowire(),
            PoliciesController::class => autowire(),
            ReportersController::class => autowire(),
            ConsumersController::class => autowire(),
            TokensController::class => autowire(),
            CategoriesController::class => autowire(),
            UsersController::class => autowire(),
            AuditController::class => autowire(),
            SearchController::class => autowire(),
            SettingsController::class => autowire(),

            ThrottleStore::class => factory(static function (ContainerInterface $c): ThrottleStore {
                /** @var LoggerInterface $logger */
                $logger = $c->get(LoggerInterface::class);
                $path = (string) $c->get('settings.login_throttle_path');

                return new FileThrottleStore($path, $logger);
            }),

            LoginThrottle::class => factory(static function (ContainerInterface $c): LoginThrottle {
                /** @var LoggerInterface $logger */
                $logger = $c->get(LoggerInterface::class);
                /** @var ThrottleStore $store */
                $store = $c->get(ThrottleStore::class);

                return new LoginThrottle($logger, null, $store);
            }),

            LocalLoginController::class => factory(static function (ContainerInterface $c): LocalLoginController {
                /** @var Twig $twig */
                $twig = $c->get(Twig::class);
                /** @var SessionManager $sessions */
                $sessions = $c->get(SessionManager::class);
                /** @var AuthClient $auth */
                $auth = $c->get(AuthClient::class);
                /** @var LoginThrottle $throttle */
                $throttle = $c->get(LoginThrottle::class);
                /** @var ResponseFactoryInterface $rf */
                $rf = $c->get(ResponseFactoryInterface::class);
                /** @var LoggerInterface $logger */
                $logger = $c->get(LoggerInterface::class);

                return new LocalLoginController(
                    $twig,
                    $sessions,
                    $auth,
                    $throttle,
                    $rf,
                    $logger,
                    (bool) $c->get('settings.local_admin_enabled'),
                    (bool) $c->get('settings.oidc_enabled'),
                    (string) $c->get('settings.local_admin_username'),
                    (string) $c->get('settings.local_admin_password_hash'),
                );
            }),

            OidcController::class => factory(static function (ContainerInterface $c): OidcController {
                /** @var OidcAuthenticator $authenticator */
                $authenticator = $c->get(OidcAuthenticator::class);
                /** @var AuthClient $auth */
                $auth = $c->get(AuthClient::class);
                /** @var SessionManager $sessions */
                $sessions = $c->get(SessionManager::class);
                /** @var LoggerInterface $logger */
                $logger = $c->get(LoggerInterface::class);

                return new OidcController($authenticator, $auth, $sessions, $logger, (bool) $c->get('settings.oidc_enabled'));
            }),
        ]);

        return $builder->build();
    }
}
