<?php

declare(strict_types=1);

namespace App\Services\EnvironmentReviews;

use App\Models\EvidenceSnapshot;
use App\Models\EnvironmentReview;
use App\Support\EnvironmentReviewStatus;

final class EnvironmentReviewComposer
{
    public function __construct(
        private readonly EnvironmentReviewFingerprint $fingerprint,
        private readonly EnvironmentReviewSectionFactory $sectionFactory,
        private readonly EnvironmentReviewReadinessGate $readinessGate,
    ) {}

    /**
     * @return array{
     *     fingerprint: string,
     *     completeness_state: string,
     *     status: string,
     *     summary: array<string, mixed>,
     *     sections: list<array<string, mixed>>
     * }
     */
    public function compose(EvidenceSnapshot $snapshot, ?EnvironmentReview $review = null): array
    {
        $tenant = $snapshot->tenant;

        if ($tenant === null) {
            throw new \RuntimeException('Evidence snapshot tenant is required for review composition.');
        }

        $sections = $this->sectionFactory->make($snapshot);
        $blockers = $this->readinessGate->blockersForSections($sections);
        $sectionStateCounts = $this->readinessGate->sectionStateCounts($sections);
        $completeness = $this->readinessGate->completenessForSections($sections);
        $status = $this->readinessGate->statusForSections($sections);
        $executiveSummarySection = collect($sections)
            ->firstWhere('section_key', 'executive_summary');
        $controlInterpretationSection = collect($sections)
            ->firstWhere('section_key', 'control_interpretation');
        $openRisksSection = collect($sections)
            ->firstWhere('section_key', 'open_risks');
        $acceptedRisksSection = collect($sections)
            ->firstWhere('section_key', 'accepted_risks');
        $operationsSection = collect($sections)
            ->firstWhere('section_key', 'operations_health');

        if ($review instanceof EnvironmentReview && $review->isPublished()) {
            $status = EnvironmentReviewStatus::Published;
        }

        return [
            'fingerprint' => $this->fingerprint->forSnapshot($tenant, $snapshot),
            'completeness_state' => $completeness->value,
            'status' => $status->value,
            'summary' => [
                'evidence_basis' => [
                    'snapshot_id' => (int) $snapshot->getKey(),
                    'snapshot_fingerprint' => (string) $snapshot->fingerprint,
                    'snapshot_completeness_state' => (string) $snapshot->completeness_state,
                    'snapshot_generated_at' => $snapshot->generated_at?->toIso8601String(),
                ],
                'section_count' => count($sections),
                'section_state_counts' => $sectionStateCounts,
                'publish_blockers' => $blockers,
                'has_ready_export' => false,
                'finding_count' => (int) data_get($sections, '0.summary_payload.finding_count', 0),
                'finding_outcomes' => is_array(data_get($sections, '0.summary_payload.finding_outcomes'))
                    ? data_get($sections, '0.summary_payload.finding_outcomes')
                    : [],
                'finding_report_buckets' => is_array(data_get($sections, '0.summary_payload.finding_report_buckets'))
                    ? data_get($sections, '0.summary_payload.finding_report_buckets')
                    : [],
                'canonical_controls' => is_array(data_get($sections, '0.summary_payload.canonical_controls'))
                    ? data_get($sections, '0.summary_payload.canonical_controls')
                    : [],
                'control_interpretation' => is_array(data_get($controlInterpretationSection, 'summary_payload'))
                    ? array_merge(
                        data_get($controlInterpretationSection, 'summary_payload'),
                        [
                            'controls' => is_array(data_get($controlInterpretationSection, 'render_payload.entries'))
                                ? data_get($controlInterpretationSection, 'render_payload.entries')
                                : [],
                        ],
                    )
                    : [],
                'report_count' => 2,
                'operation_count' => (int) data_get($operationsSection, 'summary_payload.operation_count', 0),
                'highlights' => data_get($sections, '0.render_payload.highlights', []),
                'recommended_next_actions' => data_get($sections, '0.render_payload.next_actions', []),
                'governance_package' => $this->governancePackageSummary(
                    snapshot: $snapshot,
                    executiveSummarySection: is_array($executiveSummarySection) ? $executiveSummarySection : [],
                    controlInterpretationSection: is_array($controlInterpretationSection) ? $controlInterpretationSection : [],
                    openRisksSection: is_array($openRisksSection) ? $openRisksSection : [],
                    acceptedRisksSection: is_array($acceptedRisksSection) ? $acceptedRisksSection : [],
                ),
                'last_composed_at' => now()->toIso8601String(),
            ],
            'sections' => $sections,
        ];
    }

