<?php

declare(strict_types=1);

namespace App\Support\Providers\Capabilities;

use App\Models\ManagedEnvironment;
use App\Models\ProviderConnection;
use App\Services\Intune\ManagedEnvironmentRequiredPermissionsViewModelBuilder;
use App\Support\Links\RequiredPermissionsLinks;
use App\Support\Providers\ProviderConsentStatus;
use App\Support\Providers\ProviderReasonCodes;
use App\Support\Providers\ProviderVerificationStatus;
use App\Support\Verification\ManagedEnvironmentPermissionCheckClusters;
use InvalidArgumentException;

final class ProviderCapabilityEvaluator
{
    public function __construct(
        private readonly ProviderCapabilityRegistry $registry,
        private readonly ManagedEnvironmentRequiredPermissionsViewModelBuilder $requiredPermissionsViewModelBuilder,
    ) {}

    /**
     * @param  array<string, mixed>|null  $requiredPermissionsViewModel
     */
    public function evaluate(
        ManagedEnvironment $tenant,
        ?ProviderConnection $connection,
        string $capabilityKey,
        ?array $requiredPermissionsViewModel = null,
    ): ProviderCapabilityResult {
        $definition = $this->registry->get($capabilityKey);
        $requiredPermissionsViewModel ??= $this->requiredPermissionsViewModel($tenant);

        return $this->evaluateDefinition($tenant, $connection, $definition, $requiredPermissionsViewModel);
    }

    /**
     * @return array<int, ProviderCapabilityResult>
     */
    public function evaluateForConnection(ProviderConnection $connection, ?ManagedEnvironment $tenant = null): array
    {
        $tenant ??= $connection->tenant instanceof ManagedEnvironment ? $connection->tenant : null;

        if (! $tenant instanceof ManagedEnvironment && is_numeric($connection->managed_environment_id)) {
            $tenant = ManagedEnvironment::query()->whereKey((int) $connection->managed_environment_id)->first();
        }

        if (! $tenant instanceof ManagedEnvironment) {
            return array_map(
                fn (ProviderCapabilityDefinition $definition): ProviderCapabilityResult => new ProviderCapabilityResult(
                    key: $definition->key,
                    label: $definition->label,
                    status: ProviderCapabilityStatus::Unknown,
                    reasonCode: ProviderReasonCodes::ProviderConnectionInvalid,
                    providerRequirementKeys: $definition->providerRequirementKeys,
                    primaryMessage: 'Provider capability could not be evaluated because the tenant scope is unavailable.',
                    providerHint: 'Open the provider connection from a tenant-scoped workspace context.',
                ),
                array_values($this->registry->all()),
            );
        }

        $requiredPermissionsViewModel = $this->requiredPermissionsViewModel($tenant);

        return array_map(
            fn (ProviderCapabilityDefinition $definition): ProviderCapabilityResult => $this->evaluateDefinition(
                tenant: $tenant,
                connection: $connection,
                definition: $definition,
                requiredPermissionsViewModel: $requiredPermissionsViewModel,
            ),
            array_values($this->registry->all()),
        );
    }

    /**
     * @return array<int, ProviderCapabilityResult>
     */
    public function evaluateForOperation(
        ManagedEnvironment $tenant,
        ?ProviderConnection $connection,
        string $operationType,
    ): array {
        $definitions = $this->registry->forOperationType($operationType);
        $requiredPermissionsViewModel = $this->requiredPermissionsViewModel($tenant);

        return array_map(
            fn (ProviderCapabilityDefinition $definition): ProviderCapabilityResult => $this->evaluateDefinition(
                tenant: $tenant,
                connection: $connection,
                definition: $definition,
                requiredPermissionsViewModel: $requiredPermissionsViewModel,
            ),
            $definitions,
        );
    }

    /**
     * @param  array<int, ProviderCapabilityResult>  $results
     */
    public function primaryBlockingResult(array $results): ?ProviderCapabilityResult
    {
        $blocking = array_values(array_filter(
            $results,
            static fn (ProviderCapabilityResult $result): bool => $result->blocksExecution(),
        ));

        if ($blocking === []) {
            return null;
        }

        usort(
            $blocking,
            static fn (ProviderCapabilityResult $a, ProviderCapabilityResult $b): int => $a->status->priority() <=> $b->status->priority(),
        );

        return $blocking[0];
    }

