#!/usr/bin/env php
<?php

declare(strict_types=1);

use App\App\Container;
use App\Application\Jobs\RecomputeScoresJob;
use App\Domain\Auth\Role;
use App\Domain\Auth\TokenHasher;
use App\Domain\Auth\TokenIssuer;
use App\Domain\Auth\TokenKind;
use App\Domain\Time\Clock;
use App\Infrastructure\Auth\ServiceTokenBootstrap;
use App\Infrastructure\Auth\TokenRecord;
use App\Infrastructure\Auth\TokenRepository;
use App\Infrastructure\Jobs\JobLockRepository;
use App\Infrastructure\Jobs\JobRegistry;
use App\Infrastructure\Jobs\JobRunner;
use App\Infrastructure\Jobs\JobRunRepository;

require __DIR__ . '/../vendor/autoload.php';

$argv = $_SERVER['argv'] ?? [];
$command = $argv[1] ?? null;

$phinxBin = __DIR__ . '/../vendor/bin/phinx';
$phinxConfig = __DIR__ . '/../config/phinx.php';

$run = static function (string $phinxCommand) use ($phinxBin, $phinxConfig): never {
    $cmd = sprintf(
        '%s %s --configuration=%s',
        escapeshellarg($phinxBin),
        $phinxCommand,
        escapeshellarg($phinxConfig)
    );
    passthru($cmd, $exitCode);
    exit($exitCode);
};

/**
 * @param array<int, string> $argv
 */
$flag = static function (array $argv, string $name): ?string {
    $prefix = '--' . $name . '=';
    foreach ($argv as $arg) {
        if (str_starts_with($arg, $prefix)) {
            return substr($arg, strlen($prefix));
        }
    }

    return null;
};

/**
 * @param array<int, string> $argv
 */
$hasFlag = static function (array $argv, string $name): bool {
    return in_array('--' . $name, $argv, true);
};