    /**
     * @param  array<string, mixed>  $executiveSummarySection
     * @param  array<string, mixed>  $controlInterpretationSection
     * @param  array<string, mixed>  $openRisksSection
     * @param  array<string, mixed>  $acceptedRisksSection
     * @return array<string, mixed>
     */
    private function governancePackageSummary(
        EvidenceSnapshot $snapshot,
        array $executiveSummarySection,
        array $controlInterpretationSection,
        array $openRisksSection,
        array $acceptedRisksSection,
    ): array {
        $executiveSummaryPayload = is_array($executiveSummarySection['summary_payload'] ?? null)
            ? $executiveSummarySection['summary_payload']
            : [];
        $executiveRenderPayload = is_array($executiveSummarySection['render_payload'] ?? null)
            ? $executiveSummarySection['render_payload']
            : [];
        $controlInterpretationSummary = is_array($controlInterpretationSection['summary_payload'] ?? null)
            ? $controlInterpretationSection['summary_payload']
            : [];
        $openRiskEntries = collect(data_get($openRisksSection, 'render_payload.entries', []))
            ->filter(static fn (mixed $entry): bool => is_array($entry))
            ->take(3)
            ->map(fn (array $entry): array => $this->packageFindingEntry($entry))
            ->values()
            ->all();
        $acceptedRiskEntries = collect(data_get($acceptedRisksSection, 'render_payload.entries', []))
            ->filter(static fn (mixed $entry): bool => is_array($entry))
            ->map(fn (array $entry): array => $this->packageAcceptedRiskEntry($entry))
            ->values();
        $governanceDecisionEntries = $acceptedRiskEntries
            ->filter(fn (array $entry): bool => $this->requiresGovernanceDecisionFollowUp($entry))
            ->values();
        $stableAcceptedRiskEntries = $acceptedRiskEntries
            ->reject(fn (array $entry): bool => $this->requiresGovernanceDecisionFollowUp($entry))
            ->values();
        $governanceDecisions = $governanceDecisionEntries
            ->map(fn (array $entry): array => $this->packageGovernanceDecisionEntry($entry))
            ->values()
            ->all();
        $decisionSummary = $this->packageDecisionSummary(
            snapshot: $snapshot,
            acceptedRisksSection: $acceptedRisksSection,
            governanceDecisions: $governanceDecisions,
        );

        return [
            'delivery_artifact_family' => 'review_pack',
            'interpretation_version' => is_string($controlInterpretationSummary['version_key'] ?? null)
                ? $controlInterpretationSummary['version_key']
                : null,
            'executive_summary' => $this->governancePackageExecutiveSummary(
                executiveSummaryPayload: $executiveSummaryPayload,
                executiveRenderPayload: $executiveRenderPayload,
                controlInterpretationSummary: $controlInterpretationSummary,
                acceptedRiskCount: $acceptedRiskEntries->count(),
            ),
            'top_findings' => $openRiskEntries,
            'accepted_risks' => $stableAcceptedRiskEntries->all(),
            'governance_decisions' => $governanceDecisions,
            'decision_summary' => $decisionSummary,
            'evidence_basis_summary' => $this->governancePackageEvidenceBasisSummary(
                snapshot: $snapshot,
                controlInterpretationSummary: $controlInterpretationSummary,
            ),
            'supporting_artifact_links' => [
                [
                    'artifact_family' => 'evidence_snapshot',
                    'artifact_key' => 'evidence_snapshot:'.$snapshot->getKey(),
                    'purpose' => 'evidence_basis',
                ],
                [
                    'artifact_family' => 'review_pack',
                    'artifact_key' => 'review_pack:current_export',
                    'purpose' => 'stakeholder_delivery',
                ],
            ],
        ];
    }

    /**
     * @param  array<string, mixed>  $entry
     * @return array<string, mixed>
     */
    private function packageFindingEntry(array $entry): array
    {
        return [
            'finding_id' => is_numeric($entry['id'] ?? null) ? (int) $entry['id'] : null,
            'title' => $this->entryTitle($entry, 'Open finding'),
            'severity' => is_string($entry['severity'] ?? null) ? $entry['severity'] : 'unknown',
            'status' => is_string($entry['status'] ?? null) ? $entry['status'] : 'unknown',
            'summary' => $this->entrySummary($entry, 'This finding remains open in the released review and should be discussed in stakeholder delivery.'),
        ];
    }

