<?php

declare(strict_types=1);

namespace App\Services\EnvironmentReviews;

use App\Models\EvidenceSnapshot;
use App\Models\EvidenceSnapshotItem;
use App\Support\Findings\FindingOutcomeSemantics;
use App\Support\Governance\Controls\ComplianceEvidenceMappingV1;
use App\Support\EnvironmentReviewCompletenessState;
use Illuminate\Support\Arr;
use Illuminate\Support\Collection;

final class EnvironmentReviewSectionFactory
{
    public function __construct(
        private readonly FindingOutcomeSemantics $findingOutcomeSemantics,
        private readonly ComplianceEvidenceMappingV1 $complianceEvidenceMapping,
    ) {}

    /**
     * @return list<array<string, mixed>>
     */
    public function make(EvidenceSnapshot $snapshot): array
    {
        $items = $snapshot->items->keyBy('dimension_key');
        $findingsItem = $this->item($items, 'findings_summary');
        $permissionItem = $this->item($items, 'permission_posture');
        $rolesItem = $this->item($items, 'entra_admin_roles');
        $baselineItem = $this->item($items, 'baseline_drift_posture');
        $operationsItem = $this->item($items, 'operations_summary');

        $controlInterpretation = $this->complianceEvidenceMapping->interpret($snapshot, $findingsItem);

        return [
            $this->executiveSummarySection($snapshot, $findingsItem, $permissionItem, $rolesItem, $baselineItem, $operationsItem),
            $controlInterpretation['section'],
            $this->openRisksSection($findingsItem),
            $this->acceptedRisksSection($findingsItem),
            $this->permissionPostureSection($permissionItem, $rolesItem),
            $this->baselineDriftSection($baselineItem),
            $this->operationsHealthSection($operationsItem),
        ];
    }

    private function executiveSummarySection(
        EvidenceSnapshot $snapshot,
        ?EvidenceSnapshotItem $findingsItem,
        ?EvidenceSnapshotItem $permissionItem,
        ?EvidenceSnapshotItem $rolesItem,
        ?EvidenceSnapshotItem $baselineItem,
        ?EvidenceSnapshotItem $operationsItem,
    ): array {
        $findingsSummary = $this->summary($findingsItem);
        $permissionSummary = $this->summary($permissionItem);
        $rolesSummary = $this->summary($rolesItem);
        $baselineSummary = $this->summary($baselineItem);
        $operationsSummary = $this->summary($operationsItem);
        $findingOutcomes = is_array($findingsSummary['outcome_counts'] ?? null) ? $findingsSummary['outcome_counts'] : [];
        $findingReportBuckets = is_array($findingsSummary['report_bucket_counts'] ?? null) ? $findingsSummary['report_bucket_counts'] : [];
        $riskAcceptance = is_array($findingsSummary['risk_acceptance'] ?? null) ? $findingsSummary['risk_acceptance'] : [];
        $canonicalControls = is_array($findingsSummary['canonical_controls'] ?? null) ? $findingsSummary['canonical_controls'] : [];

        $openCount = (int) ($findingsSummary['open_count'] ?? 0);
        $findingCount = (int) ($findingsSummary['count'] ?? 0);
        $driftCount = (int) ($baselineSummary['open_drift_count'] ?? 0);
        $postureScore = $permissionSummary['posture_score'] ?? null;
        $operationFailures = (int) ($operationsSummary['failed_count'] ?? 0);
        $partialOperations = (int) ($operationsSummary['partial_count'] ?? 0);
        $outcomeSummary = $this->findingOutcomeSemantics->compactOutcomeSummary($findingOutcomes);

        $highlights = array_values(array_filter([
            sprintf('%d open risks from %d tracked findings.', $openCount, $findingCount),
            $outcomeSummary !== null ? 'Terminal outcomes: '.$outcomeSummary.'.' : null,
            $postureScore !== null ? sprintf('Permission posture score is %s.', $postureScore) : 'Permission posture report is unavailable.',
            sprintf('%d baseline drift findings remain open.', $driftCount),
            sprintf('%d recent operations failed and %d completed with warnings.', $operationFailures, $partialOperations),
            $canonicalControls !== [] ? sprintf('%d canonical controls are referenced by the findings evidence.', count($canonicalControls)) : null,
            sprintf('%d risk-accepted findings are currently governed.', (int) ($riskAcceptance['valid_governed_count'] ?? 0)),
            sprintf('%d privileged Entra roles are captured in the evidence basis.', (int) ($rolesSummary['role_count'] ?? 0)),
        ]));

        return [
            'section_key' => 'executive_summary',
            'title' => 'Executive summary',
            'sort_order' => 10,
            'required' => true,
            'completeness_state' => $this->maxState([
                $this->state($findingsItem),
                $this->state($permissionItem),
                $this->state($rolesItem),
                $this->state($baselineItem),
                $this->state($operationsItem),
            ])->value,
            'source_snapshot_fingerprint' => (string) $snapshot->fingerprint,
            'summary_payload' => [
                'finding_count' => $findingCount,
                'open_risk_count' => $openCount,
                'finding_outcomes' => $findingOutcomes,
                'finding_report_buckets' => $findingReportBuckets,
                'posture_score' => $postureScore,
                'baseline_drift_count' => $driftCount,
                'failed_operation_count' => $operationFailures,
                'partial_operation_count' => $partialOperations,
                'canonical_control_count' => count($canonicalControls),
                'canonical_controls' => $canonicalControls,
                'risk_acceptance' => $riskAcceptance,
            ],
            'render_payload' => [
                'highlights' => $highlights,
                'next_actions' => $this->nextActions(
                    openCount: $openCount,
                    driftCount: $driftCount,
                    operationFailures: $operationFailures,
                    postureScore: is_numeric($postureScore) ? (int) $postureScore : null,
                    riskWarnings: (int) ($riskAcceptance['warning_count'] ?? 0),
                ),
                'included_dimensions' => collect($snapshot->items)
                    ->map(static fn (EvidenceSnapshotItem $item): array => [
                        'key' => (string) $item->dimension_key,
                        'state' => (string) $item->state,
                        'required' => (bool) $item->required,
                    ])
                    ->values()
                    ->all(),
                'artifact_sources' => $this->artifactSourceSummaries(
                    $findingsItem,
                    $permissionItem,
                    $rolesItem,
                    $baselineItem,
                    $operationsItem,
                ),
            ],
            'measured_at' => $snapshot->generated_at,
        ];
    }

