<?php

namespace App\Support\Providers\TargetScope;

use App\Models\ProviderConnection;
use InvalidArgumentException;

final class ProviderConnectionTargetScopeNormalizer
{
    public const string STATUS_NORMALIZED = 'normalized';

    public const string STATUS_BLOCKED = 'blocked';

    public const string FAILURE_NONE = 'none';

    public const string FAILURE_UNSUPPORTED_PROVIDER_SCOPE_COMBINATION = 'unsupported_provider_scope_combination';

    public const string FAILURE_MISSING_PROVIDER_CONTEXT = 'missing_provider_context';

    /**
     * @param  array<string, string>  $providerSpecificIdentity
     * @return array{
     *     status: string,
     *     provider: string,
     *     scope_kind: string,
     *     target_scope?: ProviderConnectionTargetScopeDescriptor,
     *     contextual_identity_details?: list<ProviderIdentityContextMetadata>,
     *     preview_summary?: ?ProviderConnectionSurfaceSummary,
     *     failure_code: string,
     *     message: string
     * }
     */
    public function normalizeInput(
        string $provider,
        string $scopeKind,
        string $scopeIdentifier,
        ?string $scopeDisplayName = null,
        array $providerSpecificIdentity = [],
    ): array {
        $provider = trim($provider);
        $scopeKind = trim($scopeKind);
        $scopeIdentifier = trim($scopeIdentifier);

        if ($provider !== 'microsoft' || $scopeKind !== ProviderConnectionTargetScopeDescriptor::SCOPE_KIND_TENANT) {
            return $this->blocked(
                provider: $provider,
                scopeKind: $scopeKind,
                failureCode: self::FAILURE_UNSUPPORTED_PROVIDER_SCOPE_COMBINATION,
                message: 'This provider and target-scope combination is not supported.',
            );
        }

        if ($scopeIdentifier === '') {
            return $this->blocked(
                provider: $provider,
                scopeKind: $scopeKind,
                failureCode: self::FAILURE_MISSING_PROVIDER_CONTEXT,
                message: 'A target scope identifier is required for this provider connection.',
            );
        }

        $descriptor = ProviderConnectionTargetScopeDescriptor::fromInput(
            provider: $provider,
            scopeKind: $scopeKind,
            scopeIdentifier: $scopeIdentifier,
            scopeDisplayName: $scopeDisplayName,
        );

        return [
            'status' => self::STATUS_NORMALIZED,
            'provider' => $provider,
            'scope_kind' => $scopeKind,
            'target_scope' => $descriptor,
            'contextual_identity_details' => $this->contextualIdentityDetails(
                provider: $provider,
                scopeIdentifier: $scopeIdentifier,
                providerSpecificIdentity: $providerSpecificIdentity,
            ),
            'preview_summary' => null,
            'failure_code' => self::FAILURE_NONE,
            'message' => 'Target scope normalized.',
        ];
    }

    /**
     * @return array{
     *     status: string,
     *     provider: string,
     *     scope_kind: string,
     *     target_scope?: ProviderConnectionTargetScopeDescriptor,
     *     contextual_identity_details?: list<ProviderIdentityContextMetadata>,
     *     preview_summary?: ?ProviderConnectionSurfaceSummary,
     *     failure_code: string,
     *     message: string
     * }
     */
    public function normalizeConnection(ProviderConnection $connection): array
    {
        $descriptor = ProviderConnectionTargetScopeDescriptor::fromConnection($connection);
        $connectionTenantId = trim((string) $connection->entra_tenant_id);

        return $this->normalizeInput(
            provider: trim((string) $connection->provider),
            scopeKind: ProviderConnectionTargetScopeDescriptor::SCOPE_KIND_TENANT,
            scopeIdentifier: $descriptor->scopeIdentifier,
            scopeDisplayName: $descriptor->scopeDisplayName,
            providerSpecificIdentity: [
                'microsoft_tenant_id' => $connectionTenantId !== '' ? $connectionTenantId : $descriptor->scopeIdentifier,
            ],
        );
    }

    public function descriptorForConnection(ProviderConnection $connection): ProviderConnectionTargetScopeDescriptor
    {
        $result = $this->normalizeConnection($connection);
        $descriptor = $result['target_scope'] ?? null;

        if (! $descriptor instanceof ProviderConnectionTargetScopeDescriptor) {
            throw new InvalidArgumentException($result['message']);
        }

        return $descriptor;
    }

    /**
     * @return list<ProviderIdentityContextMetadata>
     */
    public function contextualIdentityDetailsForConnection(ProviderConnection $connection): array
    {
        $descriptor = ProviderConnectionTargetScopeDescriptor::fromConnection($connection);
        $connectionTenantId = trim((string) $connection->entra_tenant_id);

        return $this->contextualIdentityDetails(
            provider: trim((string) $connection->provider),
            scopeIdentifier: $descriptor->scopeIdentifier,
            providerSpecificIdentity: [
                'microsoft_tenant_id' => $connectionTenantId !== '' ? $connectionTenantId : $descriptor->scopeIdentifier,
            ],
        );
    }

    /**
     * @return array<string, mixed>
     */
    public function auditMetadataForConnection(ProviderConnection $connection, array $extra = []): array
    {
        $summary = ProviderConnectionSurfaceSummary::forConnection($connection);

        return array_merge([
            'provider_connection_id' => (int) $connection->getKey(),
            'provider' => (string) $connection->provider,
            'target_scope' => $summary->targetScope->toArray(),
            'provider_context' => [
                'provider' => (string) $connection->provider,
                'details' => array_map(
                    static fn (ProviderIdentityContextMetadata $detail): array => $detail->toArray(),
                    $summary->contextualIdentityDetails,
                ),
            ],
        ], $extra);
    }

    /**
     * @param  list<ProviderIdentityContextMetadata>  $details
     * @return array{provider: string, details: list<array<string, string>>}
     */
    public function providerContext(string $provider, array $details): array
    {
        return [
            'provider' => $provider,
            'details' => array_map(
                static fn (ProviderIdentityContextMetadata $detail): array => $detail->toArray(),
                $details,
            ),
        ];
    }

    /**
     * @param  list<string>  $fields
     * @return list<string>
     */
    public function auditFieldNames(array $fields): array
    {
        return array_values(array_map(
            static fn (string $field): string => $field === 'entra_tenant_id' ? 'target_scope_identifier' : $field,
            $fields,
        ));
    }

    /**
     * @param  array<string, string>  $providerSpecificIdentity
     * @return list<ProviderIdentityContextMetadata>
     */
    private function contextualIdentityDetails(string $provider, string $scopeIdentifier, array $providerSpecificIdentity = []): array
    {
        if ($provider !== 'microsoft') {
            return [];
        }

        $details = [
            ProviderIdentityContextMetadata::microsoftTenantId(
                $providerSpecificIdentity['microsoft_tenant_id'] ?? $scopeIdentifier,
            ),
        ];

        return array_values(array_filter(
            $details,
            static fn (?ProviderIdentityContextMetadata $detail): bool => $detail instanceof ProviderIdentityContextMetadata,
        ));
    }

    /**
     * @return array{
     *     status: string,
     *     provider: string,
     *     scope_kind: string,
     *     failure_code: string,
     *     message: string
     * }
     */
    private function blocked(string $provider, string $scopeKind, string $failureCode, string $message): array
    {
        return [
            'status' => self::STATUS_BLOCKED,
            'provider' => $provider,
            'scope_kind' => $scopeKind,
            'failure_code' => $failureCode,
            'message' => $message,
        ];
    }
}