    /**
     * @param  array<string, mixed>  $entry
     * @return array<string, mixed>
     */
    private function packageAcceptedRiskEntry(array $entry): array
    {
        return [
            'finding_id' => is_numeric($entry['id'] ?? null) ? (int) $entry['id'] : null,
            'title' => $this->entryTitle($entry, 'Accepted risk'),
            'governance_state' => is_string($entry['governance_state'] ?? null) ? $entry['governance_state'] : 'unknown',
            'summary' => $this->entrySummary($entry, 'This accepted-risk entry qualifies the current governance position for stakeholder delivery.'),
            'owner_label' => $this->ownerLabel($entry),
        ];
    }

    /**
     * @param  array<string, mixed>  $entry
     */
    private function requiresGovernanceDecisionFollowUp(array $entry): bool
    {
        return in_array((string) ($entry['governance_state'] ?? ''), [
            'expired_exception',
            'revoked_exception',
            'risk_accepted_without_valid_exception',
        ], true);
    }

    /**
     * @param  array<string, mixed>  $entry
     * @return array<string, mixed>
     */
    private function packageGovernanceDecisionEntry(array $entry): array
    {
        $governanceState = (string) ($entry['governance_state'] ?? 'unknown');

        return [
            'finding_id' => $entry['finding_id'] ?? null,
            'title' => $entry['title'] ?? 'Governance decision',
            'governance_state' => $governanceState,
            'awareness_reason' => $this->governanceDecisionAwarenessReason($governanceState),
            'summary' => match ($governanceState) {
                'expired_exception' => 'The accepted-risk exception has expired and needs follow-up before stakeholder delivery.',
                'revoked_exception' => 'The accepted-risk exception was revoked and needs follow-up before stakeholder delivery.',
                'risk_accepted_without_valid_exception' => 'The accepted-risk entry has no currently valid exception basis and needs follow-up before stakeholder delivery.',
                default => 'This governance decision needs follow-up before stakeholder delivery.',
            },
            'next_action' => $this->governanceDecisionNextAction($governanceState),
            'evidence_basis' => 'Included in the anchored released-review evidence basis.',
        ];
    }

    /**
     * @param  array<string, mixed>  $acceptedRisksSection
     * @param  list<array<string, mixed>>  $governanceDecisions
     * @return array<string, mixed>
     */
    private function packageDecisionSummary(
        EvidenceSnapshot $snapshot,
        array $acceptedRisksSection,
        array $governanceDecisions,
    ): array {
        $totalCount = count($governanceDecisions);
        $evidenceState = is_string($acceptedRisksSection['completeness_state'] ?? null)
            ? (string) $acceptedRisksSection['completeness_state']
            : (string) $snapshot->completeness_state;
        $decisionDataState = $totalCount > 0 || $this->decisionEvidenceIsAvailable($evidenceState)
            ? 'available'
            : 'incomplete';
        $status = match (true) {
            $totalCount > 0 => 'requires_awareness',
            $decisionDataState === 'incomplete' => 'unavailable',
            default => 'none',
        };

        return [
            'customer_safe' => true,
            'status' => $status,
            'decision_data_state' => $decisionDataState,
            'evidence_state' => $evidenceState,
            'total_count' => $totalCount,
            'requires_awareness' => $totalCount > 0,
            'summary' => $this->decisionSummaryText($status, $totalCount),
            'empty_state' => $this->decisionSummaryEmptyState($status),
            'next_action' => $this->decisionSummaryNextAction($status),
            'evidence_basis' => sprintf(
                'Anchored to evidence snapshot #%d with %s decision-evidence completeness.',
                (int) $snapshot->getKey(),
                $evidenceState,
            ),
            'source_section_state' => is_string($acceptedRisksSection['completeness_state'] ?? null)
                ? $acceptedRisksSection['completeness_state']
                : null,
            'entries' => $governanceDecisions,
        ];
    }

    private function decisionEvidenceIsAvailable(string $evidenceState): bool
    {
        return in_array($evidenceState, ['complete'], true);
    }