    /**
     * @param  array<int, ProviderCapabilityResult>  $results
     */
    public function primaryDisplayResult(array $results): ?ProviderCapabilityResult
    {
        if ($results === []) {
            return null;
        }

        usort(
            $results,
            static fn (ProviderCapabilityResult $a, ProviderCapabilityResult $b): int => $a->status->priority() <=> $b->status->priority(),
        );

        return $results[0];
    }

    /**
     * @param  array<string, mixed>  $requiredPermissionsViewModel
     */
    private function evaluateDefinition(
        ManagedEnvironment $tenant,
        ?ProviderConnection $connection,
        ProviderCapabilityDefinition $definition,
        array $requiredPermissionsViewModel,
    ): ProviderCapabilityResult {
        $base = [
            'key' => $definition->key,
            'label' => $definition->label,
            'providerRequirementKeys' => $definition->providerRequirementKeys,
            'lastCheckedAt' => $this->lastCheckedAt($requiredPermissionsViewModel),
        ];

        if (! $connection instanceof ProviderConnection) {
            return new ProviderCapabilityResult(
                ...$base,
                status: ProviderCapabilityStatus::Unknown,
                reasonCode: ProviderReasonCodes::ProviderConnectionMissing,
                primaryMessage: "{$definition->label} capability cannot be evaluated without a provider connection.",
                providerHint: 'Select or create a provider connection for this tenant.',
            );
        }

        $provider = trim((string) $connection->provider);
        if ($provider !== 'microsoft') {
            return new ProviderCapabilityResult(
                ...$base,
                status: ProviderCapabilityStatus::NotApplicable,
                primaryMessage: "{$definition->label} capability is not mapped for provider {$provider}.",
                providerHint: 'This release only has explicit Microsoft provider capability mappings.',
            );
        }

        if (! $this->connectionMatchesTenant($tenant, $connection)) {
            return new ProviderCapabilityResult(
                ...$base,
                status: ProviderCapabilityStatus::Blocked,
                reasonCode: ProviderReasonCodes::TenantTargetMismatch,
                primaryMessage: "{$definition->label} capability is blocked by a tenant target mismatch.",
                providerHint: 'Review the provider connection target scope before starting provider operations.',
            );
        }

        if (! (bool) $connection->is_enabled) {
            return new ProviderCapabilityResult(
                ...$base,
                status: ProviderCapabilityStatus::Blocked,
                reasonCode: ProviderReasonCodes::ProviderConnectionInvalid,
                primaryMessage: "{$definition->label} capability is blocked because the provider connection is disabled.",
                providerHint: 'Enable the provider connection after confirming it should be active.',
            );
        }

        if ($connection->requiresMigrationReview()) {
            return new ProviderCapabilityResult(
                ...$base,
                status: ProviderCapabilityStatus::Blocked,
                reasonCode: ProviderReasonCodes::ProviderConnectionReviewRequired,
                primaryMessage: "{$definition->label} capability is blocked until connection classification is reviewed.",
                providerHint: 'Review the provider connection classification before retrying.',
            );
        }

        $consentStatus = $this->stateValue($connection->consent_status);
        if ($consentStatus !== ProviderConsentStatus::Granted->value) {
            return new ProviderCapabilityResult(
                ...$base,
                status: ProviderCapabilityStatus::Missing,
                reasonCode: $this->consentReasonCode($consentStatus),
                primaryMessage: "{$definition->label} capability is missing admin consent.",
                providerHint: 'Grant admin consent, then rerun provider verification.',
                nextStepLabel: 'Grant admin consent',
                nextStepUrl: RequiredPermissionsLinks::adminConsentPrimaryUrl($tenant),
            );
        }

        $verificationStatus = $this->stateValue($connection->verification_status);
        if (in_array($verificationStatus, [
            ProviderVerificationStatus::Blocked->value,
            ProviderVerificationStatus::Error->value,
        ], true)) {
            return new ProviderCapabilityResult(
                ...$base,
                status: ProviderCapabilityStatus::Blocked,
                reasonCode: ProviderReasonCodes::ProviderPermissionRefreshFailed,
                primaryMessage: "{$definition->label} capability is blocked by the latest provider verification status.",
                providerHint: 'Open Required Permissions or rerun verification before retrying.',
                nextStepLabel: 'Open Required Permissions',
                nextStepUrl: RequiredPermissionsLinks::requiredPermissions($tenant),
            );
        }

        if ($definition->providerRequirementKeys === ['permissions.admin_consent']) {
            return new ProviderCapabilityResult(
                ...$base,
                status: ProviderCapabilityStatus::Supported,
                primaryMessage: "{$definition->label} capability is supported for this provider connection.",
                evidenceCounts: ['requirements' => 1, 'missing' => 0, 'errors' => 0],
            );
        }

        return $this->evaluatePermissionRequirements($tenant, $definition, $requiredPermissionsViewModel, $base);
    }

