<?php

declare(strict_types=1);

namespace App\Support\Baselines;

use App\Models\OperationRun;
use App\Support\ReasonTranslation\ReasonPresenter;
use App\Support\Ui\OperatorExplanation\CountDescriptor;
use App\Support\Ui\OperatorExplanation\ExplanationFamily;
use App\Support\Ui\OperatorExplanation\OperatorExplanationBuilder;
use App\Support\Ui\OperatorExplanation\OperatorExplanationPattern;
use App\Support\Ui\OperatorExplanation\TrustworthinessLevel;

final class BaselineCompareExplanationRegistry
{
    public function __construct(
        private readonly OperatorExplanationBuilder $builder,
        private readonly ReasonPresenter $reasonPresenter,
    ) {}

    public function trustLevelForRun(?OperationRun $run): string
    {
        if (! $run instanceof OperationRun) {
            return TrustworthinessLevel::Unusable->value;
        }

        $context = is_array($run->context) ? $run->context : [];
        $baselineCompare = is_array($context['baseline_compare'] ?? null) ? $context['baseline_compare'] : [];
        $coverage = is_array($baselineCompare['coverage'] ?? null) ? $baselineCompare['coverage'] : [];
        $evidenceGaps = is_array($baselineCompare['evidence_gaps'] ?? null) ? $baselineCompare['evidence_gaps'] : [];
        $reasonCode = is_string($baselineCompare['reason_code'] ?? null) ? trim((string) $baselineCompare['reason_code']) : null;
        $proof = is_bool($coverage['proof'] ?? null) ? (bool) $coverage['proof'] : null;
        $uncoveredTypes = is_array($coverage['uncovered_types'] ?? null) ? $coverage['uncovered_types'] : [];
        $evidenceGapCount = is_numeric($evidenceGaps['count'] ?? null) ? (int) $evidenceGaps['count'] : 0;

        if ($run->status !== 'completed' || $run->outcome === 'failed') {
            return TrustworthinessLevel::Unusable->value;
        }

        if ($proof === false || $reasonCode !== null) {
            return TrustworthinessLevel::DiagnosticOnly->value;
        }

        if ($uncoveredTypes !== [] || $evidenceGapCount > 0) {
            return TrustworthinessLevel::LimitedConfidence->value;
        }

        return TrustworthinessLevel::Trustworthy->value;
    }