    private function decisionSummaryText(string $status, int $totalCount): string
    {
        return match ($status) {
            'requires_awareness' => sprintf(
                '%d governance decision%s require%s customer awareness before relying on this released review.',
                $totalCount,
                $totalCount === 1 ? '' : 's',
                $totalCount === 1 ? 's' : '',
            ),
            'unavailable' => 'Decision evidence is incomplete for this released review; no customer-aware decisions can be confirmed from the current evidence basis.',
            default => 'No governance decisions require customer awareness in this released review.',
        };
    }

    private function decisionSummaryEmptyState(string $status): string
    {
        return match ($status) {
            'unavailable' => 'Decision evidence is incomplete in the current released-review basis.',
            'requires_awareness' => '',
            default => 'No governance decisions require awareness in this released review.',
        };
    }

    private function decisionSummaryNextAction(string $status): string
    {
        return match ($status) {
            'requires_awareness' => 'Review the accepted-risk decision basis before customer delivery.',
            'unavailable' => 'Open the review evidence before treating the decision register summary as complete.',
            default => 'No customer action is needed for Decision Register follow-up from this review.',
        };
    }

    private function governanceDecisionAwarenessReason(string $governanceState): string
    {
        return match ($governanceState) {
            'expired_exception' => 'The accepted-risk approval has expired and needs customer awareness before the review is relied on.',
            'revoked_exception' => 'The accepted-risk approval was revoked and needs customer awareness before the review is relied on.',
            'risk_accepted_without_valid_exception' => 'The accepted risk has no valid governance backing in the released review evidence.',
            default => 'This accepted-risk governance decision needs customer awareness before the review is relied on.',
        };
    }

    private function governanceDecisionNextAction(string $governanceState): string
    {
        return match ($governanceState) {
            'expired_exception', 'revoked_exception', 'risk_accepted_without_valid_exception' => 'Confirm whether this accepted risk should be renewed, remediated, or removed before relying on the review.',
            default => 'Confirm the accepted-risk decision before relying on the review.',
        };
    }

    /**
     * @param  array<string, mixed>  $executiveSummaryPayload
     * @param  array<string, mixed>  $executiveRenderPayload
     * @param  array<string, mixed>  $controlInterpretationSummary
     */
    private function governancePackageExecutiveSummary(
        array $executiveSummaryPayload,
        array $executiveRenderPayload,
        array $controlInterpretationSummary,
        int $acceptedRiskCount,
    ): string {
        $highlights = collect($executiveRenderPayload['highlights'] ?? [])
            ->filter(static fn (mixed $highlight): bool => is_string($highlight) && trim($highlight) !== '')
            ->values();

        if ($highlights->isNotEmpty()) {
            return (string) $highlights->first();
        }

        return sprintf(
            'This released review summarizes %d mapped control(s), %d open risk(s), and %d accepted-risk item(s) from the anchored evidence basis.',
            (int) ($controlInterpretationSummary['mapped_control_count'] ?? 0),
            (int) ($executiveSummaryPayload['open_risk_count'] ?? 0),
            $acceptedRiskCount,
        );
    }

    /**
     * @param  array<string, mixed>  $controlInterpretationSummary
     */
    private function governancePackageEvidenceBasisSummary(EvidenceSnapshot $snapshot, array $controlInterpretationSummary): string
    {
        return sprintf(
            'Anchored to evidence snapshot #%d with %s completeness and %d mapped control(s).',
            (int) $snapshot->getKey(),
            (string) $snapshot->completeness_state,
            (int) ($controlInterpretationSummary['mapped_control_count'] ?? 0),
        );
    }

    /**
     * @param  array<string, mixed>  $entry
     */
    private function entryTitle(array $entry, string $fallback): string
    {
        foreach (['title', 'name', 'finding_title'] as $key) {
            $value = $entry[$key] ?? null;

            if (is_string($value) && trim($value) !== '') {
                return $value;
            }
        }

        return $fallback;
    }

    /**
     * @param  array<string, mixed>  $entry
     */
    private function entrySummary(array $entry, string $fallback): string
    {
        foreach (['customer_summary', 'summary', 'request_reason'] as $key) {
            $value = $entry[$key] ?? null;

            if (is_string($value) && trim($value) !== '') {
                return $value;
            }
        }

        return $fallback;
    }

    /**
     * @param  array<string, mixed>  $entry
     */
    private function ownerLabel(array $entry): ?string
    {
        $owner = $entry['owner'] ?? null;

        if (is_array($owner)) {
            $name = $owner['name'] ?? null;

            if (is_string($name) && trim($name) !== '') {
                return $name;
            }
        }

        return null;
    }
}
