<?php

declare(strict_types=1);

namespace App\Support\BackupQuality;

use App\Models\BackupItem;
use App\Models\BackupSet;
use App\Models\PolicyVersion;
use Illuminate\Support\Collection;
use Illuminate\Support\Str;

final class BackupQualityResolver
{
    public function summarizeBackupSet(BackupSet $backupSet): BackupQualitySummary
    {
        $items = $backupSet->relationLoaded('items')
            ? $backupSet->items
            : $backupSet->items()->get();

        return $this->summarizeBackupItems(
            $items,
            max((int) ($backupSet->item_count ?? 0), $items->count()),
        );
    }

    /**
     * @param  iterable<BackupItem>  $items
     */
    public function summarizeBackupItems(iterable $items, ?int $totalItems = null): BackupQualitySummary
    {
        $itemSummaries = Collection::make($items)
            ->map(fn (BackupItem $item): BackupQualitySummary => $this->forBackupItem($item))
            ->values();

        $resolvedTotalItems = max($itemSummaries->count(), (int) ($totalItems ?? 0));
        $metadataOnlyCount = $itemSummaries->where('metadataOnlyCount', '>', 0)->count();
        $assignmentIssueCount = $itemSummaries->where('assignmentIssueCount', '>', 0)->count();
        $orphanedAssignmentCount = $itemSummaries->where('orphanedAssignmentCount', '>', 0)->count();
        $integrityWarningCount = $itemSummaries->where('integrityWarningCount', '>', 0)->count();
        $unknownQualityCount = $itemSummaries->where('unknownQualityCount', '>', 0)->count();
        $degradedItemCount = $itemSummaries->filter(
            fn (BackupQualitySummary $summary): bool => $summary->hasDegradations()
        )->count();

        $degradationFamilies = $this->orderedFamilies(
            $itemSummaries
                ->flatMap(fn (BackupQualitySummary $summary): array => $summary->degradationFamilies)
                ->all(),
        );

        $qualityHighlights = $this->setHighlights(
            totalItems: $resolvedTotalItems,
            degradedItemCount: $degradedItemCount,
            metadataOnlyCount: $metadataOnlyCount,
            assignmentIssueCount: $assignmentIssueCount,
            orphanedAssignmentCount: $orphanedAssignmentCount,
            integrityWarningCount: $integrityWarningCount,
            unknownQualityCount: $unknownQualityCount,
        );

        $compactSummary = $qualityHighlights === []
            ? $this->defaultSetCompactSummary($resolvedTotalItems)
            : implode(' • ', $qualityHighlights);

        $summaryMessage = match (true) {
            $resolvedTotalItems === 0 => 'No backup items were captured in this set.',
            $degradedItemCount === 0 => sprintf(
                'No degradations were detected across %d captured item%s.',
                $resolvedTotalItems,
                $resolvedTotalItems === 1 ? '' : 's',
            ),
            default => sprintf(
                '%d of %d captured item%s show degraded input quality.',
                $degradedItemCount,
                $resolvedTotalItems,
                $resolvedTotalItems === 1 ? '' : 's',
            ),
        };

        $nextAction = match (true) {
            $resolvedTotalItems === 0 => 'Create or refresh a backup set before starting a restore review.',
            $degradedItemCount > 0 => 'Open the backup set detail and inspect degraded items before continuing into restore.',
            default => 'Open the backup set detail to verify item-level context before relying on it for restore work.',
        };

        return new BackupQualitySummary(
            kind: 'backup_set',
            snapshotMode: $this->aggregateSnapshotMode($resolvedTotalItems, $metadataOnlyCount, $unknownQualityCount),
            totalItems: $resolvedTotalItems,
            degradedItemCount: $degradedItemCount,
            metadataOnlyCount: $metadataOnlyCount,
            assignmentIssueCount: $assignmentIssueCount,
            orphanedAssignmentCount: $orphanedAssignmentCount,
            integrityWarningCount: $integrityWarningCount,
            unknownQualityCount: $unknownQualityCount,
            hasAssignmentIssues: $assignmentIssueCount > 0,
            hasOrphanedAssignments: $orphanedAssignmentCount > 0,
            assignmentCaptureReason: null,
            integrityWarning: null,
            degradationFamilies: $degradationFamilies,
            qualityHighlights: $qualityHighlights,
            compactSummary: $compactSummary,
            summaryMessage: $summaryMessage,
            nextAction: $nextAction,
            positiveClaimBoundary: $this->positiveClaimBoundary(),
        );
    }