    public function forStats(BaselineCompareStats $stats): OperatorExplanationPattern
    {
        $reason = $stats->reasonCode !== null
            ? $this->reasonPresenter->forArtifactTruth($stats->reasonCode, 'baseline_compare')
            : null;
        $isFailed = $stats->state === 'failed';
        $isInProgress = $stats->state === 'comparing';
        $hasCoverageWarnings = in_array($stats->coverageStatus, ['warning', 'unproven'], true);
        $hasEvidenceGaps = (int) ($stats->evidenceGapsCount ?? 0) > 0;
        $hasWarnings = $hasCoverageWarnings || $hasEvidenceGaps;
        $findingsCount = (int) ($stats->findingsCount ?? 0);
        $executionOutcome = match ($stats->state) {
            'comparing' => 'in_progress',
            'failed' => 'failed',
            default => $hasWarnings ? 'completed_with_follow_up' : 'completed',
        };
        $executionOutcomeLabel = match ($executionOutcome) {
            'in_progress' => 'In progress',
            'failed' => 'Execution failed',
            'completed_with_follow_up' => 'Completed with follow-up',
            default => 'Completed successfully',
        };
        $family = $reason?->absencePattern !== null
            ? ExplanationFamily::tryFrom(str_replace('true_no_result', 'no_issues_detected', $reason->absencePattern)) ?? null
            : null;
        $family ??= match (true) {
            $isInProgress => ExplanationFamily::InProgress,
            $isFailed => ExplanationFamily::BlockedPrerequisite,
            $stats->state === 'no_tenant',
            $stats->state === 'no_assignment',
            $stats->state === 'no_snapshot',
            $stats->state === 'idle' => ExplanationFamily::Unavailable,
            $findingsCount === 0 && ! $hasWarnings => ExplanationFamily::NoIssuesDetected,
            $hasWarnings => ExplanationFamily::CompletedButLimited,
            default => ExplanationFamily::TrustworthyResult,
        };
        $trustworthiness = $reason?->trustImpact !== null
            ? TrustworthinessLevel::tryFrom($reason->trustImpact)
            : null;
        $trustworthiness ??= match (true) {
            $family === ExplanationFamily::NoIssuesDetected,
            $family === ExplanationFamily::TrustworthyResult => TrustworthinessLevel::Trustworthy,
            $family === ExplanationFamily::CompletedButLimited => TrustworthinessLevel::LimitedConfidence,
            $family === ExplanationFamily::InProgress => TrustworthinessLevel::DiagnosticOnly,
            default => TrustworthinessLevel::Unusable,
        };
        $evaluationResult = $isFailed
            ? 'failed_result'
            : match ($family) {
                ExplanationFamily::TrustworthyResult => 'full_result',
                ExplanationFamily::NoIssuesDetected => 'no_result',
                ExplanationFamily::SuppressedOutput => 'suppressed_result',
                ExplanationFamily::MissingInput,
                ExplanationFamily::BlockedPrerequisite,
                ExplanationFamily::Unavailable,
                ExplanationFamily::InProgress => 'unavailable',
                ExplanationFamily::CompletedButLimited => $reason?->absencePattern === 'suppressed_output'
                    ? 'suppressed_result'
                    : 'incomplete_result',
            };
        $headline = match (true) {
            $isFailed => 'The comparison failed before it produced a usable result.',
            default => match ($family) {
                ExplanationFamily::NoIssuesDetected => 'The comparison found no actionable drift.',
                ExplanationFamily::TrustworthyResult => 'The comparison produced a usable drift result.',
                ExplanationFamily::CompletedButLimited => $findingsCount > 0
                    ? 'The comparison found drift, but the result needs caution.'
                    : 'The comparison finished, but the current result is not an all-clear.',
                ExplanationFamily::SuppressedOutput => 'The comparison finished, but normal result output was suppressed.',
                ExplanationFamily::MissingInput => 'The comparison could not produce a usable result because required inputs were missing.',
                ExplanationFamily::BlockedPrerequisite => 'The comparison could not produce a usable result because a prerequisite blocked it.',
                ExplanationFamily::InProgress => 'The comparison is still running.',
                ExplanationFamily::Unavailable => 'A usable comparison result is not currently available.',
            },
        };
        $coverageStatement = match (true) {
            $stats->state === 'idle' => 'No compare run has produced a result yet for this tenant.',
            $isFailed => 'The last compare run did not finish cleanly, so current counts should not be treated as decision-grade.',
            $stats->coverageStatus === 'unproven' => 'Coverage proof was unavailable, so suppressed output is possible even when the findings count is zero.',
            $stats->uncoveredTypes !== [] => 'One or more in-scope governed subjects were not fully covered in this compare run.',
            $hasEvidenceGaps => 'Evidence gaps limited the quality of the visible result for some subjects.',
            $isInProgress => 'Counts will become decision-grade after the compare run finishes.',
            in_array($family, [ExplanationFamily::MissingInput, ExplanationFamily::BlockedPrerequisite, ExplanationFamily::Unavailable], true) => $reason?->shortExplanation ?? 'The compare inputs were not complete enough to produce a normal result.',
            default => 'Coverage matched the in-scope compare input for this run.',
        };
        $reliabilityStatement = $isFailed
            ? 'The last compare failed, so the tenant needs review before you rely on this posture.'
            : match ($trustworthiness) {
                TrustworthinessLevel::Trustworthy => $findingsCount > 0
                    ? 'The compare completed with enough coverage to treat the recorded drift as decision-grade.'
                    : 'The compare completed with enough coverage to treat the absence of findings as trustworthy.',
                TrustworthinessLevel::LimitedConfidence => 'The compare completed, but coverage or evidence limitations mean the visible result should be treated as partial.',
                TrustworthinessLevel::DiagnosticOnly => 'The compare is still in progress, so current counts are only diagnostic.',
                TrustworthinessLevel::Unusable => 'The compare did not produce a result that should be used for the intended decision yet.',
            };
        $nextActionText = $isFailed
            ? 'Review the failed compare run before relying on this tenant posture'
            : ($reason?->firstNextStep()?->label ?? match ($family) {
                ExplanationFamily::NoIssuesDetected => 'No action needed',
                ExplanationFamily::TrustworthyResult => 'Review the detected drift findings',
                ExplanationFamily::SuppressedOutput => 'Review the missing coverage or evidence before treating this compare as complete',
                ExplanationFamily::CompletedButLimited => 'Review coverage limits before acting on this compare',
                ExplanationFamily::InProgress => 'Wait for the compare to finish',
                ExplanationFamily::MissingInput,
                ExplanationFamily::BlockedPrerequisite,
                ExplanationFamily::Unavailable => $stats->state === 'idle'
                    ? 'Run the baseline compare to generate a result'
                    : 'Review the blocking baseline or scope prerequisite',
            });

        return $this->builder->build(
            family: $family,
            headline: $headline,
            executionOutcome: $executionOutcome,
            executionOutcomeLabel: $executionOutcomeLabel,
            evaluationResult: $evaluationResult,
            trustworthinessLevel: $trustworthiness,
            reliabilityStatement: $reliabilityStatement,
            coverageStatement: $coverageStatement,
            dominantCauseCode: $reason?->internalCode ?? $stats->reasonCode,
            dominantCauseLabel: $reason?->operatorLabel,
            dominantCauseExplanation: $reason?->shortExplanation ?? $stats->message ?? $stats->failureReason,
            nextActionCategory: $isFailed
                ? 'inspect_run'
                : ($family === ExplanationFamily::NoIssuesDetected
                    ? 'none'
                    : match ($family) {
                        ExplanationFamily::TrustworthyResult => 'manual_validate',
                        ExplanationFamily::MissingInput,
                        ExplanationFamily::BlockedPrerequisite,
                        ExplanationFamily::Unavailable => 'fix_prerequisite',
                        default => 'review_evidence_gaps',
                    }),
            nextActionText: $nextActionText,
            countDescriptors: $this->countDescriptors($stats, $hasCoverageWarnings, $hasEvidenceGaps),
            diagnosticsAvailable: $stats->operationRunId !== null || $reason !== null,
            diagnosticsSummary: 'Run evidence and low-level compare diagnostics remain available below the primary explanation.',
        );
    }