    /**
     * @param  array<string, mixed>  $requiredPermissionsViewModel
     * @param  array<string, mixed>  $base
     */
    private function evaluatePermissionRequirements(
        ManagedEnvironment $tenant,
        ProviderCapabilityDefinition $definition,
        array $requiredPermissionsViewModel,
        array $base,
    ): ProviderCapabilityResult {
        $permissions = is_array($requiredPermissionsViewModel['permissions'] ?? null)
            ? $requiredPermissionsViewModel['permissions']
            : [];
        $freshness = is_array(data_get($requiredPermissionsViewModel, 'overview.freshness'))
            ? data_get($requiredPermissionsViewModel, 'overview.freshness')
            : [];

        $requirementRows = [];

        foreach ($definition->providerRequirementKeys as $requirementKey) {
            if ($requirementKey === 'permissions.admin_consent') {
                continue;
            }

            $requirementRows[$requirementKey] = ManagedEnvironmentPermissionCheckClusters::rowsForRequirementKey($permissions, $requirementKey);
        }

        $allRows = array_values(array_merge(...array_values($requirementRows ?: [[]])));

        if ($allRows === []) {
            return new ProviderCapabilityResult(
                ...$base,
                status: ProviderCapabilityStatus::NotApplicable,
                primaryMessage: "{$definition->label} capability has no mapped permission rows for this tenant.",
                providerHint: 'No configured permission requirement currently maps to this provider capability.',
                evidenceCounts: ['requirements' => 0, 'missing' => 0, 'errors' => 0],
            );
        }

        $missingRows = array_values(array_filter(
            $allRows,
            static fn (array $row): bool => ($row['status'] ?? null) === 'missing',
        ));
        $errorRows = array_values(array_filter(
            $allRows,
            static fn (array $row): bool => ($row['status'] ?? null) === 'error',
        ));
        $missingRequirementKeys = $this->missingRequirementKeys($requirementRows);

        if ($errorRows !== []) {
            return new ProviderCapabilityResult(
                ...$base,
                status: ProviderCapabilityStatus::Unknown,
                reasonCode: ProviderReasonCodes::ProviderPermissionRefreshFailed,
                missingRequirementKeys: $missingRequirementKeys,
                primaryMessage: "{$definition->label} capability needs a fresh permission check.",
                providerHint: 'Stored permission evidence has errors. Rerun provider verification.',
                evidenceCounts: [
                    'requirements' => count($allRows),
                    'missing' => count($missingRows),
                    'errors' => count($errorRows),
                ],
                nextStepLabel: 'Open Required Permissions',
                nextStepUrl: RequiredPermissionsLinks::requiredPermissions($tenant),
            );
        }

        if ($missingRows !== []) {
            return new ProviderCapabilityResult(
                ...$base,
                status: ProviderCapabilityStatus::Missing,
                reasonCode: $this->permissionMissingReasonCode($definition, $missingRequirementKeys),
                missingRequirementKeys: $missingRequirementKeys,
                primaryMessage: "{$definition->label} capability is missing required provider permissions.",
                providerHint: 'Open Required Permissions to review capability-grouped remediation.',
                evidenceCounts: [
                    'requirements' => count($allRows),
                    'missing' => count($missingRows),
                    'errors' => 0,
                ],
                nextStepLabel: 'Open Required Permissions',
                nextStepUrl: RequiredPermissionsLinks::requiredPermissions($tenant),
            );
        }

        if ((bool) ($freshness['is_stale'] ?? true)) {
            return new ProviderCapabilityResult(
                ...$base,
                status: ProviderCapabilityStatus::Unknown,
                reasonCode: ProviderReasonCodes::ProviderPermissionRefreshFailed,
                primaryMessage: "{$definition->label} capability is based on stale permission evidence.",
                providerHint: 'Rerun provider verification before starting this provider operation.',
                evidenceCounts: [
                    'requirements' => count($allRows),
                    'missing' => 0,
                    'errors' => 0,
                ],
                nextStepLabel: 'Open Required Permissions',
                nextStepUrl: RequiredPermissionsLinks::requiredPermissions($tenant),
            );
        }

        return new ProviderCapabilityResult(
            ...$base,
            status: ProviderCapabilityStatus::Supported,
            primaryMessage: "{$definition->label} capability is supported for this provider connection.",
            evidenceCounts: [
                'requirements' => count($allRows),
                'missing' => 0,
                'errors' => 0,
            ],
        );
    }

