<?php

declare(strict_types=1);

namespace App\Command;

use App\Entity\IngestJob;
use App\Index\IndexMetaManager;
use App\Service\IngestJobService;
use App\Service\IngestOrchestrator;
use App\Tag\TagNdjsonExporter;
use App\Tag\TagVectorIndexBuilder;
use App\Tag\TagVectorIndexHealthService;
use App\Vector\VectorIndexHealthService;
use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;
use Symfony\Component\Process\Process;

#[AsCommand(
    name: 'mto:agent:system:rebuild',
    description: 'Hard rebuild: global reindex (chunks+vector), rebuild tag index, then reload vector service'
)]
final class SystemRebuildCommand extends Command
{
    public function __construct(
        private readonly IngestJobService $jobService,
        private readonly IngestOrchestrator $orchestrator,
        private readonly TagNdjsonExporter $tagExporter,
        private readonly TagVectorIndexBuilder $tagIndexBuilder,
        private readonly IndexMetaManager $metaManager,
        private readonly VectorIndexHealthService $health,
        private readonly TagVectorIndexHealthService $tagHealth,
        private readonly string $projectDir,
        private readonly string $vectorPythonBin = '.venv/bin/python',
        private readonly string $vectorControlScript = 'python/vector/vector_control.py',
        private readonly string $vectorHost = '0.0.0.0',
        private readonly int $vectorPort = 8090,
        private readonly int $vectorTimeoutSeconds = 600,
    ) {
        parent::__construct();
    }

    protected function configure(): void
    {
        $this
            ->addOption('hard', null, InputOption::VALUE_NONE, 'Required safety switch. Without --hard, the command aborts.')
            ->addOption('no-tags', null, InputOption::VALUE_NONE, 'Skip tag rebuild')
            ->addOption('no-reload', null, InputOption::VALUE_NONE, 'Skip vector service reload/start')
            ->addOption('no-health', null, InputOption::VALUE_NONE, 'Skip health check (not recommended)')
            ->addOption('dry-run', null, InputOption::VALUE_NONE, 'Simulate ingest steps (no writes)');
    }

    protected function execute(InputInterface $input, OutputInterface $output): int
    {
        $io = new SymfonyStyle($input, $output);

        if (!$input->getOption('hard')) {
            $io->error('Safety switch missing: you must pass --hard to run this command.');
            $io->writeln('Example: bin/console mto:agent:system:rebuild --hard');

            return Command::FAILURE;
        }

        $dryRun = (bool) $input->getOption('dry-run');

        $io->title('mto:agent:system:rebuild --hard');

        if (!$this->runGlobalReindex($io, $dryRun)) {
            return Command::FAILURE;
        }

        if (!$this->runTagRebuild($io, $input, $dryRun)) {
            return Command::FAILURE;
        }

        if (!$this->runVectorServiceReload($io, $input, $dryRun)) {
            return Command::FAILURE;
        }

        if (!$this->runHealthChecks($io, $input)) {
            return Command::FAILURE;
        }

        $io->success('System rebuild finished.');

        return Command::SUCCESS;
    }

    private function runGlobalReindex(SymfonyStyle $io, bool $dryRun): bool
    {
        $io->section('1/4 Global reindex (chunks + vector index)');

        $job = $this->jobService->startJob(
            IngestJob::TYPE_GLOBAL_REINDEX,
            null,
            null,
            null,
            null,
            IngestJob::STATUS_QUEUED
        );

        try {
            $this->orchestrator->runExistingJob($job, $dryRun);
            $io->success('Global reindex completed.');

            return true;
        } catch (\Throwable $e) {
            $io->error('Global reindex failed: ' . $e->getMessage());

            return false;
        }
    }

    private function runTagRebuild(SymfonyStyle $io, InputInterface $input, bool $dryRun): bool
    {
        if ((bool) $input->getOption('no-tags')) {
            $io->section('2/4 Tag rebuild');
            $io->note('Skipped due to --no-tags.');

            return true;
        }

        $io->section('2/4 Tag rebuild (tags.ndjson + vector_tags.index)');

        if ($dryRun) {
            $io->note('dry-run enabled: tag rebuild skipped (would export + build tag index).');

            return true;
        }

        try {
            $export = $this->tagExporter->export();

            $io->writeln('<info>Exported tags.ndjson</info>');
            $io->writeln('Path:  ' . (string) $export['path']);
            $io->writeln('Tags:  ' . (string) $export['tags']);
            $io->writeln('Lines: ' . (string) $export['lines']);
            $io->writeln('Bytes: ' . (string) $export['bytes']);

            $this->tagIndexBuilder->build();

            $io->success('Tag rebuild completed.');

            return true;
        } catch (\Throwable $e) {
            $io->error('Tag rebuild failed: ' . $e->getMessage());

            return false;
        }
    }

