<?php
declare(strict_types=1);

namespace App\Services;

use App\Core\Database;
use App\Repositories\UpdatePackageRegistryRepository;
use App\Services\Pipeline\PipelineOrchestrator;
use PDO;
use RuntimeException;
use Throwable;

final class UpdateValidationEngineService
{
    /**
     * @return array<string,mixed>
     */
    public function dashboard(int $selectedRunId = 0): array
    {
        $registry = new UpdatePackageRegistryRepository();
        $snapshot = $registry->snapshot();

        $warnings = (array) ($snapshot['warnings'] ?? []);
        $runs = $this->runs($warnings);
        $stepsByRun = $this->steps($warnings);
        $selectedRun = $selectedRunId > 0
            ? $this->runReport($selectedRunId, $warnings)
            : null;

        return [
            'packages' => (array) ($snapshot['packages'] ?? []),
            'runs' => $runs,
            'steps_by_run' => $stepsByRun,
            'selected_run' => $selectedRun,
            'generated_at' => date('Y-m-d H:i:s'),
            'warnings' => array_values(array_unique($warnings)),
            'runtime_database' => (string) ($snapshot['database'] ?? 'inconnue'),
            'runtime_package_count' => (int) ($snapshot['count'] ?? 0),
        ];
    }

    /**
     * @param array<int,string> $warnings
     * @return array<int,array<string,mixed>>
     */
    private function runs(array &$warnings): array
    {
        try {
            $statement = Database::connection()->query(
                "SELECT
                    id,
                    package_id,
                    package_name,
                    package_version,
                    archive_name,
                    run_type,
                    status,
                    decision,
                    score,
                    blocking_step,
                    duration_ms,
                    executed_by,
                    created_at,
                    finished_at
                 FROM system_package_pipeline_runs
                 ORDER BY id DESC
                 LIMIT 50"
            );

            $rows = $statement->fetchAll(PDO::FETCH_ASSOC) ?: [];
            $statement->closeCursor();

            return $rows;
        } catch (Throwable $error) {
            $warnings[] = 'Lecture des exécutions du pipeline indisponible.';
            $this->log('runs', $error);
            return [];
        }
    }

    /**
     * @param array<int,string> $warnings
     * @return array<int,array<int,array<string,mixed>>>
     */
    private function steps(array &$warnings): array
    {
        try {
            $statement = Database::connection()->query(
                "SELECT
                    id,
                    run_id,
                    step_code,
                    step_label,
                    step_order,
                    status,
                    decision,
                    score,
                    critical,
                    message,
                    issues_json,
                    warnings_json,
                    details_json,
                    duration_ms,
                    created_at
                 FROM system_package_pipeline_steps
                 ORDER BY run_id DESC, step_order ASC, id ASC
                 LIMIT 300"
            );

            $stepsByRun = [];
            foreach ($statement->fetchAll(PDO::FETCH_ASSOC) ?: [] as $step) {
                $stepsByRun[(int) $step['run_id']][] = $this->normalizeStep($step);
            }
            $statement->closeCursor();

            return $stepsByRun;
        } catch (Throwable $error) {
            $warnings[] = 'Lecture des étapes du pipeline indisponible.';
            $this->log('steps', $error);
            return [];
        }
    }

    /**
     * @param array<int,string> $warnings
     * @return array<string,mixed>|null
     */
    private function runReport(int $runId, array &$warnings): ?array
    {
        try {
            $runStatement = Database::connection()->prepare(
                "SELECT
                    id,
                    package_id,
                    package_name,
                    package_version,
                    archive_name,
                    run_type,
                    status,
                    decision,
                    score,
                    blocking_step,
                    summary_json,
                    duration_ms,
                    executed_by,
                    created_at,
                    finished_at
                 FROM system_package_pipeline_runs
                 WHERE id = :id
                 LIMIT 1"
            );
            $runStatement->execute(['id' => $runId]);
            $run = $runStatement->fetch(PDO::FETCH_ASSOC);
            $runStatement->closeCursor();

            if (!is_array($run)) {
                $warnings[] = 'L’exécution demandée est introuvable.';
                return null;
            }

            $stepStatement = Database::connection()->prepare(
                "SELECT
                    id,
                    run_id,
                    step_code,
                    step_label,
                    step_order,
                    status,
                    decision,
                    score,
                    critical,
                    message,
                    issues_json,
                    warnings_json,
                    details_json,
                    duration_ms,
                    created_at
                 FROM system_package_pipeline_steps
                 WHERE run_id = :run_id
                 ORDER BY step_order ASC, id ASC"
            );
            $stepStatement->execute(['run_id' => $runId]);

            $steps = [];
            foreach ($stepStatement->fetchAll(PDO::FETCH_ASSOC) ?: [] as $step) {
                $steps[] = $this->normalizeStep($step);
            }
            $stepStatement->closeCursor();

            $run['summary'] = $this->decodeJson((string) ($run['summary_json'] ?? ''));
            unset($run['summary_json']);
            $run['steps'] = $steps;

            return $run;
        } catch (Throwable $error) {
            $warnings[] = 'Chargement du rapport détaillé impossible.';
            $this->log('run_report', $error);
            return null;
        }
    }

    /**
     * @param array<string,mixed> $step
     * @return array<string,mixed>
     */
    private function normalizeStep(array $step): array
    {
        $step['issues'] = $this->decodeList((string) ($step['issues_json'] ?? ''));
        $step['warnings'] = $this->decodeList((string) ($step['warnings_json'] ?? ''));
        $step['details'] = $this->decodeJson((string) ($step['details_json'] ?? ''));

        unset(
            $step['issues_json'],
            $step['warnings_json'],
            $step['details_json']
        );

        return $step;
    }

    /**
     * @return array<int,mixed>
     */
    private function decodeList(string $json): array
    {
        $value = json_decode($json, true);
        return is_array($value) ? array_values($value) : [];
    }

    /**
     * @return array<string,mixed>
     */
    private function decodeJson(string $json): array
    {
        $value = json_decode($json, true);
        return is_array($value) ? $value : [];
    }

    /**
     * @return array<string,mixed>
     */
    public function simulate(int $packageId, ?int $userId): array
    {
        $package = (new UpdatePackageRegistryRepository())->find($packageId);

        if ($package === null) {
            throw new RuntimeException('Package introuvable.');
        }

        $archive = (string) ($package['fichier_original'] ?? '');
        if ($archive === '') {
            throw new RuntimeException('Archive du package absente.');
        }

        return (new PipelineOrchestrator())->execute(
            BASE_PATH . '/storage/updates/packages/' . basename($archive),
            $packageId,
            $userId,
            'simulation',
            true
        );
    }

    private function log(string $section, Throwable $error): void
    {
        $directory = BASE_PATH . '/storage/logs';
        if (!is_dir($directory)) {
            @mkdir($directory, 0770, true);
        }

        $entry = json_encode([
            'at' => gmdate('c'),
            'event' => 'validation_center.error',
            'section' => $section,
            'type' => get_class($error),
            'message' => $error->getMessage(),
            'file' => $error->getFile(),
            'line' => $error->getLine(),
        ], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);

        if (is_string($entry)) {
            @file_put_contents(
                $directory . '/validation_center.log',
                $entry . PHP_EOL,
                FILE_APPEND | LOCK_EX
            );
        }
    }
}
