<?php

declare(strict_types=1);

namespace App\Support\WorkspaceIsolation;

use App\Filament\Resources\BackupScheduleResource;
use App\Filament\Resources\BackupSetResource;
use App\Filament\Resources\EntraGroupResource;
use App\Filament\Resources\EnvironmentReviewResource;
use App\Filament\Resources\EvidenceSnapshotResource;
use App\Filament\Resources\FindingExceptionResource;
use App\Filament\Resources\FindingResource;
use App\Filament\Resources\InventoryItemResource;
use App\Filament\Resources\PolicyResource;
use App\Filament\Resources\PolicyVersionResource;
use App\Filament\Resources\RestoreRunResource;
use App\Filament\Resources\StoredReportResource;
use App\Models\BackupSchedule;
use App\Models\BackupSet;
use App\Models\EntraGroup;
use App\Models\EnvironmentReview;
use App\Models\EvidenceSnapshot;
use App\Models\Finding;
use App\Models\FindingException;
use App\Models\InventoryItem;
use App\Models\Policy;
use App\Models\PolicyVersion;
use App\Models\RestoreRun;
use App\Models\StoredReport;

final class TenantOwnedModelFamilies
{
    /**
     * @return array<string, array{
     *     table: string,
     *     model: class-string,
     *     resource: class-string,
     *     tenant_relationship: string,
     *     search_posture: 'scoped'|'disabled'|'not_applicable',
     *     action_surface: 'declared'|'baseline_exemption',
     *     action_surface_reason: string,
     *     notes: string
     * }>
     */
    public static function firstSlice(): array
    {
        return [
            'Policy' => [
                'table' => 'policies',
                'model' => Policy::class,
                'resource' => PolicyResource::class,
                'tenant_relationship' => 'tenant',
                'search_posture' => 'disabled',
                'action_surface' => 'declared',
                'action_surface_reason' => 'PolicyResource declares its action surface contract directly.',
                'notes' => 'Policy search remains disabled until list/detail parity is fully migrated.',
            ],
            'PolicyVersion' => [
                'table' => 'policy_versions',
                'model' => PolicyVersion::class,
                'resource' => PolicyVersionResource::class,
                'tenant_relationship' => 'tenant',
                'search_posture' => 'disabled',
                'action_surface' => 'declared',
                'action_surface_reason' => 'PolicyVersionResource declares its action surface contract directly.',
                'notes' => 'Policy version search remains disabled until parity is guaranteed.',
            ],
            'BackupSchedule' => [
                'table' => 'backup_schedules',
                'model' => BackupSchedule::class,
                'resource' => BackupScheduleResource::class,
                'tenant_relationship' => 'tenant',
                'search_posture' => 'not_applicable',
                'action_surface' => 'declared',
                'action_surface_reason' => 'BackupScheduleResource declares its action surface contract directly.',
                'notes' => 'Backup schedules are not part of global search.',
            ],
            'BackupSet' => [
                'table' => 'backup_sets',
                'model' => BackupSet::class,
                'resource' => BackupSetResource::class,
                'tenant_relationship' => 'tenant',
                'search_posture' => 'not_applicable',
                'action_surface' => 'declared',
                'action_surface_reason' => 'BackupSetResource declares its action surface contract directly.',
                'notes' => 'Backup sets are not part of global search.',
            ],
            'RestoreRun' => [
                'table' => 'restore_runs',
                'model' => RestoreRun::class,
                'resource' => RestoreRunResource::class,
                'tenant_relationship' => 'tenant',
                'search_posture' => 'not_applicable',
                'action_surface' => 'declared',
                'action_surface_reason' => 'RestoreRunResource declares its action surface contract directly.',
                'notes' => 'Restore runs are not part of global search.',
            ],
            'Finding' => [
                'table' => 'findings',
                'model' => Finding::class,
                'resource' => FindingResource::class,
                'tenant_relationship' => 'tenant',
                'search_posture' => 'not_applicable',
                'action_surface' => 'declared',
                'action_surface_reason' => 'FindingResource declares its action surface contract directly.',
                'notes' => 'Findings are not part of global search in the first slice.',
            ],
            'FindingException' => [
                'table' => 'finding_exceptions',
                'model' => FindingException::class,
                'resource' => FindingExceptionResource::class,
                'tenant_relationship' => 'tenant',
                'search_posture' => 'disabled',
                'action_surface' => 'declared',
                'action_surface_reason' => 'FindingExceptionResource declares its action surface contract directly.',
                'notes' => 'Finding exceptions stay off global search in the first rollout.',
            ],
            'EvidenceSnapshot' => [
                'table' => 'evidence_snapshots',
                'model' => EvidenceSnapshot::class,
                'resource' => EvidenceSnapshotResource::class,
                'tenant_relationship' => 'tenant',
                'search_posture' => 'disabled',
                'action_surface' => 'declared',
                'action_surface_reason' => 'EvidenceSnapshotResource declares its action surface contract directly.',
                'notes' => 'Evidence snapshots stay off global search until broader evidence discovery is introduced.',
            ],
            'InventoryItem' => [
                'table' => 'inventory_items',
                'model' => InventoryItem::class,
                'resource' => InventoryItemResource::class,
                'tenant_relationship' => 'tenant',
                'search_posture' => 'not_applicable',
                'action_surface' => 'declared',
                'action_surface_reason' => 'InventoryItemResource declares its action surface contract directly.',
                'notes' => 'Inventory items stay off global search.',
            ],
            'EntraGroup' => [
                'table' => 'entra_groups',
                'model' => EntraGroup::class,
                'resource' => EntraGroupResource::class,
                'tenant_relationship' => 'tenant',
                'search_posture' => 'scoped',
                'action_surface' => 'declared',
                'action_surface_reason' => 'EntraGroupResource declares its action surface contract directly.',
                'notes' => 'Directory groups already support tenant-safe global search.',
            ],
            'EnvironmentReview' => [
                'table' => 'environment_reviews',
                'model' => EnvironmentReview::class,
                'resource' => EnvironmentReviewResource::class,
                'tenant_relationship' => 'tenant',
                'search_posture' => 'disabled',
                'action_surface' => 'declared',
                'action_surface_reason' => 'EnvironmentReviewResource declares its action surface contract directly.',
                'notes' => 'ManagedEnvironment reviews stay out of global search and are surfaced through tenant detail plus the canonical register.',
            ],
            'StoredReport' => [
                'table' => 'stored_reports',
                'model' => StoredReport::class,
                'resource' => StoredReportResource::class,
                'tenant_relationship' => 'tenant',
                'search_posture' => 'disabled',
                'action_surface' => 'declared',
                'action_surface_reason' => 'StoredReportResource declares its read-only stored-report action surface contract directly.',
                'notes' => 'Stored reports stay out of global search and are surfaced through the tenant-scoped register and detail route.',
            ],
        ];
    }

