<?php

declare(strict_types=1);

namespace App\Support\Findings;

use App\Models\Finding;

final class FindingOutcomeSemantics
{
    public const string VERIFICATION_PENDING = 'pending_verification';

    public const string VERIFICATION_VERIFIED = 'verified_cleared';

    public const string VERIFICATION_NOT_APPLICABLE = 'not_applicable';

    public const string OUTCOME_RESOLVED_PENDING_VERIFICATION = 'resolved_pending_verification';

    public const string OUTCOME_VERIFIED_CLEARED = 'verified_cleared';

    public const string OUTCOME_CLOSED_FALSE_POSITIVE = 'closed_false_positive';

    public const string OUTCOME_CLOSED_DUPLICATE = 'closed_duplicate';

    public const string OUTCOME_CLOSED_NO_LONGER_APPLICABLE = 'closed_no_longer_applicable';

    public const string OUTCOME_RISK_ACCEPTED = 'risk_accepted';

    /**
     * @return array{
     *     terminal_outcome_key: ?string,
     *     label: ?string,
     *     verification_state: string,
     *     verification_label: ?string,
     *     report_bucket: ?string
     * }
     */
    public function describe(Finding $finding): array
    {
        $terminalOutcomeKey = $this->terminalOutcomeKey($finding);
        $verificationState = $this->verificationState($finding);

        return [
            'terminal_outcome_key' => $terminalOutcomeKey,
            'label' => $terminalOutcomeKey !== null ? $this->terminalOutcomeLabel($terminalOutcomeKey) : null,
            'verification_state' => $verificationState,
            'verification_label' => $verificationState !== self::VERIFICATION_NOT_APPLICABLE
                ? $this->verificationStateLabel($verificationState)
                : null,
            'report_bucket' => $terminalOutcomeKey !== null ? $this->reportBucket($terminalOutcomeKey) : null,
        ];
    }

    public function terminalOutcomeKey(Finding $finding): ?string
    {
        return match ((string) $finding->status) {
            Finding::STATUS_RESOLVED => $this->resolvedTerminalOutcomeKey((string) ($finding->resolved_reason ?? '')),
            Finding::STATUS_CLOSED => $this->closedTerminalOutcomeKey((string) ($finding->closed_reason ?? '')),
            Finding::STATUS_RISK_ACCEPTED => self::OUTCOME_RISK_ACCEPTED,
            default => null,
        };
    }

    public function verificationState(Finding $finding): string
    {
        if ((string) $finding->status !== Finding::STATUS_RESOLVED) {
            return self::VERIFICATION_NOT_APPLICABLE;
        }

        $reason = (string) ($finding->resolved_reason ?? '');

        if (Finding::isSystemResolveReason($reason)) {
            return self::VERIFICATION_VERIFIED;
        }

        if (Finding::isManualResolveReason($reason)) {
            return self::VERIFICATION_PENDING;
        }

        return self::VERIFICATION_NOT_APPLICABLE;
    }

    public function systemReopenReasonFor(Finding $finding): string
    {
        return $this->verificationState($finding) === self::VERIFICATION_PENDING
            ? Finding::REOPEN_REASON_VERIFICATION_FAILED
            : Finding::REOPEN_REASON_RECURRED_AFTER_RESOLUTION;
    }

    /**
     * @return array<string, string>
     */
    public function terminalOutcomeOptions(): array
    {
        return [
            self::OUTCOME_RESOLVED_PENDING_VERIFICATION => $this->terminalOutcomeLabel(self::OUTCOME_RESOLVED_PENDING_VERIFICATION),
            self::OUTCOME_VERIFIED_CLEARED => $this->terminalOutcomeLabel(self::OUTCOME_VERIFIED_CLEARED),
            self::OUTCOME_CLOSED_FALSE_POSITIVE => $this->terminalOutcomeLabel(self::OUTCOME_CLOSED_FALSE_POSITIVE),
            self::OUTCOME_CLOSED_DUPLICATE => $this->terminalOutcomeLabel(self::OUTCOME_CLOSED_DUPLICATE),
            self::OUTCOME_CLOSED_NO_LONGER_APPLICABLE => $this->terminalOutcomeLabel(self::OUTCOME_CLOSED_NO_LONGER_APPLICABLE),
            self::OUTCOME_RISK_ACCEPTED => $this->terminalOutcomeLabel(self::OUTCOME_RISK_ACCEPTED),
        ];
    }