    private function openRisksSection(?EvidenceSnapshotItem $findingsItem): array
    {
        $summary = $this->summary($findingsItem);
        $entries = collect(Arr::wrap($summary['entries'] ?? []))
            ->filter(static fn (mixed $entry): bool => is_array($entry) && in_array((string) ($entry['status'] ?? ''), ['open', 'new', 'triaged', 'in_progress', 'reopened'], true))
            ->sortByDesc(static fn (array $entry): int => match ((string) ($entry['severity'] ?? 'low')) {
                'critical' => 4,
                'high' => 3,
                'medium' => 2,
                default => 1,
            })
            ->take(5)
            ->values()
            ->all();

        return [
            'section_key' => 'open_risks',
            'title' => 'Open risk highlights',
            'sort_order' => 20,
            'required' => true,
            'completeness_state' => $this->state($findingsItem)->value,
            'source_snapshot_fingerprint' => $this->sourceFingerprint($findingsItem),
            'summary_payload' => [
                'open_count' => (int) ($summary['open_count'] ?? 0),
                'severity_counts' => is_array($summary['severity_counts'] ?? null) ? $summary['severity_counts'] : [],
                'canonical_controls' => $this->canonicalControlsFromEntries($entries),
            ],
            'render_payload' => [
                'entries' => $entries,
                'empty_state' => empty($entries) ? 'No open risks are recorded in the anchored evidence basis.' : null,
                'artifact_sources' => $this->artifactSourceSummaries($findingsItem),
            ],
            'measured_at' => $findingsItem?->measured_at,
        ];
    }

