<?php

declare(strict_types=1);

namespace App\Support\Baselines;

use App\Support\Ui\OperatorExplanation\OperatorExplanationPattern;
use App\Support\Ui\OperatorExplanation\TrustworthinessLevel;
use Carbon\CarbonImmutable;

final class BaselineCompareSummaryAssessor
{
    private const int STALE_AFTER_DAYS = 7;

    public static function staleAfterDays(): int
    {
        return self::STALE_AFTER_DAYS;
    }

    public static function isStaleComparedAt(\DateTimeInterface|string|null $value): bool
    {
        if ($value === null) {
            return false;
        }

        try {
            $comparedAt = $value instanceof \DateTimeInterface
                ? CarbonImmutable::instance(\DateTimeImmutable::createFromInterface($value))
                : CarbonImmutable::parse($value);
        } catch (\Throwable) {
            return false;
        }

        return $comparedAt->lt(now()->subDays(self::STALE_AFTER_DAYS));
    }

    public function assess(BaselineCompareStats $stats): BaselineCompareSummaryAssessment
    {
        $explanation = $stats->operatorExplanation();
        $findingsVisibleCount = (int) ($stats->findingsCount ?? 0);
        $highSeverityCount = (int) ($stats->severityCounts['high'] ?? 0);
        $overdueOpenFindingsCount = $stats->overdueOpenFindingsCount;
        $expiringGovernanceCount = $stats->expiringGovernanceCount;
        $lapsedGovernanceCount = $stats->lapsedGovernanceCount;
        $reasonCode = is_string($stats->reasonCode) ? BaselineCompareReasonCode::tryFrom($stats->reasonCode) : null;
        $evaluationResult = $stats->state === 'failed'
            ? 'failed_result'
            : $explanation->evaluationResult;
        $positiveClaimAllowed = $this->positiveClaimAllowed(
            $stats,
            $explanation,
            $reasonCode,
            $evaluationResult,
            $overdueOpenFindingsCount,
            $expiringGovernanceCount,
            $lapsedGovernanceCount,
        );
        $isStale = $this->hasStaleResult($stats, $evaluationResult);
        $stateFamily = $this->stateFamily(
            $stats,
            $findingsVisibleCount,
            $positiveClaimAllowed,
            $isStale,
            $overdueOpenFindingsCount,
            $expiringGovernanceCount,
            $lapsedGovernanceCount,
        );
        $summaryReasonCode = $this->summaryReasonCode(
            $stats,
            $overdueOpenFindingsCount,
            $expiringGovernanceCount,
            $lapsedGovernanceCount,
        );

        return new BaselineCompareSummaryAssessment(
            stateFamily: $stateFamily,
            headline: $this->headline(
                $stats,
                $stateFamily,
                $findingsVisibleCount,
                $highSeverityCount,
                $evaluationResult,
                $overdueOpenFindingsCount,
                $expiringGovernanceCount,
                $lapsedGovernanceCount,
            ),
            supportingMessage: $this->supportingMessage(
                $stats,
                $stateFamily,
                $findingsVisibleCount,
                $evaluationResult,
                $overdueOpenFindingsCount,
                $expiringGovernanceCount,
                $lapsedGovernanceCount,
            ),
            tone: $this->tone($stats, $stateFamily),
            positiveClaimAllowed: $positiveClaimAllowed,
            trustworthinessLevel: $explanation->trustworthinessLevel->value,
            evaluationResult: $evaluationResult,
            evidenceImpact: $this->evidenceImpact($stats, $evaluationResult, $isStale),
            findingsVisibleCount: $findingsVisibleCount,
            highSeverityCount: $highSeverityCount,
            nextAction: $this->nextAction(
                $stats,
                $stateFamily,
                $findingsVisibleCount,
                $evaluationResult,
                $overdueOpenFindingsCount,
                $expiringGovernanceCount,
                $lapsedGovernanceCount,
            ),
            lastComparedLabel: $stats->lastComparedHuman,
            reasonCode: $summaryReasonCode,
            overdueOpenFindingsCount: $overdueOpenFindingsCount,
            expiringGovernanceCount: $expiringGovernanceCount,
            lapsedGovernanceCount: $lapsedGovernanceCount,
        );
    }