    /**
     * @return array<int, CountDescriptor>
     */
    private function countDescriptors(
        BaselineCompareStats $stats,
        bool $hasCoverageWarnings,
        bool $hasEvidenceGaps,
    ): array {
        $descriptors = [];

        if ($stats->findingsCount !== null) {
            $descriptors[] = new CountDescriptor(
                label: 'Findings shown',
                value: (int) $stats->findingsCount,
                role: CountDescriptor::ROLE_EVALUATION_OUTPUT,
                qualifier: $hasCoverageWarnings || $hasEvidenceGaps ? 'not complete' : null,
            );
        }

        if ($stats->uncoveredTypesCount !== null) {
            $descriptors[] = new CountDescriptor(
                label: 'Uncovered types',
                value: (int) $stats->uncoveredTypesCount,
                role: CountDescriptor::ROLE_COVERAGE,
                qualifier: (int) $stats->uncoveredTypesCount > 0 ? 'coverage gap' : null,
            );
        }

        if ($stats->evidenceGapsCount !== null) {
            $descriptors[] = new CountDescriptor(
                label: 'Evidence gaps',
                value: (int) $stats->evidenceGapsCount,
                role: CountDescriptor::ROLE_RELIABILITY_SIGNAL,
                qualifier: (int) $stats->evidenceGapsCount > 0 ? 'review needed' : null,
            );
        }

        if ($stats->evidenceGapStructuralCount !== null && $stats->evidenceGapStructuralCount > 0) {
            $descriptors[] = new CountDescriptor(
                label: 'Structural gaps',
                value: (int) $stats->evidenceGapStructuralCount,
                role: CountDescriptor::ROLE_RELIABILITY_SIGNAL,
                qualifier: 'product or support limit',
                visibilityTier: CountDescriptor::VISIBILITY_DIAGNOSTIC,
            );
        }

        if ($stats->evidenceGapOperationalCount !== null && $stats->evidenceGapOperationalCount > 0) {
            $descriptors[] = new CountDescriptor(
                label: 'Operational gaps',
                value: (int) $stats->evidenceGapOperationalCount,
                role: CountDescriptor::ROLE_RELIABILITY_SIGNAL,
                qualifier: 'local evidence missing',
                visibilityTier: CountDescriptor::VISIBILITY_DIAGNOSTIC,
            );
        }

        if ($stats->evidenceGapTransientCount !== null && $stats->evidenceGapTransientCount > 0) {
            $descriptors[] = new CountDescriptor(
                label: 'Transient gaps',
                value: (int) $stats->evidenceGapTransientCount,
                role: CountDescriptor::ROLE_RELIABILITY_SIGNAL,
                qualifier: 'retry may help',
                visibilityTier: CountDescriptor::VISIBILITY_DIAGNOSTIC,
            );
        }

        if ($stats->severityCounts !== []) {
            foreach (['high' => 'High severity', 'medium' => 'Medium severity', 'low' => 'Low severity'] as $key => $label) {
                $value = (int) ($stats->severityCounts[$key] ?? 0);

                if ($value === 0) {
                    continue;
                }

                $descriptors[] = new CountDescriptor(
                    label: $label,
                    value: $value,
                    role: CountDescriptor::ROLE_EVALUATION_OUTPUT,
                    visibilityTier: CountDescriptor::VISIBILITY_DIAGNOSTIC,
                );
            }
        }

        return $descriptors;
    }
}