    public function forBackupItem(BackupItem $backupItem): BackupQualitySummary
    {
        $snapshotMode = $this->resolveSnapshotMode(
            source: $backupItem->snapshotSource(),
            warnings: $backupItem->warningMessages(),
            hasCapturedPayload: $backupItem->hasCapturedPayload(),
        );

        $assignmentCaptureReason = $backupItem->assignmentCaptureReason();
        $integrityWarning = $backupItem->integrityWarning();
        $hasAssignmentIssues = $backupItem->assignmentsFetchFailed();
        $hasOrphanedAssignments = $backupItem->hasOrphanedAssignments();

        $degradationFamilies = $this->singleRecordFamilies(
            snapshotMode: $snapshotMode,
            hasAssignmentIssues: $hasAssignmentIssues,
            hasOrphanedAssignments: $hasOrphanedAssignments,
            integrityWarning: $integrityWarning,
        );

        $qualityHighlights = $this->singleRecordHighlights(
            snapshotMode: $snapshotMode,
            hasAssignmentIssues: $hasAssignmentIssues,
            hasOrphanedAssignments: $hasOrphanedAssignments,
            integrityWarning: $integrityWarning,
            assignmentCaptureReason: $assignmentCaptureReason,
        );

        return new BackupQualitySummary(
            kind: 'backup_item',
            snapshotMode: $snapshotMode,
            totalItems: 1,
            degradedItemCount: $degradationFamilies === [] ? 0 : 1,
            metadataOnlyCount: $snapshotMode === 'metadata_only' ? 1 : 0,
            assignmentIssueCount: $hasAssignmentIssues ? 1 : 0,
            orphanedAssignmentCount: $hasOrphanedAssignments ? 1 : 0,
            integrityWarningCount: $integrityWarning !== null ? 1 : 0,
            unknownQualityCount: $degradationFamilies === ['unknown_quality'] ? 1 : 0,
            hasAssignmentIssues: $hasAssignmentIssues,
            hasOrphanedAssignments: $hasOrphanedAssignments,
            assignmentCaptureReason: $assignmentCaptureReason,
            integrityWarning: $integrityWarning,
            degradationFamilies: $degradationFamilies,
            qualityHighlights: $qualityHighlights,
            compactSummary: $this->compactSummaryFromHighlights($qualityHighlights, $snapshotMode),
            summaryMessage: $this->singleRecordSummaryMessage($qualityHighlights, $snapshotMode),
            nextAction: $degradationFamilies === []
                ? 'Open the linked detail if you need deeper restore context.'
                : 'Inspect the linked detail before relying on this backup item for restore.',
            positiveClaimBoundary: $this->positiveClaimBoundary(),
        );
    }