    private function acceptedRisksSection(?EvidenceSnapshotItem $findingsItem): array
    {
        $summary = $this->summary($findingsItem);
        $entries = collect(Arr::wrap($summary['entries'] ?? []))
            ->filter(static fn (mixed $entry): bool => is_array($entry) && (string) ($entry['status'] ?? '') === 'risk_accepted')
            ->take(5)
            ->values()
            ->all();
        $riskAcceptance = is_array($summary['risk_acceptance'] ?? null) ? $summary['risk_acceptance'] : [];

        return [
            'section_key' => 'accepted_risks',
            'title' => 'Accepted risk summary',
            'sort_order' => 30,
            'required' => true,
            'completeness_state' => $this->state($findingsItem)->value,
            'source_snapshot_fingerprint' => $this->sourceFingerprint($findingsItem),
            'summary_payload' => [
                'status_marked_count' => (int) ($riskAcceptance['status_marked_count'] ?? 0),
                'valid_governed_count' => (int) ($riskAcceptance['valid_governed_count'] ?? 0),
                'warning_count' => (int) ($riskAcceptance['warning_count'] ?? 0),
                'expired_count' => (int) ($riskAcceptance['expired_count'] ?? 0),
                'revoked_count' => (int) ($riskAcceptance['revoked_count'] ?? 0),
                'missing_exception_count' => (int) ($riskAcceptance['missing_exception_count'] ?? 0),
                'canonical_controls' => $this->canonicalControlsFromEntries($entries),
            ],
            'render_payload' => [
                'entries' => $entries,
                'disclosure' => (int) ($riskAcceptance['warning_count'] ?? 0) > 0
                    ? 'Some accepted risks need governance follow-up before stakeholder delivery.'
                    : 'Accepted risks are governed by the anchored evidence basis.',
                'artifact_sources' => $this->artifactSourceSummaries($findingsItem),
            ],
            'measured_at' => $findingsItem?->measured_at,
        ];
    }

    private function permissionPostureSection(?EvidenceSnapshotItem $permissionItem, ?EvidenceSnapshotItem $rolesItem): array
    {
        $permissionSummary = $this->summary($permissionItem);
        $rolesSummary = $this->summary($rolesItem);

        return [
            'section_key' => 'permission_posture',
            'title' => 'Permission posture',
            'sort_order' => 40,
            'required' => true,
            'completeness_state' => $this->maxState([
                $this->state($permissionItem),
                $this->state($rolesItem),
            ])->value,
            'source_snapshot_fingerprint' => $this->sourceFingerprint($permissionItem) ?? $this->sourceFingerprint($rolesItem),
            'summary_payload' => [
                'posture_score' => $permissionSummary['posture_score'] ?? null,
                'required_count' => (int) ($permissionSummary['required_count'] ?? 0),
                'granted_count' => (int) ($permissionSummary['granted_count'] ?? 0),
                'role_count' => (int) ($rolesSummary['role_count'] ?? 0),
            ],
            'render_payload' => [
                'permission_payload' => is_array($permissionSummary['payload'] ?? null) ? $permissionSummary['payload'] : [],
                'roles' => is_array($rolesSummary['roles'] ?? null) ? $rolesSummary['roles'] : [],
                'artifact_sources' => $this->artifactSourceSummaries($permissionItem, $rolesItem),
            ],
            'measured_at' => $permissionItem?->measured_at ?? $rolesItem?->measured_at,
        ];
    }

    private function baselineDriftSection(?EvidenceSnapshotItem $baselineItem): array
    {
        $summary = $this->summary($baselineItem);

        return [
            'section_key' => 'baseline_drift_posture',
            'title' => 'Baseline drift posture',
            'sort_order' => 50,
            'required' => true,
            'completeness_state' => $this->state($baselineItem)->value,
            'source_snapshot_fingerprint' => $this->sourceFingerprint($baselineItem),
            'summary_payload' => [
                'drift_count' => (int) ($summary['drift_count'] ?? 0),
                'open_drift_count' => (int) ($summary['open_drift_count'] ?? 0),
            ],
            'render_payload' => [
                'disclosure' => (int) ($summary['open_drift_count'] ?? 0) > 0
                    ? 'Baseline drift remains visible in this review and should be discussed as hardening work.'
                    : 'No open baseline drift findings are present in the anchored evidence basis.',
                'artifact_sources' => $this->artifactSourceSummaries($baselineItem),
            ],
            'measured_at' => $baselineItem?->measured_at,
        ];
    }

    private function operationsHealthSection(?EvidenceSnapshotItem $operationsItem): array
    {
        $summary = $this->summary($operationsItem);

        return [
            'section_key' => 'operations_health',
            'title' => 'Operations health',
            'sort_order' => 60,
            'required' => true,
            'completeness_state' => $this->state($operationsItem)->value,
            'source_snapshot_fingerprint' => $this->sourceFingerprint($operationsItem),
            'summary_payload' => [
                'operation_count' => (int) ($summary['operation_count'] ?? 0),
                'failed_count' => (int) ($summary['failed_count'] ?? 0),
                'partial_count' => (int) ($summary['partial_count'] ?? 0),
            ],
            'render_payload' => [
                'entries' => array_values(array_slice(Arr::wrap($summary['entries'] ?? []), 0, 10)),
                'artifact_sources' => $this->artifactSourceSummaries($operationsItem),
            ],
            'measured_at' => $operationsItem?->measured_at,
        ];
    }

