<?php

declare(strict_types=1);

namespace App\Services\Findings;

use App\Models\Finding;
use App\Models\FindingException;
use App\Models\FindingExceptionDecision;
use App\Support\Findings\FindingOutcomeSemantics;
use Carbon\CarbonImmutable;
use Illuminate\Support\Carbon;

final class FindingRiskGovernanceResolver
{
    public function __construct(
        private readonly FindingOutcomeSemantics $findingOutcomeSemantics,
    ) {}

    public function resolveWorkflowFamily(Finding $finding): string
    {
        return match (Finding::canonicalizeStatus((string) $finding->status)) {
            Finding::STATUS_RISK_ACCEPTED => 'accepted_risk',
            Finding::STATUS_RESOLVED,
            Finding::STATUS_CLOSED => 'historical',
            default => 'active',
        };
    }

    public function resolveExceptionStatus(FindingException $exception, ?CarbonImmutable $now = null): string
    {
        $now ??= CarbonImmutable::instance(now());

        $status = (string) $exception->status;

        if (in_array($status, [
            FindingException::STATUS_REJECTED,
            FindingException::STATUS_REVOKED,
            FindingException::STATUS_SUPERSEDED,
        ], true)) {
            return $status;
        }

        if ($status === FindingException::STATUS_PENDING) {
            return FindingException::STATUS_PENDING;
        }

        $expiresAt = $exception->expires_at instanceof Carbon
            ? CarbonImmutable::instance($exception->expires_at)
            : null;

        if ($expiresAt instanceof CarbonImmutable && $expiresAt->lessThanOrEqualTo($now)) {
            return FindingException::STATUS_EXPIRED;
        }

        if ($this->isExpiring($exception, $now)) {
            return FindingException::STATUS_EXPIRING;
        }

        return FindingException::STATUS_ACTIVE;
    }

    public function resolveValidityState(FindingException $exception, ?CarbonImmutable $now = null): string
    {
        if ($exception->isPendingRenewal()) {
            return $this->resolveApprovedValidityState($exception, $now);
        }

        return match ($this->resolveExceptionStatus($exception, $now)) {
            FindingException::STATUS_ACTIVE => FindingException::VALIDITY_VALID,
            FindingException::STATUS_EXPIRING => FindingException::VALIDITY_EXPIRING,
            FindingException::STATUS_EXPIRED => FindingException::VALIDITY_EXPIRED,
            FindingException::STATUS_REVOKED => FindingException::VALIDITY_REVOKED,
            FindingException::STATUS_REJECTED => FindingException::VALIDITY_REJECTED,
            default => FindingException::VALIDITY_MISSING_SUPPORT,
        };
    }

    public function resolveFindingState(Finding $finding, ?FindingException $exception = null, ?CarbonImmutable $now = null): string
    {
        $exception ??= $finding->relationLoaded('findingException')
            ? $finding->findingException
            : $finding->findingException()->first();

        $findingIsRiskAccepted = $finding->isRiskAccepted();

        if (! $exception instanceof FindingException) {
            return $findingIsRiskAccepted
                ? 'risk_accepted_without_valid_exception'
                : 'ungoverned';
        }

        if (! $findingIsRiskAccepted) {
            return $exception->isPending()
                ? 'pending_exception'
                : 'ungoverned';
        }

        if ($exception->isPendingRenewal()) {
            return match ($this->resolveApprovedValidityState($exception, $now)) {
                FindingException::VALIDITY_VALID => 'valid_exception',
                FindingException::VALIDITY_EXPIRING => 'expiring_exception',
                FindingException::VALIDITY_EXPIRED => 'expired_exception',
                default => 'pending_exception',
            };
        }

        return match ($this->resolveExceptionStatus($exception, $now)) {
            FindingException::STATUS_PENDING => 'pending_exception',
            FindingException::STATUS_ACTIVE => 'valid_exception',
            FindingException::STATUS_EXPIRING => 'expiring_exception',
            FindingException::STATUS_EXPIRED => 'expired_exception',
            FindingException::STATUS_REVOKED => 'revoked_exception',
            FindingException::STATUS_REJECTED => 'rejected_exception',
            default => $findingIsRiskAccepted
                ? 'risk_accepted_without_valid_exception'
                : 'ungoverned',
        };
    }

