<?php

declare(strict_types=1);

namespace App\Support\Inventory;

use App\Support\Baselines\ResolutionPath;
use App\Support\Baselines\SubjectClass;
use Illuminate\Support\Str;

class InventoryPolicyTypeMeta
{
    /**
     * Canonical inventory policy-type metadata source-of-truth.
     *
     * These definitions are used for UI classification (restore/risk) and KPI aggregation.
     * The authoritative inputs are:
     * - `inventory_items.policy_type`
     * - `config('tenantpilot.supported_policy_types')` and `config('tenantpilot.foundation_types')`
     *   meta fields, especially: `restore`, `risk`.
     */
    public static function all(): array
    {
        return array_merge(
            static::supported(),
            static::foundations(),
        );
    }

    /**
     * @return array<int, array<string, mixed>>
     */
    public static function supported(): array
    {
        $supported = config('tenantpilot.supported_policy_types', []);

        return is_array($supported) ? $supported : [];
    }

    /**
     * @return array<int, array<string, mixed>>
     */
    public static function foundations(): array
    {
        $foundations = config('tenantpilot.foundation_types', []);

        return is_array($foundations) ? $foundations : [];
    }

    /**
     * @return array<int, array<string, mixed>>
     */
    public static function baselineSupportedFoundations(): array
    {
        return array_values(array_filter(
            static::foundations(),
            static fn (array $row): bool => filled($row['type'] ?? null)
                && is_array($row['baseline_compare'] ?? null)
                && (bool) ($row['baseline_compare']['supported'] ?? false),
        ));
    }

    /**
     * @return array<string, array<string, mixed>>
     */
    public static function byType(): array
    {
        return collect(static::all())
            ->filter(fn (array $row): bool => filled($row['type'] ?? null))
            ->keyBy(fn (array $row): string => (string) $row['type'])
            ->all();
    }

    /**
     * @return array<string, mixed>
     */
    public static function metaFor(?string $type): array
    {
        if (! filled($type)) {
            return [];
        }

        return static::byType()[(string) $type] ?? [];
    }

    public static function label(?string $type): ?string
    {
        $label = static::metaFor($type)['label'] ?? null;

        return is_string($label) ? $label : null;
    }

    public static function category(?string $type): ?string
    {
        $category = static::metaFor($type)['category'] ?? null;

        return is_string($category) ? $category : null;
    }

    /**
     * @param  array<string, mixed>  $metadata
     * @return array{
     *     canonical_type: string,
     *     provider_object_type: string,
     *     provider_display_type: string,
     *     legacy_policy_type: ?string
     * }
     */
    public static function typeDescriptorFor(?string $type, array $metadata = []): array
    {
        $providerObjectType = static::providerObjectType($type, $metadata);
        $legacyPolicyType = filled($type) ? (string) $type : null;
        $providerDisplayType = static::label($type)
            ?? static::metadataString($metadata, ['provider_display_type', 'display_type', 'type_label'])
            ?? Str::headline($providerObjectType);

        return [
            'canonical_type' => static::canonicalTypeFor($type, $metadata),
            'provider_object_type' => $providerObjectType,
            'provider_display_type' => $providerDisplayType,
            'legacy_policy_type' => $legacyPolicyType,
        ];
    }

    private static function canonicalTypeFor(?string $type, array $metadata): string
    {
        $configured = static::metadataString(static::metaFor($type), ['canonical_type'])
            ?? static::metadataString($metadata, ['canonical_type']);

        if ($configured !== null) {
            return $configured;
        }

        $category = Str::lower((string) (static::category($type) ?? ''));

        return match (true) {
            ! filled($type) => 'unknown_artifact',
            static::isFoundation($type) => 'foundation_artifact',
            str_contains($category, 'compliance') => 'endpoint_compliance_policy',
            str_contains($category, 'endpoint security') => 'endpoint_security_policy',
            str_contains($category, 'conditional access') => 'identity_access_policy',
            str_contains($category, 'apps') || str_contains($category, 'applications') => 'application_management_artifact',
            str_contains($category, 'script') => 'endpoint_script',
            str_contains($category, 'update') => 'endpoint_update_policy',
            str_contains($category, 'enrollment') => 'endpoint_enrollment_policy',
            str_contains($category, 'configuration') => 'endpoint_configuration_policy',
            default => 'governed_configuration_artifact',
        };
    }