    private function runVectorServiceReload(SymfonyStyle $io, InputInterface $input, bool $dryRun): bool
    {
        if ((bool) $input->getOption('no-reload')) {
            $io->section('3/4 Vector service reload');
            $io->note('Skipped due to --no-reload.');

            return true;
        }

        $io->section('3/4 Vector service reload (uvicorn)');

        if ($dryRun) {
            $io->note('dry-run enabled: service reload skipped.');

            return true;
        }

        $cmd = [
            $this->vectorPythonBin,
            $this->vectorControlScript,
            '--install',
            '--start',
            '--reload',
            '--port', (string) $this->vectorPort,
            '--host', $this->vectorHost,
        ];

        $process = new Process($cmd, $this->projectDir);
        $process->setTimeout($this->vectorTimeoutSeconds);
        $process->run();

        $stdout = trim($process->getOutput());
        $stderr = trim($process->getErrorOutput());

        if ($stdout !== '') {
            $io->writeln($stdout);
        }

        if ($stderr !== '') {
            $io->writeln('<comment>' . $stderr . '</comment>');
        }

        if (!$process->isSuccessful()) {
            $io->error('Vector service reload failed (non-zero exit code).');

            return false;
        }

        $io->success('Vector service reloaded.');

        return true;
    }

    private function runHealthChecks(SymfonyStyle $io, InputInterface $input): bool
    {
        if ((bool) $input->getOption('no-health')) {
            $io->section('4/4 Health check');
            $io->note('Skipped due to --no-health.');

            return true;
        }

        $io->section('4/4 Health check');

        try {
            $chunkReport = $this->health->check();
        } catch (\Throwable $e) {
            $io->error('Health check failed: ' . $e->getMessage());

            return false;
        }

        try {
            $tagReport = $this->tagHealth->check();
        } catch (\Throwable $e) {
            $io->error('Tag health check failed: ' . $e->getMessage());

            return false;
        }

        $this->renderChunkHealth($io, $chunkReport);
        $this->renderTagHealth($io, $tagReport);

        if (!$this->isHealthOk((string) ($chunkReport['status'] ?? 'UNKNOWN'))) {
            $io->error('Chunk health check not OK: ' . (string) ($chunkReport['status'] ?? 'UNKNOWN'));

            return false;
        }

        if (!$this->isHealthOk((string) ($tagReport['status'] ?? 'UNKNOWN'))) {
            $io->error('Tag health check not OK: ' . (string) ($tagReport['status'] ?? 'UNKNOWN'));

            return false;
        }

        $io->success('Health check OK.');

        return true;
    }

    private function renderChunkHealth(SymfonyStyle $io, array $report): void
    {
        $io->definitionList(
            ['ndjson_exists' => !empty($report['ndjson_exists']) ? 'yes' : 'no'],
            ['ndjson_chunk_count' => (string) ($report['ndjson_chunk_count'] ?? 0)],
            ['vector_exists' => !empty($report['vector_exists']) ? 'yes' : 'no'],
            ['meta_exists' => !empty($report['meta_exists']) ? 'yes' : 'no'],
            ['vector_chunk_count' => (string) ($report['vector_chunk_count'] ?? 0)],
            ['status' => (string) ($report['status'] ?? 'UNKNOWN')],
        );
    }

    private function renderTagHealth(SymfonyStyle $io, array $report): void
    {
        $io->definitionList(
            ['tags_ndjson_exists' => !empty($report['tags_ndjson_exists']) ? 'yes' : 'no'],
            ['tags_ndjson_count' => (string) ($report['tags_ndjson_count'] ?? 0)],
            ['tag_vector_exists' => !empty($report['vector_exists']) ? 'yes' : 'no'],
            ['tag_meta_exists' => !empty($report['meta_exists']) ? 'yes' : 'no'],
            ['vector_tag_count' => (string) ($report['vector_tag_count'] ?? 0)],
            ['tags_with_active_document_ids' => (string) ($report['tags_with_active_document_ids'] ?? 0)],
            ['meta_valid' => !empty($report['meta_valid']) ? 'yes' : 'no'],
            ['status' => (string) ($report['status'] ?? 'UNKNOWN')],
        );
    }

    private function isHealthOk(string $status): bool
    {
        return in_array($status, ['OK', 'OK_EMPTY'], true);
    }
}