    public function isValidGovernedAcceptedRisk(Finding $finding, ?FindingException $exception = null, ?CarbonImmutable $now = null): bool
    {
        return in_array($this->resolveFindingState($finding, $exception, $now), [
            'valid_exception',
            'expiring_exception',
        ], true);
    }

    public function resolveGovernanceValidity(Finding $finding, ?FindingException $exception = null, ?CarbonImmutable $now = null): ?string
    {
        $exception ??= $finding->relationLoaded('findingException')
            ? $finding->findingException
            : $finding->findingException()->first();

        if ($exception instanceof FindingException) {
            return $this->resolveValidityState($exception, $now);
        }

        return $finding->isRiskAccepted()
            ? FindingException::VALIDITY_MISSING_SUPPORT
            : null;
    }

    public function resolveGovernanceAttention(Finding $finding, ?FindingException $exception = null, ?CarbonImmutable $now = null): string
    {
        if (! $finding->isRiskAccepted()) {
            return 'not_applicable';
        }

        return match ($this->resolveGovernanceValidity($finding, $exception, $now)) {
            FindingException::VALIDITY_VALID => 'healthy',
            FindingException::VALIDITY_EXPIRING,
            FindingException::VALIDITY_EXPIRED,
            FindingException::VALIDITY_REVOKED,
            FindingException::VALIDITY_REJECTED,
            FindingException::VALIDITY_MISSING_SUPPORT => 'attention_needed',
            default => 'attention_needed',
        };
    }

    public function requiresGovernanceAttention(Finding $finding, ?FindingException $exception = null, ?CarbonImmutable $now = null): bool
    {
        return $this->resolveGovernanceAttention($finding, $exception, $now) === 'attention_needed';
    }

    public function resolveWarningMessage(Finding $finding, ?FindingException $exception = null, ?CarbonImmutable $now = null): ?string
    {
        $exception ??= $finding->relationLoaded('findingException')
            ? $finding->findingException
            : $finding->findingException()->first();

        if (! $exception instanceof FindingException) {
            return $finding->isRiskAccepted()
                ? 'This finding is marked as accepted risk without a valid exception record.'
                : null;
        }

        $exceptionStatus = $exception->isPendingRenewal()
            ? match ($this->resolveApprovedValidityState($exception, $now)) {
                FindingException::VALIDITY_EXPIRED => FindingException::STATUS_EXPIRED,
                FindingException::VALIDITY_EXPIRING => FindingException::STATUS_EXPIRING,
                FindingException::VALIDITY_VALID => FindingException::STATUS_ACTIVE,
                default => FindingException::STATUS_PENDING,
            }
        : $this->resolveExceptionStatus($exception, $now);

        if ($finding->isRiskAccepted()) {
            return match ($this->resolveFindingState($finding, $exception, $now)) {
                'risk_accepted_without_valid_exception' => 'This finding is marked as accepted risk without a valid exception record.',
                'expiring_exception' => 'The linked exception is still valid, but it is nearing expiry and needs review.',
                'expired_exception' => 'The linked exception has expired and no longer governs accepted risk.',
                'revoked_exception' => 'The linked exception was revoked and no longer governs accepted risk.',
                'rejected_exception' => 'The linked exception was rejected and does not govern accepted risk.',
                default => null,
            };
        }

        if ($exception->requiresFreshDecisionForFinding($finding)) {
            return 'This finding changed after the earlier exception decision; a fresh decision is required.';
        }

        return match ($exceptionStatus) {
            FindingException::STATUS_EXPIRING => 'The linked exception is nearing expiry and needs review.',
            FindingException::STATUS_EXPIRED => 'The linked exception has expired and no longer governs accepted risk.',
            FindingException::STATUS_REVOKED => 'The linked exception was revoked and no longer governs accepted risk.',
            FindingException::STATUS_REJECTED => 'The linked exception was rejected and does not govern accepted risk.',
            default => null,
        };
    }

    public function resolveHistoricalContext(Finding $finding): ?string
    {
        return match ((string) $finding->status) {
            Finding::STATUS_RESOLVED => $this->resolvedHistoricalContext($finding),
            Finding::STATUS_CLOSED => $this->closedHistoricalContext($finding),
            default => null,
        };
    }