    private function positiveClaimAllowed(
        BaselineCompareStats $stats,
        OperatorExplanationPattern $explanation,
        ?BaselineCompareReasonCode $reasonCode,
        string $evaluationResult,
        int $overdueOpenFindingsCount,
        int $expiringGovernanceCount,
        int $lapsedGovernanceCount,
    ): bool {
        if ($stats->state !== 'ready') {
            return false;
        }

        if ((int) ($stats->findingsCount ?? 0) > 0) {
            return false;
        }

        if ($evaluationResult !== 'no_result') {
            return false;
        }

        if ($explanation->trustworthinessLevel !== TrustworthinessLevel::Trustworthy) {
            return false;
        }

        if (in_array($stats->coverageStatus, ['warning', 'unproven'], true)) {
            return false;
        }

        if ((int) ($stats->evidenceGapsCount ?? 0) > 0) {
            return false;
        }

        if ($overdueOpenFindingsCount > 0 || $expiringGovernanceCount > 0 || $lapsedGovernanceCount > 0) {
            return false;
        }

        if ($this->hasStaleResult($stats, $evaluationResult)) {
            return false;
        }

        if ($stats->reasonCode === null) {
            return true;
        }

        return $reasonCode?->supportsPositiveClaim() ?? false;
    }

    private function stateFamily(
        BaselineCompareStats $stats,
        int $findingsVisibleCount,
        bool $positiveClaimAllowed,
        bool $isStale,
        int $overdueOpenFindingsCount,
        int $expiringGovernanceCount,
        int $lapsedGovernanceCount,
    ): string {
        return match (true) {
            $stats->state === 'comparing' => BaselineCompareSummaryAssessment::STATE_IN_PROGRESS,
            $stats->state === 'failed',
            $findingsVisibleCount > 0,
            $overdueOpenFindingsCount > 0,
            $expiringGovernanceCount > 0,
            $lapsedGovernanceCount > 0 => BaselineCompareSummaryAssessment::STATE_ACTION_REQUIRED,
            in_array($stats->state, ['no_tenant', 'no_assignment', 'no_snapshot', 'idle'], true) => BaselineCompareSummaryAssessment::STATE_UNAVAILABLE,
            $isStale => BaselineCompareSummaryAssessment::STATE_STALE,
            $positiveClaimAllowed => BaselineCompareSummaryAssessment::STATE_POSITIVE,
            default => BaselineCompareSummaryAssessment::STATE_CAUTION,
        };
    }

    private function evidenceImpact(BaselineCompareStats $stats, string $evaluationResult, bool $isStale): string
    {
        if (in_array($stats->state, ['no_tenant', 'no_assignment', 'no_snapshot', 'idle', 'failed'], true)) {
            return BaselineCompareSummaryAssessment::EVIDENCE_UNAVAILABLE;
        }

        if ($isStale) {
            return BaselineCompareSummaryAssessment::EVIDENCE_STALE_RESULT;
        }

        if ($evaluationResult === 'suppressed_result') {
            return BaselineCompareSummaryAssessment::EVIDENCE_SUPPRESSED_OUTPUT;
        }

        if ((int) ($stats->evidenceGapsCount ?? 0) > 0) {
            return BaselineCompareSummaryAssessment::EVIDENCE_EVIDENCE_GAP;
        }

        if (in_array($stats->coverageStatus, ['warning', 'unproven'], true)) {
            return BaselineCompareSummaryAssessment::EVIDENCE_COVERAGE_WARNING;
        }

        return BaselineCompareSummaryAssessment::EVIDENCE_NONE;
    }