    /**
     * @return array<string, string>
     */
    public function verificationStateOptions(): array
    {
        return [
            self::VERIFICATION_PENDING => $this->verificationStateLabel(self::VERIFICATION_PENDING),
            self::VERIFICATION_VERIFIED => $this->verificationStateLabel(self::VERIFICATION_VERIFIED),
            self::VERIFICATION_NOT_APPLICABLE => $this->verificationStateLabel(self::VERIFICATION_NOT_APPLICABLE),
        ];
    }

    public function terminalOutcomeLabel(string $terminalOutcomeKey): string
    {
        return match ($terminalOutcomeKey) {
            self::OUTCOME_RESOLVED_PENDING_VERIFICATION => 'Resolved pending verification',
            self::OUTCOME_VERIFIED_CLEARED => 'Verified cleared',
            self::OUTCOME_CLOSED_FALSE_POSITIVE => 'Closed as false positive',
            self::OUTCOME_CLOSED_DUPLICATE => 'Closed as duplicate',
            self::OUTCOME_CLOSED_NO_LONGER_APPLICABLE => 'Closed as no longer applicable',
            self::OUTCOME_RISK_ACCEPTED => 'Risk accepted',
            default => 'Unknown outcome',
        };
    }

    public function verificationStateLabel(string $verificationState): string
    {
        return match ($verificationState) {
            self::VERIFICATION_PENDING => 'Pending verification',
            self::VERIFICATION_VERIFIED => 'Verified cleared',
            default => 'Not applicable',
        };
    }

    public function reportBucket(string $terminalOutcomeKey): string
    {
        return match ($terminalOutcomeKey) {
            self::OUTCOME_RESOLVED_PENDING_VERIFICATION => 'remediation_pending_verification',
            self::OUTCOME_VERIFIED_CLEARED => 'remediation_verified',
            self::OUTCOME_RISK_ACCEPTED => 'accepted_risk',
            default => 'administrative_closure',
        };
    }

    public function compactOutcomeSummary(array $counts): ?string
    {
        $parts = [];

        foreach ($this->orderedOutcomeKeys() as $outcomeKey) {
            $count = (int) ($counts[$outcomeKey] ?? 0);

            if ($count < 1) {
                continue;
            }

            $parts[] = sprintf('%d %s', $count, strtolower($this->terminalOutcomeLabel($outcomeKey)));
        }

        return $parts === [] ? null : implode(', ', $parts);
    }

    /**
     * @return array<int, string>
     */
    public function orderedOutcomeKeys(): array
    {
        return [
            self::OUTCOME_RESOLVED_PENDING_VERIFICATION,
            self::OUTCOME_VERIFIED_CLEARED,
            self::OUTCOME_CLOSED_FALSE_POSITIVE,
            self::OUTCOME_CLOSED_DUPLICATE,
            self::OUTCOME_CLOSED_NO_LONGER_APPLICABLE,
            self::OUTCOME_RISK_ACCEPTED,
        ];
    }

    private function resolvedTerminalOutcomeKey(string $reason): ?string
    {
        if (Finding::isSystemResolveReason($reason)) {
            return self::OUTCOME_VERIFIED_CLEARED;
        }

        if (Finding::isManualResolveReason($reason)) {
            return self::OUTCOME_RESOLVED_PENDING_VERIFICATION;
        }

        return null;
    }

    private function closedTerminalOutcomeKey(string $reason): ?string
    {
        return match ($reason) {
            Finding::CLOSE_REASON_FALSE_POSITIVE => self::OUTCOME_CLOSED_FALSE_POSITIVE,
            Finding::CLOSE_REASON_DUPLICATE => self::OUTCOME_CLOSED_DUPLICATE,
            Finding::CLOSE_REASON_NO_LONGER_APPLICABLE => self::OUTCOME_CLOSED_NO_LONGER_APPLICABLE,
            default => null,
        };
    }
}