    public function resolvePrimaryNarrative(Finding $finding, ?FindingException $exception = null, ?CarbonImmutable $now = null): string
    {
        return match ($this->resolveWorkflowFamily($finding)) {
            'accepted_risk' => $this->resolveGovernanceAttention($finding, $exception, $now) === 'healthy'
                ? 'Accepted risk remains visible because current governance is still valid.'
                : 'Accepted risk is still on record, but governance follow-up is needed before it can be treated as safe to ignore.',
            'historical' => $this->historicalPrimaryNarrative($finding),
            default => match ($finding->responsibilityState()) {
                Finding::RESPONSIBILITY_STATE_ORPHANED_ACCOUNTABILITY => 'This finding is still active workflow work and currently has orphaned accountability.',
                Finding::RESPONSIBILITY_STATE_OWNED_UNASSIGNED => 'This finding has an accountable owner, but the active remediation work is still unassigned.',
                default => 'This finding is still active workflow work with accountable ownership and an active assignee.',
            },
        };
    }

    public function resolvePrimaryNextAction(Finding $finding, ?FindingException $exception = null, ?CarbonImmutable $now = null): ?string
    {
        if ($finding->hasOpenStatus() && $finding->due_at?->isPast() === true) {
            return match ($finding->responsibilityState()) {
                Finding::RESPONSIBILITY_STATE_ORPHANED_ACCOUNTABILITY => 'Review the overdue finding, set an accountable owner, and confirm the next workflow step.',
                Finding::RESPONSIBILITY_STATE_OWNED_UNASSIGNED => 'Review the overdue finding and assign the active remediation work or the next workflow step.',
                default => 'Review the overdue finding and confirm the next workflow step.',
            };
        }

        if ($this->resolveWorkflowFamily($finding) === 'accepted_risk') {
            return match ($this->resolveGovernanceValidity($finding, $exception, $now)) {
                FindingException::VALIDITY_VALID => 'Keep the exception under review until remediation is possible.',
                FindingException::VALIDITY_EXPIRING => 'Renew or review the exception before the current governance window lapses.',
                FindingException::VALIDITY_EXPIRED,
                FindingException::VALIDITY_REVOKED,
                FindingException::VALIDITY_REJECTED,
                FindingException::VALIDITY_MISSING_SUPPORT => 'Restore valid governance or move the finding back into active remediation.',
                default => 'Review the current governance state before treating this accepted risk as stable.',
            };
        }

        if ((string) $finding->status === Finding::STATUS_RESOLVED) {
            return $this->findingOutcomeSemantics->verificationState($finding) === FindingOutcomeSemantics::VERIFICATION_PENDING
                ? 'Wait for later trusted evidence to confirm the issue is actually clear, or reopen the finding if verification still fails.'
                : 'Keep the finding closed unless later trusted evidence shows the issue has returned.';
        }

        if ((string) $finding->status === Finding::STATUS_CLOSED) {
            return 'Review the administrative closure context and reopen the finding if the tenant reality no longer matches that decision.';
        }

        return match ($finding->responsibilityState()) {
            Finding::RESPONSIBILITY_STATE_ORPHANED_ACCOUNTABILITY => 'Set an accountable owner so follow-up does not stall, even if remediation work is already assigned.',
            Finding::RESPONSIBILITY_STATE_OWNED_UNASSIGNED => 'An accountable owner is set. Assign the active remediation work or record the next workflow step.',
            default => 'Review the current workflow state and decide whether to progress, resolve, close, or request governance coverage.',
        };
    }

    public function syncExceptionState(FindingException $exception, ?CarbonImmutable $now = null): FindingException
    {
        $resolvedStatus = $this->resolveExceptionStatus($exception, $now);
        $resolvedValidityState = $this->resolveValidityState($exception, $now);

        if ((string) $exception->status === $resolvedStatus && (string) $exception->current_validity_state === $resolvedValidityState) {
            return $exception;
        }

        $exception->forceFill([
            'status' => $resolvedStatus,
            'current_validity_state' => $resolvedValidityState,
        ])->save();

        return $exception->refresh();
    }

    private function resolveApprovedValidityState(FindingException $exception, ?CarbonImmutable $now = null): string
    {
        $now ??= CarbonImmutable::instance(now());

        $expiresAt = $this->renewalAwareDate(
            $exception,
            'previous_expires_at',
            $exception->expires_at,
        );

        if ($expiresAt instanceof CarbonImmutable && $expiresAt->lessThanOrEqualTo($now)) {
            return FindingException::VALIDITY_EXPIRED;
        }

        if ($this->isExpiring($exception, $now, renewalAware: true)) {
            return FindingException::VALIDITY_EXPIRING;
        }

        return FindingException::VALIDITY_VALID;
    }