    public function forPolicyVersion(PolicyVersion $policyVersion): BackupQualitySummary
    {
        $snapshotMode = $this->resolveSnapshotMode(
            source: $policyVersion->snapshotSource(),
            warnings: $policyVersion->warningMessages(),
            hasCapturedPayload: $policyVersion->hasCapturedPayload(),
        );

        $integrityWarning = $policyVersion->integrityWarning();
        $hasAssignmentIssues = $policyVersion->assignmentsFetchFailed();
        $hasOrphanedAssignments = $policyVersion->hasOrphanedAssignments();

        $degradationFamilies = $this->singleRecordFamilies(
            snapshotMode: $snapshotMode,
            hasAssignmentIssues: $hasAssignmentIssues,
            hasOrphanedAssignments: $hasOrphanedAssignments,
            integrityWarning: $integrityWarning,
        );

        $qualityHighlights = $this->singleRecordHighlights(
            snapshotMode: $snapshotMode,
            hasAssignmentIssues: $hasAssignmentIssues,
            hasOrphanedAssignments: $hasOrphanedAssignments,
            integrityWarning: $integrityWarning,
        );

        return new BackupQualitySummary(
            kind: 'policy_version',
            snapshotMode: $snapshotMode,
            totalItems: 1,
            degradedItemCount: $degradationFamilies === [] ? 0 : 1,
            metadataOnlyCount: $snapshotMode === 'metadata_only' ? 1 : 0,
            assignmentIssueCount: $hasAssignmentIssues ? 1 : 0,
            orphanedAssignmentCount: $hasOrphanedAssignments ? 1 : 0,
            integrityWarningCount: $integrityWarning !== null ? 1 : 0,
            unknownQualityCount: $degradationFamilies === ['unknown_quality'] ? 1 : 0,
            hasAssignmentIssues: $hasAssignmentIssues,
            hasOrphanedAssignments: $hasOrphanedAssignments,
            assignmentCaptureReason: null,
            integrityWarning: $integrityWarning,
            degradationFamilies: $degradationFamilies,
            qualityHighlights: $qualityHighlights,
            compactSummary: $this->compactSummaryFromHighlights($qualityHighlights, $snapshotMode),
            summaryMessage: $this->singleRecordSummaryMessage($qualityHighlights, $snapshotMode),
            nextAction: $degradationFamilies === []
                ? $this->text('next_action_open_version_detail')
                : $this->text('next_action_prefer_stronger_version'),
            positiveClaimBoundary: $this->positiveClaimBoundary(),
        );
    }

    /**
     * @param  list<string>  $warnings
     */
    private function resolveSnapshotMode(?string $source, array $warnings, bool $hasCapturedPayload): string
    {
        if ($source === 'metadata_only' || $this->warningsIndicateMetadataOnly($warnings)) {
            return 'metadata_only';
        }

        if ($hasCapturedPayload) {
            return 'full';
        }

        return 'unknown';
    }

    /**
     * @param  list<string>  $warnings
     */
    private function warningsIndicateMetadataOnly(array $warnings): bool
    {
        return Collection::make($warnings)
            ->contains(function (mixed $warning): bool {
                if (! is_string($warning)) {
                    return false;
                }

                $normalized = Str::lower($warning);

                return str_contains($normalized, 'metadata')
                    && (
                        str_contains($normalized, 'only')
                        || str_contains($normalized, 'fallback')
                    );
            });
    }

    /**
     * @return list<string>
     */
    private function singleRecordFamilies(
        string $snapshotMode,
        bool $hasAssignmentIssues,
        bool $hasOrphanedAssignments,
        ?string $integrityWarning,
    ): array {
        $families = [];

        if ($snapshotMode === 'metadata_only') {
            $families[] = 'metadata_only';
        }

        if ($hasAssignmentIssues) {
            $families[] = 'assignment_capture_issue';
        }

        if ($hasOrphanedAssignments) {
            $families[] = 'orphaned_assignments';
        }

        if ($integrityWarning !== null) {
            $families[] = 'integrity_warning';
        }

        if ($families === [] && $snapshotMode === 'unknown') {
            $families[] = 'unknown_quality';
        }

        return $this->orderedFamilies($families);
    }

    /**
     * @return list<string>
     */
    private function singleRecordHighlights(
        string $snapshotMode,
        bool $hasAssignmentIssues,
        bool $hasOrphanedAssignments,
        ?string $integrityWarning,
        ?string $assignmentCaptureReason = null,
    ): array {
        $highlights = [];

        if ($snapshotMode === 'metadata_only') {
            $highlights[] = $this->text('quality_highlight_metadata_only');
        }

        if ($hasAssignmentIssues) {
            $highlights[] = $this->text('quality_highlight_assignment_fetch_failed');
        } elseif ($assignmentCaptureReason === 'separate_role_assignments') {
            $highlights[] = $this->text('quality_highlight_assignments_captured_separately');
        }

        if ($hasOrphanedAssignments) {
            $highlights[] = $this->text('quality_highlight_orphaned_assignments');
        }

        if ($integrityWarning !== null) {
            $highlights[] = $this->text('quality_highlight_integrity_warning');
        }

        if ($snapshotMode === 'unknown' && $highlights === []) {
            $highlights[] = $this->text('quality_highlight_unknown_quality');
        }

        return array_values(array_unique($highlights));
    }