    private function headline(
        BaselineCompareStats $stats,
        string $stateFamily,
        int $findingsVisibleCount,
        int $highSeverityCount,
        string $evaluationResult,
        int $overdueOpenFindingsCount,
        int $expiringGovernanceCount,
        int $lapsedGovernanceCount,
    ): string {
        return match ($stateFamily) {
            BaselineCompareSummaryAssessment::STATE_POSITIVE => 'No confirmed drift in the latest baseline compare.',
            BaselineCompareSummaryAssessment::STATE_CAUTION => match (true) {
                $evaluationResult === 'suppressed_result' => 'The last compare finished, but normal result output was suppressed.',
                (int) ($stats->evidenceGapsCount ?? 0) > 0 => 'No confirmed drift is visible, but evidence gaps still limit this result.',
                in_array($stats->coverageStatus, ['warning', 'unproven'], true) => 'No confirmed drift is visible, but coverage limits this compare.',
                default => 'The latest compare result needs caution before you treat it as an all-clear.',
            },
            BaselineCompareSummaryAssessment::STATE_STALE => 'The latest baseline compare result is stale.',
            BaselineCompareSummaryAssessment::STATE_ACTION_REQUIRED => match (true) {
                $stats->state === 'failed' || $evaluationResult === 'failed_result' => 'The latest baseline compare failed before it produced a usable result.',
                $lapsedGovernanceCount > 0 => sprintf('Accepted-risk governance has lapsed on %d finding%s.', $lapsedGovernanceCount, $lapsedGovernanceCount === 1 ? '' : 's'),
                $overdueOpenFindingsCount > 0 => sprintf('%d overdue finding%s still need review.', $overdueOpenFindingsCount, $overdueOpenFindingsCount === 1 ? '' : 's'),
                $expiringGovernanceCount > 0 => sprintf('Accepted-risk governance is nearing expiry on %d finding%s.', $expiringGovernanceCount, $expiringGovernanceCount === 1 ? '' : 's'),
                $highSeverityCount > 0 => sprintf('%d high-severity drift finding%s need review.', $highSeverityCount, $highSeverityCount === 1 ? '' : 's'),
                default => sprintf('%d open drift finding%s need review.', $findingsVisibleCount, $findingsVisibleCount === 1 ? '' : 's'),
            },
            BaselineCompareSummaryAssessment::STATE_IN_PROGRESS => 'Baseline compare is in progress.',
            default => match ($stats->state) {
                'no_assignment' => 'This environment does not have an assigned baseline yet.',
                'no_snapshot' => 'The current baseline snapshot is not available for compare.',
                'idle' => 'A current baseline compare result is not available yet.',
                default => 'A usable baseline compare result is not currently available.',
            },
        };
    }

    private function supportingMessage(
        BaselineCompareStats $stats,
        string $stateFamily,
        int $findingsVisibleCount,
        string $evaluationResult,
        int $overdueOpenFindingsCount,
        int $expiringGovernanceCount,
        int $lapsedGovernanceCount,
    ): ?string {
        return match ($stateFamily) {
            BaselineCompareSummaryAssessment::STATE_POSITIVE => $stats->lastComparedHuman !== null
                ? 'Last compared '.$stats->lastComparedHuman.'.'
                : 'The latest compare result is trustworthy enough to treat zero findings as current.',
            BaselineCompareSummaryAssessment::STATE_CAUTION => match (true) {
                $evaluationResult === 'suppressed_result' => 'Review the operation detail before treating zero visible findings as complete.',
                (int) ($stats->evidenceGapsCount ?? 0) > 0 => 'Review the compare detail to see which evidence gaps still limit trust.',
                in_array($stats->coverageStatus, ['warning', 'unproven'], true) => 'Coverage warnings mean zero visible findings are not an all-clear on their own.',
                default => $stats->reasonMessage ?? $stats->message,
            },
            BaselineCompareSummaryAssessment::STATE_STALE => $stats->lastComparedHuman !== null
                ? 'Last compared '.$stats->lastComparedHuman.'. Refresh compare before relying on this posture.'
                : 'Refresh compare before relying on this posture.',
            BaselineCompareSummaryAssessment::STATE_ACTION_REQUIRED => match (true) {
                $stats->state === 'failed' => $stats->failureReason,
                $lapsedGovernanceCount > 0 => 'Restore valid governance or move those findings back into active remediation before relying on accepted risk.',
                $overdueOpenFindingsCount > 0 => 'Overdue workflow items remain even if the latest compare did not introduce new drift findings.',
                $expiringGovernanceCount > 0 => 'Current governance is still valid, but review or renewal is due soon.',
                $findingsVisibleCount > 0 => 'Open findings remain on this tenant and need review.',
                default => $stats->message,
            },
            BaselineCompareSummaryAssessment::STATE_IN_PROGRESS => 'Current counts are diagnostic only until the compare operation finishes.',
            default => $stats->message,
        };
    }

    private function tone(BaselineCompareStats $stats, string $stateFamily): string
    {
        return match ($stateFamily) {
            BaselineCompareSummaryAssessment::STATE_POSITIVE => 'success',
            BaselineCompareSummaryAssessment::STATE_ACTION_REQUIRED => 'danger',
            BaselineCompareSummaryAssessment::STATE_IN_PROGRESS => 'info',
            BaselineCompareSummaryAssessment::STATE_UNAVAILABLE => $stats->state === 'no_snapshot' ? 'warning' : 'gray',
            default => 'warning',
        };
    }