    private function isExpiring(FindingException $exception, CarbonImmutable $now, bool $renewalAware = false): bool
    {
        $reviewDueAt = $renewalAware
            ? $this->renewalAwareDate($exception, 'previous_review_due_at', $exception->review_due_at)
            : ($exception->review_due_at instanceof Carbon ? CarbonImmutable::instance($exception->review_due_at) : null);

        if ($reviewDueAt instanceof CarbonImmutable && $reviewDueAt->lessThanOrEqualTo($now)) {
            return true;
        }

        $expiresAt = $renewalAware
            ? $this->renewalAwareDate($exception, 'previous_expires_at', $exception->expires_at)
            : ($exception->expires_at instanceof Carbon ? CarbonImmutable::instance($exception->expires_at) : null);

        if (! $expiresAt instanceof CarbonImmutable) {
            return false;
        }

        return $expiresAt->lessThanOrEqualTo($now->addDays(7));
    }

    private function renewalAwareDate(FindingException $exception, string $metadataKey, mixed $fallback): ?CarbonImmutable
    {
        $currentDecision = $exception->relationLoaded('currentDecision')
            ? $exception->currentDecision
            : $exception->currentDecision()->first();

        if ($currentDecision instanceof FindingExceptionDecision && is_string($currentDecision->metadata[$metadataKey] ?? null)) {
            return CarbonImmutable::parse((string) $currentDecision->metadata[$metadataKey]);
        }

        return $fallback instanceof Carbon
            ? CarbonImmutable::instance($fallback)
            : null;
    }

    private function resolvedHistoricalContext(Finding $finding): ?string
    {
        return match ($this->findingOutcomeSemantics->terminalOutcomeKey($finding)) {
            FindingOutcomeSemantics::OUTCOME_RESOLVED_PENDING_VERIFICATION => 'This finding was resolved manually and is still waiting for trusted evidence to confirm the issue is actually gone.',
            FindingOutcomeSemantics::OUTCOME_VERIFIED_CLEARED => 'Trusted evidence later confirmed the triggering condition was no longer present at the last observed check.',
            default => 'Resolved records the workflow outcome. Review the reason and latest evidence before treating it as technical proof.',
        };
    }

    private function closedHistoricalContext(Finding $finding): ?string
    {
        return match ($this->findingOutcomeSemantics->terminalOutcomeKey($finding)) {
            FindingOutcomeSemantics::OUTCOME_CLOSED_FALSE_POSITIVE => 'This finding was closed as a false positive, which is an administrative closure rather than proof of remediation.',
            FindingOutcomeSemantics::OUTCOME_CLOSED_DUPLICATE => 'This finding was closed as a duplicate, which is an administrative closure rather than proof of remediation.',
            FindingOutcomeSemantics::OUTCOME_CLOSED_NO_LONGER_APPLICABLE => 'This finding was closed as no longer applicable, which is an administrative closure rather than proof of remediation.',
            FindingOutcomeSemantics::OUTCOME_RISK_ACCEPTED => 'Closed reflects workflow handling. Governance validity still determines whether accepted risk remains safe to rely on.',
            default => 'Closed records the workflow outcome. Review the recorded reason before treating it as technical proof.',
        };
    }

    private function historicalPrimaryNarrative(Finding $finding): string
    {
        return match ($this->findingOutcomeSemantics->terminalOutcomeKey($finding)) {
            FindingOutcomeSemantics::OUTCOME_RESOLVED_PENDING_VERIFICATION => 'Resolved pending verification means an operator declared the remediation complete, but trusted verification has not confirmed it yet.',
            FindingOutcomeSemantics::OUTCOME_VERIFIED_CLEARED => 'Verified cleared means trusted evidence later confirmed the issue was no longer present.',
            FindingOutcomeSemantics::OUTCOME_CLOSED_FALSE_POSITIVE,
            FindingOutcomeSemantics::OUTCOME_CLOSED_DUPLICATE,
            FindingOutcomeSemantics::OUTCOME_CLOSED_NO_LONGER_APPLICABLE => 'This finding is closed for an administrative reason and should not be read as a remediation outcome.',
            default => 'This finding is historical workflow context.',
        };
    }
}
