<?php
declare(strict_types=1);

use TicketTimer\Controller\DiscoveryController;
use TicketTimer\Controller\DeploymentHealthController;
use TicketTimer\Controller\PageController;
use TicketTimer\Controller\ReferenceTimeController;
use TicketTimer\Controller\TargetSkewController;
use TicketTimer\Controller\TargetTimeController;
use TicketTimer\Core\LocaleCatalog;
use TicketTimer\Core\MarketCatalog;
use TicketTimer\Core\TargetCatalog;
use TicketTimer\Core\Request;
use TicketTimer\Core\Response;
use TicketTimer\Core\Router;
use TicketTimer\Core\SecurityHeaders;
use TicketTimer\Core\View;
use TicketTimer\Timing\ProbeTokenSigner;
use TicketTimer\Timing\ReferenceSessionEstimator;
use TicketTimer\Timing\TargetMeasurementSession;
use TicketTimer\Timing\TargetPromotionPolicy;
use TicketTimer\Timing\TargetSessionCodec;
use TicketTimer\Timing\Core\TimingConfig;
use TicketTimer\Timing\Core\TimingEngine;

$root = dirname(__DIR__, 2);
if (PHP_SAPI === 'cli-server') {
    $assetPath = __DIR__ . (parse_url($_SERVER['REQUEST_URI'] ?? '/', PHP_URL_PATH) ?: '/');
    if (is_file($assetPath)) return false;
}
require $root . '/vendor/autoload.php';
date_default_timezone_set('UTC');
ini_set('display_errors', '0');
ini_set('log_errors', '1');
header_remove('X-Powered-By');

$origin = rtrim(getenv('APP_ORIGIN') ?: 'https://time.util.monster.test', '/');
$appEnv = getenv('APP_ENV') ?: 'development';
$catalog = new LocaleCatalog(require $root . '/web/config/locales.php', $root . '/shared/i18n', $origin);
$spec = json_decode((string) file_get_contents($root . '/shared/timing-spec.json'), true, 512, JSON_THROW_ON_ERROR);
$targets = new TargetCatalog($root . '/shared/catalog/targets.json');
$pages = new PageController(
    new View($root . '/web/templates', $appEnv === 'production' ? $root . '/web/var/cache/twig' : false),
    $catalog,
    new MarketCatalog($root . '/shared/markets'),
    $targets,
    getenv('APP_STORE_URL') ?: null,
);
$probeSecret = getenv('APP_PROBE_SECRET') ?: '';
if ($probeSecret === '' && $appEnv === 'production') throw new RuntimeException('APP_PROBE_SECRET is required in production');
if ($probeSecret === '') $probeSecret = hash('sha256', __FILE__ . PHP_VERSION . php_uname('n'));
$probeSigner = new ProbeTokenSigner($probeSecret);
$referenceTime = new ReferenceTimeController((string) $spec['specVersion'], $probeSigner, new ReferenceSessionEstimator($probeSigner), $origin);
$timingConfig = TimingConfig::fromFile($root . '/shared/timing-spec.json');
$targetPromotionPolicy = TargetPromotionPolicy::fromEnvironment($root);
$targetTime = new TargetTimeController(
    new TargetMeasurementSession(
        $timingConfig,
        new TimingEngine($timingConfig),
        new TargetSessionCodec($probeSecret),
        $targetPromotionPolicy->allowlist(...),
        (string) $spec['specVersion'],
    ),
    $origin,
);
$deploymentHealth = new DeploymentHealthController(
    $root,
    $probeSecret,
    $appEnv === 'production' && getenv('APP_DEPLOY_HEALTH_ALLOWED') === '1',
    $targetPromotionPolicy->cacheHealth(...),
    (string) (getenv('TIMER_RELEASE_ACTIVATION_DIGEST') ?: ''),
);
$targetSkew = new TargetSkewController($root . '/web/var/target-skew-cache.json', $targets, $origin);
$discovery = new DiscoveryController($catalog, $targets);
$router = new Router();

foreach (['ko' => '', 'en' => '/en', 'ja' => '/ja'] as $locale => $prefix) {
    $router->add('GET', $prefix === '' ? '/' : $prefix, fn () => $pages->home($locale));
    $router->add('GET', $prefix . '/methodology', fn () => $pages->methodology($locale));
    $router->add('GET', $prefix . '/privacy', fn () => $pages->privacy($locale));
    $router->add('GET', $prefix . '/support', fn () => $pages->support($locale));
    $router->add('GET', $prefix . '/stopwatch', fn () => $pages->stopwatch($locale));
    $router->add('GET', $prefix . '/practice', fn () => $pages->practice($locale));
}
foreach ($targets->acquisitionRoutes() as $route) {
    $router->add('GET', $route['path'], fn () => $pages->target($route['locale'], (string) $route['target']['id']));
}
$router->add('GET', '/api/reference-time', fn () => $referenceTime->show(Request::fromGlobals()));
$router->add('POST', '/api/reference-time/estimate', fn () => $referenceTime->estimate(Request::fromGlobals()));
$router->add('OPTIONS', '/api/reference-time', fn () => $referenceTime->options());
$router->add('GET', '/api/target-time/session', fn () => $targetTime->availability(Request::fromGlobals()));
$router->add('POST', '/api/target-time/session', fn () => $targetTime->start(Request::fromGlobals()));
$router->add('POST', '/api/target-time/session/observe', fn () => $targetTime->observe(Request::fromGlobals()));
$router->add('GET', '/api/target-skew', fn () => $targetSkew->show(Request::fromGlobals()));
$router->add('GET', '/sitemap.xml', fn () => $discovery->sitemap());
$router->add('GET', '/robots.txt', fn () => $discovery->robots());
$router->add('GET', '/__deploy-health/dev03', fn () => $deploymentHealth->show());
$router->add('GET', '/__deploy-health/dev04', fn () => $deploymentHealth->show());

try {
    $request = Request::fromGlobals();
    $response = $router->dispatch($request);
    if ($response->status === 404 || $response->status === 405) {
        $context = $catalog->contextForPath($request->path());
        $allow = $response->headers['Allow'] ?? null;
        $response = $pages->error($context['locale'], $response->status);
        if ($allow !== null) $response = $response->withHeaders(['Allow' => $allow]);
    }
    $targetConnectOrigins = [];
    if (str_starts_with(strtolower($response->headers['Content-Type'] ?? ''), 'text/html')) {
        foreach ($targetPromotionPolicy->allowlist() as $promotion) {
            $parts = parse_url($promotion['clockEndpoint']);
            if (!is_array($parts) || !isset($parts['host'])) continue;
            $targetConnectOrigins[] = 'https://' . $parts['host'] . (($parts['port'] ?? 443) === 443 ? '' : ':' . $parts['port']);
        }
    }
    SecurityHeaders::apply($response, array_values(array_unique($targetConnectOrigins)))->send();
} catch (Throwable $error) {
    error_log((string) $error);
    (new Response('Internal server error', 500, ['Content-Type' => 'text/plain; charset=utf-8']))->send();
}