    /**
     * @return array<string, array{table: string, likely_surface: string, why_not_in_first_slice: string}>
     */
    public static function residualRolloutInventory(): array
    {
        return [
            'BackupItem' => [
                'table' => 'backup_items',
                'likely_surface' => 'BackupSetResource::BackupItemsRelationManager',
                'why_not_in_first_slice' => 'Covered through the backup-set relation manager rather than a standalone primary resource.',
            ],
            'InventoryLink' => [
                'table' => 'inventory_links',
                'likely_surface' => 'InventoryItemResource related-links affordances',
                'why_not_in_first_slice' => 'Inventory links are subordinate navigation metadata and inherit tenant scope through inventory items.',
            ],
            'EntraRoleDefinition' => [
                'table' => 'entra_role_definitions',
                'likely_surface' => 'Entra admin-role reporting and findings reference flows',
                'why_not_in_first_slice' => 'Read paths remain indirect via reporting and findings surfaces, so direct tenant-owned resource parity is deferred.',
            ],
            'ManagedEnvironmentPermission' => [
                'table' => 'managed_environment_permissions',
                'likely_surface' => 'Permissions and onboarding diagnostics surfaces',
                'why_not_in_first_slice' => 'Permission posture is enforced through dedicated diagnostics and onboarding flows, not a first-slice primary resource.',
            ],
            'FindingExceptionDecision' => [
                'table' => 'finding_exception_decisions',
                'likely_surface' => 'FindingExceptionResource decision history entries',
                'why_not_in_first_slice' => 'Decision history is subordinate to the finding exception aggregate instead of a standalone primary resource.',
            ],
            'FindingExceptionEvidenceReference' => [
                'table' => 'finding_exception_evidence_references',
                'likely_surface' => 'FindingExceptionResource evidence sections',
                'why_not_in_first_slice' => 'Evidence references are subordinate support records rendered inside finding exception detail.',
            ],
        ];
    }