    /**
     * @param  array<string, mixed>  $metadata
     */
    private static function providerObjectType(?string $type, array $metadata): string
    {
        return static::metadataString($metadata, [
            'provider_object_type',
            '@odata.type',
            'odata_type',
            'graph_type',
            'resource_type',
        ]) ?? (filled($type) ? (string) $type : 'unknown');
    }

    /**
     * @param  array<string, mixed>  $metadata
     * @param  list<string>  $keys
     */
    private static function metadataString(array $metadata, array $keys): ?string
    {
        foreach ($keys as $key) {
            $value = $metadata[$key] ?? null;

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

        return null;
    }

    public static function isFoundation(?string $type): bool
    {
        if (! filled($type)) {
            return false;
        }

        return collect(static::foundations())
            ->pluck('type')
            ->contains((string) $type);
    }

    public static function restoreMode(?string $type): ?string
    {
        $restore = static::metaFor($type)['restore'] ?? null;

        return is_string($restore) ? $restore : null;
    }

    public static function riskLevel(?string $type): ?string
    {
        $risk = static::metaFor($type)['risk'] ?? null;

        return is_string($risk) ? $risk : null;
    }

    public static function isRestorable(?string $type): bool
    {
        return static::restoreMode($type) === 'enabled';
    }

    public static function isPartial(?string $type): bool
    {
        $restore = static::restoreMode($type);

        return filled($restore) && $restore !== 'enabled';
    }

    public static function isHighRisk(?string $type): bool
    {
        $risk = static::riskLevel($type);

        return is_string($risk) && str_contains($risk, 'high');
    }

    /**
     * @return array<string, mixed>
     */
    public static function baselineCompareMeta(?string $type): array
    {
        $meta = static::metaFor($type)['baseline_compare'] ?? null;

        return is_array($meta) ? $meta : [];
    }

    public static function isBaselineSupportedFoundation(?string $type): bool
    {
        if (! static::isFoundation($type)) {
            return false;
        }

        return (bool) (static::baselineCompareMeta($type)['supported'] ?? false);
    }

    public static function baselineCompareIdentityStrategy(?string $type): string
    {
        $strategy = static::baselineCompareMeta($type)['identity_strategy'] ?? null;

        return in_array($strategy, ['display_name', 'external_id'], true)
            ? (string) $strategy
            : 'display_name';
    }

    public static function baselineCompareLabel(?string $type): ?string
    {
        $label = static::baselineCompareMeta($type)['label'] ?? null;

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

        return static::label($type);
    }

    /**
     * @return array{
     *   config_supported: bool,
     *   runtime_valid: bool,
     *   subject_class: string,
     *   resolution_path: string,
     *   compare_capability: string,
     *   capture_capability: string,
     *   source_model_expected: 'policy'|'inventory'|'derived'|null
     * }
     */
    public static function baselineSupportContract(?string $type): array
    {
        $contract = static::defaultBaselineSupportContract($type);
        $resolution = static::baselineCompareMeta($type)['resolution'] ?? null;

        if (is_array($resolution)) {
            $contract = array_replace($contract, array_filter([
                'subject_class' => is_string($resolution['subject_class'] ?? null) ? $resolution['subject_class'] : null,
                'resolution_path' => is_string($resolution['resolution_path'] ?? null) ? $resolution['resolution_path'] : null,
                'compare_capability' => is_string($resolution['compare_capability'] ?? null) ? $resolution['compare_capability'] : null,
                'capture_capability' => is_string($resolution['capture_capability'] ?? null) ? $resolution['capture_capability'] : null,
                'source_model_expected' => is_string($resolution['source_model_expected'] ?? null) ? $resolution['source_model_expected'] : null,
            ], static fn (mixed $value): bool => $value !== null));
        }

        $subjectClass = SubjectClass::tryFrom((string) ($contract['subject_class'] ?? ''));
        $resolutionPath = ResolutionPath::tryFrom((string) ($contract['resolution_path'] ?? ''));
        $compareCapability = in_array($contract['compare_capability'] ?? null, ['supported', 'limited', 'unsupported'], true)
            ? (string) $contract['compare_capability']
            : 'unsupported';
        $captureCapability = in_array($contract['capture_capability'] ?? null, ['supported', 'limited', 'unsupported'], true)
            ? (string) $contract['capture_capability']
            : 'unsupported';
        $sourceModelExpected = in_array($contract['source_model_expected'] ?? null, ['policy', 'inventory', 'derived'], true)
            ? (string) $contract['source_model_expected']
            : null;

        $runtimeValid = $subjectClass instanceof SubjectClass
            && $resolutionPath instanceof ResolutionPath
            && static::pathMatchesSubjectClass($subjectClass, $resolutionPath)
            && static::pathMatchesExpectedSource($resolutionPath, $sourceModelExpected);

        if (! $runtimeValid) {
            $compareCapability = 'unsupported';
            $captureCapability = 'unsupported';
        }

        return [
            'config_supported' => (bool) ($contract['config_supported'] ?? false),
            'runtime_valid' => $runtimeValid,
            'subject_class' => ($subjectClass ?? SubjectClass::Derived)->value,
            'resolution_path' => ($resolutionPath ?? ResolutionPath::Derived)->value,
            'compare_capability' => $compareCapability,
            'capture_capability' => $captureCapability,
            'source_model_expected' => $sourceModelExpected,
        ];
    }

    /**
     * @return array{
     *   config_supported: bool,
     *   subject_class: string,
     *   resolution_path: string,
     *   compare_capability: string,
     *   capture_capability: string,
     *   source_model_expected: 'policy'|'inventory'|'derived'|null
     * }
     */
    private static function defaultBaselineSupportContract(?string $type): array
    {
        if (filled($type) && ! static::isFoundation($type) && static::metaFor($type) !== []) {
            return [
                'config_supported' => true,
                'subject_class' => SubjectClass::PolicyBacked->value,
                'resolution_path' => ResolutionPath::Policy->value,
                'compare_capability' => 'supported',
                'capture_capability' => 'supported',
                'source_model_expected' => 'policy',
            ];
        }

        if (static::isFoundation($type)) {
            $supported = (bool) (static::baselineCompareMeta($type)['supported'] ?? false);
            $identityStrategy = static::baselineCompareIdentityStrategy($type);
            $usesPolicyPath = $identityStrategy === 'external_id';

            return [
                'config_supported' => $supported,
                'subject_class' => SubjectClass::FoundationBacked->value,
                'resolution_path' => $usesPolicyPath
                    ? ResolutionPath::FoundationPolicy->value
                    : ResolutionPath::FoundationInventory->value,
                'compare_capability' => ! $supported
                    ? 'unsupported'
                    : ($usesPolicyPath ? 'supported' : 'limited'),
                'capture_capability' => ! $supported
                    ? 'unsupported'
                    : ($usesPolicyPath ? 'supported' : 'limited'),
                'source_model_expected' => $usesPolicyPath ? 'policy' : 'inventory',
            ];
        }

        return [
            'config_supported' => false,
            'subject_class' => SubjectClass::Derived->value,
            'resolution_path' => ResolutionPath::Derived->value,
            'compare_capability' => 'unsupported',
            'capture_capability' => 'unsupported',
            'source_model_expected' => 'derived',
        ];
    }

    private static function pathMatchesSubjectClass(SubjectClass $subjectClass, ResolutionPath $resolutionPath): bool
    {
        return match ($subjectClass) {
            SubjectClass::PolicyBacked => $resolutionPath === ResolutionPath::Policy,
            SubjectClass::InventoryBacked => $resolutionPath === ResolutionPath::Inventory,
            SubjectClass::FoundationBacked => in_array($resolutionPath, [
                ResolutionPath::FoundationInventory,
                ResolutionPath::FoundationPolicy,
            ], true),
            SubjectClass::Derived => $resolutionPath === ResolutionPath::Derived,
        };
    }

    private static function pathMatchesExpectedSource(ResolutionPath $resolutionPath, ?string $sourceModelExpected): bool
    {
        return match ($resolutionPath) {
            ResolutionPath::Policy,
            ResolutionPath::FoundationPolicy => $sourceModelExpected === 'policy',
            ResolutionPath::Inventory,
            ResolutionPath::FoundationInventory => $sourceModelExpected === 'inventory',
            ResolutionPath::Derived => $sourceModelExpected === 'derived',
        };
    }
}