    /**
     * @return list<array{label: string, source_family: string, source_kind: string, source_target_kind: string, control_key: ?string, detector_key: ?string}>
     */
    private function artifactSourceSummaries(?EvidenceSnapshotItem ...$items): array
    {
        return collect($items)
            ->filter(static fn (?EvidenceSnapshotItem $item): bool => $item instanceof EvidenceSnapshotItem)
            ->map(function (EvidenceSnapshotItem $item): array {
                $descriptor = $item->artifactSourceDescriptor();

                return [
                    'label' => (string) $item->dimension_key,
                    'source_family' => $descriptor->sourceFamily,
                    'source_kind' => $descriptor->sourceKind,
                    'source_target_kind' => $descriptor->sourceTargetKind,
                    'control_key' => $descriptor->controlKey,
                    'detector_key' => $descriptor->detectorKey,
                ];
            })
            ->unique(static fn (array $item): string => implode('|', [
                $item['source_family'],
                $item['source_kind'],
                $item['source_target_kind'],
                $item['control_key'] ?? '',
                $item['detector_key'] ?? '',
            ]))
            ->values()
            ->all();
    }

    private function item(Collection $items, string $key): ?EvidenceSnapshotItem
    {
        $item = $items->get($key);

        return $item instanceof EvidenceSnapshotItem ? $item : null;
    }

    /**
     * @return array<string, mixed>
     */
    private function summary(?EvidenceSnapshotItem $item): array
    {
        return is_array($item?->summary_payload) ? $item->summary_payload : [];
    }

    private function state(?EvidenceSnapshotItem $item): EnvironmentReviewCompletenessState
    {
        return EnvironmentReviewCompletenessState::tryFrom((string) $item?->state)
            ?? EnvironmentReviewCompletenessState::Missing;
    }

    private function sourceFingerprint(?EvidenceSnapshotItem $item): ?string
    {
        $fingerprint = $item?->source_fingerprint;

        return is_string($fingerprint) && $fingerprint !== '' ? $fingerprint : null;
    }

    /**
     * @param  list<array<string, mixed>>  $entries
     * @return list<array<string, mixed>>
     */
    private function canonicalControlsFromEntries(array $entries): array
    {
        return collect($entries)
            ->map(static fn (array $entry): mixed => data_get($entry, 'canonical_control_resolution.control'))
            ->filter(static fn (mixed $control): bool => is_array($control) && filled($control['control_key'] ?? null))
            ->unique(static fn (array $control): string => (string) $control['control_key'])
            ->values()
            ->all();
    }

    /**
     * @param  array<int, EnvironmentReviewCompletenessState>  $states
     */
    private function maxState(array $states): EnvironmentReviewCompletenessState
    {
        if (in_array(EnvironmentReviewCompletenessState::Missing, $states, true)) {
            return EnvironmentReviewCompletenessState::Missing;
        }

        if (in_array(EnvironmentReviewCompletenessState::Stale, $states, true)) {
            return EnvironmentReviewCompletenessState::Stale;
        }

        if (in_array(EnvironmentReviewCompletenessState::Partial, $states, true)) {
            return EnvironmentReviewCompletenessState::Partial;
        }

        return EnvironmentReviewCompletenessState::Complete;
    }

    /**
     * @return list<string>
     */
    private function nextActions(
        int $openCount,
        int $driftCount,
        int $operationFailures,
        ?int $postureScore,
        int $riskWarnings,
    ): array {
        $actions = [];

        if ($openCount > 0) {
            $actions[] = 'Review the highest-severity open findings with the tenant and confirm ownership.';
        }

        if ($riskWarnings > 0) {
            $actions[] = 'Reconcile accepted-risk governance records before external delivery.';
        }

        if ($postureScore !== null && $postureScore < 80) {
            $actions[] = 'Prioritize missing permissions or posture controls that materially affect review confidence.';
        }

        if ($driftCount > 0) {
            $actions[] = 'Schedule remediation for recurring baseline drift to reduce repeated review findings.';
        }

        if ($operationFailures > 0) {
            $actions[] = 'Inspect recent failed operations to confirm tenant management workflows are stable.';
        }

        if ($actions === []) {
            $actions[] = 'No immediate corrective action is required beyond the normal review cadence.';
        }

        return $actions;
    }
}