    /**
     * @return array<int, string>
     */
    public static function names(): array
    {
        return array_keys(self::firstSlice());
    }

    /**
     * @return array{table: string, model: class-string, resource: class-string, tenant_relationship: string, search_posture: 'scoped'|'disabled'|'not_applicable', action_surface: 'declared'|'baseline_exemption', action_surface_reason: string, notes: string}|null
     */
    public static function forModel(string $modelClass): ?array
    {
        foreach (self::firstSlice() as $family) {
            if ($family['model'] === $modelClass) {
                return $family;
            }
        }

        return null;
    }

    public static function searchPostureForModel(string $modelClass): ?string
    {
        return self::forModel($modelClass)['search_posture'] ?? null;
    }

    public static function supportsScopedGlobalSearch(string $modelClass): bool
    {
        return self::searchPostureForModel($modelClass) === 'scoped';
    }

    /**
     * @return array<string, array{exception_kind: string, why_excepted: string, still_required_checks: list<string>}>
     */
    public static function scopeExceptions(): array
    {
        return [
            'ProviderConnectionResource' => [
                'exception_kind' => 'workspace_admin_canonical_viewer',
                'why_excepted' => 'Workspace-admin tenant-default surface referencing tenant-owned data without being part of the mandatory first-slice canon.',
                'still_required_checks' => [
                    'workspace membership',
                    'remembered environment entitlement',
                    'capability gating on the destination action',
                ],
            ],
            'OperationRunResource' => [
                'exception_kind' => 'workspace_owned_reference_surface',
                'why_excepted' => 'Workspace-owned canonical monitoring surface that may deep-link into tenant-owned records only after entitlement checks.',
                'still_required_checks' => [
                    'workspace membership',
                    'tenant entitlement on deep links',
                    'record-owner congruence before rendering tenant-owned destinations',
                ],
            ],
            'AlertDeliveryResource' => [
                'exception_kind' => 'deferred_family',
                'why_excepted' => 'Mixed workspace-owned and tenant-bound semantics keep this surface outside the mandatory tenant-owned family set for the first slice.',
                'still_required_checks' => [
                    'workspace membership',
                    'tenant capability checks for tenant-bound mutations',
                ],
            ],
        ];
    }

    /**
     * @return array<string, string>
     */
    public static function explicitScopeExceptions(): array
    {
        return array_map(
            static fn (array $exception): string => $exception['why_excepted'],
            self::scopeExceptions(),
        );
    }

    /**
     * @return array<class-string, string>
     */
    public static function actionSurfaceBaselineExemptions(): array
    {
        $exemptions = [];

        foreach (self::firstSlice() as $family) {
            if ($family['action_surface'] !== 'baseline_exemption') {
                continue;
            }

            $exemptions[$family['resource']] = $family['action_surface_reason'];
        }

        return $exemptions;
    }
}