    /**
     * @return array<string, mixed>
     */
    private function requiredPermissionsViewModel(ManagedEnvironment $tenant): array
    {
        return $this->requiredPermissionsViewModelBuilder->build($tenant, [
            'status' => 'all',
            'type' => 'all',
            'features' => [],
            'search' => '',
        ]);
    }

    /**
     * @param  array<string, array<int, array<string, mixed>>>  $requirementRows
     * @return array<int, string>
     */
    private function missingRequirementKeys(array $requirementRows): array
    {
        $missingKeys = [];

        foreach ($requirementRows as $requirementKey => $rows) {
            foreach ($rows as $row) {
                if (in_array(($row['status'] ?? null), ['missing', 'error'], true)) {
                    $missingKeys[] = $requirementKey;
                    break;
                }
            }
        }

        return array_values(array_unique($missingKeys));
    }

    /**
     * @param  array<int, string>  $missingRequirementKeys
     */
    private function permissionMissingReasonCode(
        ProviderCapabilityDefinition $definition,
        array $missingRequirementKeys,
    ): string {
        if ($definition->key === 'restore_execute' || in_array('permissions.intune_rbac_assignments', $missingRequirementKeys, true)) {
            return ProviderReasonCodes::IntuneRbacPermissionMissing;
        }

        return $definition->defaultReasonCode;
    }

    private function consentReasonCode(string $consentStatus): string
    {
        return match ($consentStatus) {
            ProviderConsentStatus::Failed->value => ProviderReasonCodes::ProviderConsentFailed,
            ProviderConsentStatus::Revoked->value => ProviderReasonCodes::ProviderConsentRevoked,
            default => ProviderReasonCodes::ProviderConsentMissing,
        };
    }

    private function connectionMatchesTenant(ManagedEnvironment $tenant, ProviderConnection $connection): bool
    {
        if (is_numeric($connection->managed_environment_id) && (int) $connection->managed_environment_id !== (int) $tenant->getKey()) {
            return false;
        }

        $connectionTenantId = trim((string) $connection->entra_tenant_id);
        $tenantDirectoryId = trim((string) $tenant->managed_environment_id);

        return $connectionTenantId === '' || $tenantDirectoryId === '' || $connectionTenantId === $tenantDirectoryId;
    }

    /**
     * @param  array<string, mixed>  $requiredPermissionsViewModel
     */
    private function lastCheckedAt(array $requiredPermissionsViewModel): ?string
    {
        $lastCheckedAt = data_get($requiredPermissionsViewModel, 'overview.freshness.last_refreshed_at');

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

    private function stateValue(mixed $state): string
    {
        if ($state instanceof ProviderConsentStatus || $state instanceof ProviderVerificationStatus) {
            return $state->value;
        }

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

        if ($state === null) {
            return '';
        }

        throw new InvalidArgumentException('Provider capability evaluator received an unsupported provider state value.');
    }
}