    private function compactSummaryFromHighlights(array $qualityHighlights, string $snapshotMode): string
    {
        if ($qualityHighlights !== []) {
            return implode(' • ', $qualityHighlights);
        }

        return match ($snapshotMode) {
            'full' => $this->text('compact_summary_full_payload'),
            'unknown' => $this->text('compact_summary_unknown_quality'),
            default => $this->text('compact_summary_no_degradations_detected'),
        };
    }

    private function singleRecordSummaryMessage(array $qualityHighlights, string $snapshotMode): string
    {
        if ($qualityHighlights === []) {
            return match ($snapshotMode) {
                'full' => $this->text('summary_full_no_degradations'),
                'unknown' => $this->text('summary_unknown_quality'),
                default => $this->text('summary_no_degradations'),
            };
        }

        return implode(' • ', $qualityHighlights).'.';
    }

    private function text(string $key, array $replace = []): string
    {
        return __('localization.policy.versions.'.$key, $replace);
    }

    private function aggregateSnapshotMode(int $totalItems, int $metadataOnlyCount, int $unknownQualityCount): string
    {
        if ($totalItems === 0) {
            return 'unknown';
        }

        if ($metadataOnlyCount === $totalItems) {
            return 'metadata_only';
        }

        if ($metadataOnlyCount === 0 && $unknownQualityCount === 0) {
            return 'full';
        }

        return 'unknown';
    }

    /**
     * @return list<string>
     */
    private function orderedFamilies(array $families): array
    {
        $weights = [
            'metadata_only' => 10,
            'assignment_capture_issue' => 20,
            'orphaned_assignments' => 30,
            'integrity_warning' => 40,
            'unknown_quality' => 50,
        ];

        $families = array_values(array_unique(array_filter(
            $families,
            static fn (mixed $family): bool => is_string($family) && $family !== '',
        )));

        usort($families, static function (string $left, string $right) use ($weights): int {
            return ($weights[$left] ?? 999) <=> ($weights[$right] ?? 999);
        });

        return $families;
    }

    /**
     * @return list<string>
     */
    private function setHighlights(
        int $totalItems,
        int $degradedItemCount,
        int $metadataOnlyCount,
        int $assignmentIssueCount,
        int $orphanedAssignmentCount,
        int $integrityWarningCount,
        int $unknownQualityCount,
    ): array {
        if ($totalItems === 0) {
            return [];
        }

        $highlights = [];

        if ($degradedItemCount > 0) {
            $highlights[] = sprintf(
                '%d degraded item%s',
                $degradedItemCount,
                $degradedItemCount === 1 ? '' : 's',
            );
        }

        if ($metadataOnlyCount > 0) {
            $highlights[] = sprintf(
                '%d metadata-only',
                $metadataOnlyCount,
            );
        }

        if ($assignmentIssueCount > 0) {
            $highlights[] = sprintf(
                '%d assignment issue%s',
                $assignmentIssueCount,
                $assignmentIssueCount === 1 ? '' : 's',
            );
        }

        if ($orphanedAssignmentCount > 0) {
            $highlights[] = sprintf(
                '%d orphaned assignment%s',
                $orphanedAssignmentCount,
                $orphanedAssignmentCount === 1 ? '' : 's',
            );
        }

        if ($integrityWarningCount > 0) {
            $highlights[] = sprintf(
                '%d integrity warning%s',
                $integrityWarningCount,
                $integrityWarningCount === 1 ? '' : 's',
            );
        }

        if ($unknownQualityCount > 0) {
            $highlights[] = sprintf(
                '%d unknown quality',
                $unknownQualityCount,
            );
        }

        return $highlights;
    }

    private function defaultSetCompactSummary(int $totalItems): string
    {
        if ($totalItems === 0) {
            return 'No items captured';
        }

        return sprintf(
            'No degradations detected across %d item%s',
            $totalItems,
            $totalItems === 1 ? '' : 's',
        );
    }

    private function positiveClaimBoundary(): string
    {
        return 'Input quality signals do not prove safe restore, restore readiness, or tenant-wide recoverability.';
    }
}