switch ($command) {
    case 'db:migrate':
        $run('migrate');
        // no break
    case 'db:rollback':
        $run('rollback');
        // no break
    case 'db:seed':
        $run('seed:run');
        // no break

    case 'auth:bootstrap-service-token':
        $container = Container::build();
        /** @var ServiceTokenBootstrap $boot */
        $boot = $container->get(ServiceTokenBootstrap::class);
        /** @var string $rawToken */
        $rawToken = $container->get('settings.ui_service_token');
        $boot->bootstrap($rawToken);
        exit(0);

    case 'auth:create-token':
        $kindArg = $flag($argv, 'kind') ?? '';
        $roleArg = $flag($argv, 'role');
        $quiet = $hasFlag($argv, 'quiet');

        $kind = TokenKind::tryFrom($kindArg);
        if ($kind === null) {
            fwrite(STDERR, "Unknown --kind: {$kindArg}. Admin tokens are the only kind supported here.\n");
            exit(1);
        }
        if ($kind === TokenKind::Service) {
            fwrite(STDERR, "Refusing to create a service token via this command. Use UI_SERVICE_TOKEN + auth:bootstrap-service-token.\n");
            exit(1);
        }
        if ($kind === TokenKind::Reporter || $kind === TokenKind::Consumer) {
            fwrite(STDERR, "Reporter/consumer tokens are bound to records and are issued via M04 endpoints, not this CLI.\n");
            exit(1);
        }

        // kind=admin from here on.
        $role = $roleArg !== null ? Role::tryFrom(strtolower($roleArg)) : null;
        if ($role === null) {
            fwrite(STDERR, "Admin tokens require --role=viewer|operator|admin.\n");
            exit(1);
        }

        $container = Container::build();
        /** @var TokenIssuer $issuer */
        $issuer = $container->get(TokenIssuer::class);
        /** @var TokenHasher $hasher */
        $hasher = $container->get(TokenHasher::class);
        /** @var TokenRepository $repo */
        $repo = $container->get(TokenRepository::class);

        $raw = $issuer->issue(TokenKind::Admin);
        $hash = $hasher->hash($raw);
        $repo->create(new TokenRecord(
            id: null,
            kind: TokenKind::Admin,
            hash: $hash,
            prefix: substr($raw, 0, 8),
            reporterId: null,
            consumerId: null,
            role: $role,
            expiresAt: null,
            revokedAt: null,
            lastUsedAt: null,
        ));

        if ($quiet) {
            fwrite(STDOUT, $raw);
        } else {
            fwrite(STDOUT, $raw . "\n");
            fwrite(STDERR, "Created admin token (role={$role->value}). The token is only shown once.\n");
        }
        exit(0);

    case 'jobs:run':
        $jobName = $argv[2] ?? null;
        if ($jobName === null || str_starts_with($jobName, '--')) {
            fwrite(STDERR, "Usage: jobs:run <job-name> [--full] [--max-rows=N]\n");
            exit(1);
        }
        $container = Container::build();
        /** @var JobRegistry $registry */
        $registry = $container->get(JobRegistry::class);
        if (!$registry->has($jobName)) {
            fwrite(STDERR, "Unknown job: {$jobName}\n");
            exit(1);
        }
        /** @var JobRunner $runner */
        $runner = $container->get(JobRunner::class);

        $params = [];
        if ($hasFlag($argv, 'full')) {
            $params['full'] = true;
        }
        $maxRowsArg = $flag($argv, 'max-rows');
        if ($maxRowsArg !== null && ctype_digit($maxRowsArg)) {
            $params['max_rows'] = (int) $maxRowsArg;
        }

        $outcome = $runner->run($registry->get($jobName), $params, 'manual');
        fwrite(STDOUT, json_encode($outcome->toArray(), JSON_PRETTY_PRINT) . "\n");
        exit($outcome->status->value === 'failure' ? 1 : 0);

    case 'jobs:status':
        $container = Container::build();
        /** @var JobRegistry $registry */
        $registry = $container->get(JobRegistry::class);
        /** @var JobRunRepository $runs */
        $runs = $container->get(JobRunRepository::class);
        /** @var JobLockRepository $locks */
        $locks = $container->get(JobLockRepository::class);
        /** @var Clock $clock */
        $clock = $container->get(Clock::class);

        $now = $clock->now();
        $latest = $runs->latestPerJob();
        $rows = [];
        foreach ($registry->all() as $name => $job) {
            $row = $latest[$name] ?? null;
            $finishedAt = $row['finished_at'] ?? null;
            $overdue = $row === null
                || ($finishedAt instanceof DateTimeImmutable
                    && ($now->getTimestamp() - $finishedAt->getTimestamp()) > $job->defaultIntervalSeconds());
            $rows[$name] = [
                'name' => $name,
                'default_interval_seconds' => $job->defaultIntervalSeconds(),
                'overdue' => $overdue,
                'lock' => $locks->status($name),
                'last_run' => $row === null ? null : [
                    'id' => $row['id'],
                    'status' => $row['status'],
                    'items_processed' => $row['items_processed'],
                    'triggered_by' => $row['triggered_by'],
                    'finished_at' => $finishedAt instanceof DateTimeImmutable
                        ? $finishedAt->format('Y-m-d\TH:i:s\Z')
                        : null,
                ],
            ];
        }
        fwrite(STDOUT, json_encode([
            'now' => $now->format('Y-m-d\TH:i:s\Z'),
            'jobs' => $rows,
        ], JSON_PRETTY_PRINT) . "\n");
        exit(0);

    case 'scores:rebuild':
        $container = Container::build();
        /** @var JobRegistry $registry */
        $registry = $container->get(JobRegistry::class);
        /** @var JobRunner $runner */
        $runner = $container->get(JobRunner::class);

        $outcome = $runner->run(
            $registry->get(RecomputeScoresJob::NAME),
            ['full' => true],
            'manual',
        );
        fwrite(STDOUT, json_encode($outcome->toArray(), JSON_PRETTY_PRINT) . "\n");
        exit($outcome->status->value === 'failure' ? 1 : 0);

    case null:
    case '--help':
    case '-h':
        fwrite(STDOUT, <<<TXT
            Usage: console <command>

            Commands:
              db:migrate                          Run Phinx migrations
              db:rollback                         Roll back the most recent migration
              db:seed                             Run all seeders idempotently
              auth:bootstrap-service-token        Provision UI_SERVICE_TOKEN row in api_tokens
              auth:create-token --kind=admin --role=admin|operator|viewer [--quiet]
                                                  Create an admin token; raw token printed to stdout
              jobs:run <name> [--full] [--max-rows=N]
                                                  Invoke a registered job directly. Bypasses HTTP.
              jobs:status                         Print latest run + lock state for every job
              scores:rebuild                      Alias for `jobs:run recompute-scores --full`

            TXT);
        exit(0);

    default:
        fwrite(STDERR, "Unknown command: {$command}\n");
        exit(1);
}