    /**
     * @return array{label: string, target: string}
     */
    private function nextAction(
        BaselineCompareStats $stats,
        string $stateFamily,
        int $findingsVisibleCount,
        string $evaluationResult,
        int $overdueOpenFindingsCount,
        int $expiringGovernanceCount,
        int $lapsedGovernanceCount,
    ): array {
        if ($findingsVisibleCount > 0 || $overdueOpenFindingsCount > 0 || $expiringGovernanceCount > 0 || $lapsedGovernanceCount > 0) {
            return [
                'label' => 'Open findings',
                'target' => BaselineCompareSummaryAssessment::NEXT_TARGET_FINDINGS,
            ];
        }

        return match ($stateFamily) {
            BaselineCompareSummaryAssessment::STATE_ACTION_REQUIRED => [
                'label' => $evaluationResult === 'failed_result' ? 'Review the failed operation' : 'Review compare detail',
                'target' => $stats->operationRunId !== null
                    ? BaselineCompareSummaryAssessment::NEXT_TARGET_RUN
                    : BaselineCompareSummaryAssessment::NEXT_TARGET_LANDING,
            ],
            BaselineCompareSummaryAssessment::STATE_CAUTION => [
                'label' => 'Review compare detail',
                'target' => $stats->operationRunId !== null
                    ? BaselineCompareSummaryAssessment::NEXT_TARGET_RUN
                    : BaselineCompareSummaryAssessment::NEXT_TARGET_LANDING,
            ],
            BaselineCompareSummaryAssessment::STATE_STALE => [
                'label' => 'Open Baseline Compare',
                'target' => BaselineCompareSummaryAssessment::NEXT_TARGET_LANDING,
            ],
            BaselineCompareSummaryAssessment::STATE_IN_PROGRESS => [
                'label' => $stats->operationRunId !== null ? 'Open operation' : 'Open Baseline Compare',
                'target' => $stats->operationRunId !== null
                    ? BaselineCompareSummaryAssessment::NEXT_TARGET_RUN
                    : BaselineCompareSummaryAssessment::NEXT_TARGET_LANDING,
            ],
            BaselineCompareSummaryAssessment::STATE_UNAVAILABLE => match ($stats->state) {
                'no_assignment' => [
                    'label' => 'Assign a baseline first',
                    'target' => BaselineCompareSummaryAssessment::NEXT_TARGET_NONE,
                ],
                'no_snapshot' => [
                    'label' => 'Review baseline prerequisites',
                    'target' => BaselineCompareSummaryAssessment::NEXT_TARGET_LANDING,
                ],
                'idle' => [
                    'label' => 'Open Baseline Compare',
                    'target' => BaselineCompareSummaryAssessment::NEXT_TARGET_LANDING,
                ],
                default => [
                    'label' => 'Review compare availability',
                    'target' => BaselineCompareSummaryAssessment::NEXT_TARGET_NONE,
                ],
            },
            default => [
                'label' => 'No action needed',
                'target' => BaselineCompareSummaryAssessment::NEXT_TARGET_NONE,
            ],
        };
    }

    private function summaryReasonCode(
        BaselineCompareStats $stats,
        int $overdueOpenFindingsCount,
        int $expiringGovernanceCount,
        int $lapsedGovernanceCount,
    ): ?string {
        if ($lapsedGovernanceCount > 0) {
            return BaselineCompareReasonCode::GovernanceLapsed->value;
        }

        if ($overdueOpenFindingsCount > 0) {
            return BaselineCompareReasonCode::OverdueFindingsRemain->value;
        }

        if ($expiringGovernanceCount > 0) {
            return BaselineCompareReasonCode::GovernanceExpiring->value;
        }

        return $stats->reasonCode;
    }

    private function hasStaleResult(BaselineCompareStats $stats, string $evaluationResult): bool
    {
        if ($stats->state !== 'ready') {
            return false;
        }

        if ($stats->lastComparedIso === null) {
            return false;
        }

        if (! in_array($evaluationResult, ['full_result', 'no_result', 'incomplete_result', 'suppressed_result'], true)) {
            return false;
        }

        return self::isStaleComparedAt($stats->lastComparedIso);
    }
}
