<?php

declare(strict_types=1);

namespace App\Support\Verification;

use App\Models\ProviderConnection;
use App\Models\ManagedEnvironment;
use App\Services\Intune\ManagedEnvironmentRequiredPermissionsViewModelBuilder;
use App\Support\Links\RequiredPermissionsLinks;
use App\Support\Providers\ProviderNextStepsRegistry;
use App\Support\Providers\ProviderReasonCodes;

final class VerificationAssistViewModelBuilder
{
    public function __construct(
        private readonly ManagedEnvironmentRequiredPermissionsViewModelBuilder $requiredPermissionsViewModelBuilder,
        private readonly ProviderNextStepsRegistry $providerNextStepsRegistry,
    ) {}

    /**
     * @param  array<string, mixed>|null  $verificationReport
     * @return array{is_visible:bool,reason:'permission_blocked'|'permission_attention'|'hidden_ready'|'hidden_irrelevant'}
     */
    public function visibility(ManagedEnvironment $tenant, ?array $verificationReport): array
    {
        return $this->deriveVisibility(
            $verificationReport,
            $this->requiredPermissionsViewModel($tenant),
        );
    }

    /**
     * @param  array<string, mixed>|null  $verificationReport
     * @return array{
     *   tenant: array{id:int,external_id:string,name:string},
     *   verification: array{overall:?string,status:?string,is_stale:bool,stale_reason:?string},
     *   overview: array{
     *     overall:string,
     *     counts: array{missing_application:int,missing_delegated:int,present:int,error:int},
     *     freshness: array{last_refreshed_at:?string,is_stale:bool}
     *   },
     *   missing_permissions: array{
     *     application: array<int, array{key:string,type:'application'|'delegated',description:?string,features:array<int,string>,status:'granted'|'missing'|'error',details:array<string,mixed>|null}>,
     *     delegated: array<int, array{key:string,type:'application'|'delegated',description:?string,features:array<int,string>,status:'granted'|'missing'|'error',details:array<string,mixed>|null}>
     *   },
     *   copy: array{application:string,delegated:string},
     *   actions: array{
     *     full_page: array{label:string,url:string,opens_in_new_tab:bool,available:bool,is_secondary:bool},
     *     copy_application: array{label:string,payload:string,available:bool},
     *     copy_delegated: array{label:string,payload:string,available:bool},
     *     grant_admin_consent: array{label:string,url:?string,opens_in_new_tab:bool,available:bool},
     *     manage_provider_connection: array{label:string,url:?string,opens_in_new_tab:bool,available:bool},
     *     rerun_verification: array{label:string,handled_by_existing_wizard:true}
     *   },
     *   fallback: array{has_incomplete_detail:bool,message:?string}
     * }
     */
    public function build(
        ManagedEnvironment $tenant,
        ?array $verificationReport,
        ?ProviderConnection $providerConnection = null,
        ?string $verificationStatus = null,
        bool $isVerificationStale = false,
        ?string $staleReason = null,
        bool $canAccessProviderConnectionDiagnostics = false,
    ): array {
        $requiredPermissionsViewModel = $this->requiredPermissionsViewModel($tenant);
        $tenantViewModel = is_array($requiredPermissionsViewModel['tenant'] ?? null)
            ? $requiredPermissionsViewModel['tenant']
            : [];
        $overview = is_array($requiredPermissionsViewModel['overview'] ?? null)
            ? $requiredPermissionsViewModel['overview']
            : [];
        $counts = $this->normalizeCounts(is_array($overview['counts'] ?? null) ? $overview['counts'] : []);
        $freshness = $this->normalizeFreshness(is_array($overview['freshness'] ?? null) ? $overview['freshness'] : []);
        $capabilityGroups = is_array($overview['capability_groups'] ?? null) ? $overview['capability_groups'] : [];
        $primaryCapabilityGroup = is_array($overview['primary_capability_group'] ?? null)
            ? $overview['primary_capability_group']
            : null;
        $rows = $this->attentionRows(is_array($requiredPermissionsViewModel['permissions'] ?? null)
            ? $requiredPermissionsViewModel['permissions']
            : []);
        $partitionedRows = $this->partitionRows($rows);
        $copy = is_array($requiredPermissionsViewModel['copy'] ?? null)
            ? $requiredPermissionsViewModel['copy']
            : [];

        $reasonCode = $this->primaryRelevantReasonCode($verificationReport);
        $registrySteps = $reasonCode === null
            ? []
            : $this->providerNextStepsRegistry->forReason($tenant, $reasonCode, $providerConnection);

        $fallbackMessage = $this->fallbackMessage(
            verificationReport: $verificationReport,
            overview: $overview,
            counts: $counts,
            rows: $rows,
        );

        return [
            'tenant' => [
                'id' => (int) ($tenantViewModel['id'] ?? $tenant->getKey()),
                'external_id' => (string) ($tenantViewModel['external_id'] ?? $tenant->external_id),
                'name' => (string) (($tenantViewModel['name'] ?? $tenant->name) ?: $tenant->external_id),
            ],
            'verification' => [
                'overall' => $this->verificationOverall($verificationReport),
                'status' => $this->normalizeOptionalString($verificationStatus),
                'is_stale' => $isVerificationStale,
                'stale_reason' => $this->normalizeOptionalString($staleReason),
            ],
            'overview' => [
                'overall' => $this->normalizeOverviewOverall($overview['overall'] ?? null),
                'counts' => $counts,
                'freshness' => $freshness,
                'capability_groups' => $capabilityGroups,
                'primary_capability_group' => $primaryCapabilityGroup,
            ],
            'missing_permissions' => $partitionedRows,
            'copy' => [
                'application' => (string) ($copy['application'] ?? ''),
                'delegated' => (string) ($copy['delegated'] ?? ''),
            ],
            'actions' => [
                'full_page' => [
                    'label' => 'Open full page',
                    'url' => RequiredPermissionsLinks::requiredPermissions($tenant),
                    'opens_in_new_tab' => true,
                    'available' => true,
                    'is_secondary' => true,
                ],
                'copy_application' => [
                    'label' => 'Copy missing application permissions',
                    'payload' => (string) ($copy['application'] ?? ''),
                    'available' => trim((string) ($copy['application'] ?? '')) !== '',
                ],
                'copy_delegated' => [
                    'label' => 'Copy missing delegated permissions',
                    'payload' => (string) ($copy['delegated'] ?? ''),
                    'available' => trim((string) ($copy['delegated'] ?? '')) !== '',
                ],
                'grant_admin_consent' => $this->grantAdminConsentAction($tenant, $counts, $registrySteps),
                'manage_provider_connection' => $this->manageProviderConnectionAction($registrySteps, $canAccessProviderConnectionDiagnostics),
                'rerun_verification' => [
                    'label' => 'Use the existing Start verification action in this step after reviewing changes.',
                    'handled_by_existing_wizard' => true,
                ],
            ],
            'fallback' => [
                'has_incomplete_detail' => $fallbackMessage !== null,
                'message' => $fallbackMessage,
            ],
        ];
    }

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

    /**
     * @param  array<string, mixed>|null  $verificationReport
     * @param  array<string, mixed>  $requiredPermissionsViewModel
     * @return array{is_visible:bool,reason:'permission_blocked'|'permission_attention'|'hidden_ready'|'hidden_irrelevant'}
     */
    private function deriveVisibility(?array $verificationReport, array $requiredPermissionsViewModel): array
    {
        $overview = is_array($requiredPermissionsViewModel['overview'] ?? null)
            ? $requiredPermissionsViewModel['overview']
            : [];
        $overviewOverall = $this->normalizeOverviewOverall($overview['overall'] ?? null);
        $hasRelevantPermissionIssue = $this->reportHasRelevantPermissionIssue($verificationReport);

        if ($overviewOverall === VerificationReportOverall::Blocked->value) {
            return [
                'is_visible' => true,
                'reason' => 'permission_blocked',
            ];
        }

        if ($overviewOverall === VerificationReportOverall::NeedsAttention->value || $hasRelevantPermissionIssue) {
            return [
                'is_visible' => true,
                'reason' => 'permission_attention',
            ];
        }

        if ($overviewOverall === VerificationReportOverall::Ready->value || $this->verificationOverall($verificationReport) === VerificationReportOverall::Ready->value) {
            return [
                'is_visible' => false,
                'reason' => 'hidden_ready',
            ];
        }

        return [
            'is_visible' => false,
            'reason' => 'hidden_irrelevant',
        ];
    }

    /**
     * @param  array<string, mixed>|null  $verificationReport
     * @param  array<string, mixed>  $overview
     * @param  array{missing_application:int,missing_delegated:int,present:int,error:int}  $counts
     * @param  array<int, array{key:string,type:'application'|'delegated',description:?string,features:array<int,string>,status:'granted'|'missing'|'error',details:array<string,mixed>|null}>  $rows
     */
    private function fallbackMessage(?array $verificationReport, array $overview, array $counts, array $rows): ?string
    {
        if ($counts['error'] > 0) {
            return 'Some stored permission details are incomplete. Open the full page or rerun verification for a complete diagnostic view.';
        }

        $freshness = is_array($overview['freshness'] ?? null) ? $overview['freshness'] : [];
        if ((bool) ($freshness['is_stale'] ?? false) && $rows === []) {
            return 'Stored permission data is stale. Open the full page or rerun verification to refresh diagnostics.';
        }

        if ($rows === [] && $this->reportHasRelevantPermissionIssue($verificationReport)) {
            return 'Stored verification found a permissions issue, but the compact detail is incomplete. Open the full page or rerun verification.';
        }

        return null;
    }

    /**
     * @param  array<string, mixed>|null  $verificationReport
     */
    private function verificationOverall(?array $verificationReport): ?string
    {
        $summary = is_array($verificationReport['summary'] ?? null)
            ? $verificationReport['summary']
            : [];
        $overall = $summary['overall'] ?? null;

        return is_string($overall) && in_array($overall, VerificationReportOverall::values(), true)
            ? $overall
            : null;
    }

    /**
     * @param  array<string, mixed>|null  $verificationReport
     */
    private function primaryRelevantReasonCode(?array $verificationReport): ?string
    {
        foreach ($this->relevantChecks($verificationReport) as $check) {
            $reasonCode = $check['reason_code'] ?? null;

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

        return null;
    }

    /**
     * @param  array<string, mixed>|null  $verificationReport
     * @return array<int, array<string, mixed>>
     */
    private function relevantChecks(?array $verificationReport): array
    {
        $checks = is_array($verificationReport['checks'] ?? null)
            ? $verificationReport['checks']
            : [];

        return array_values(array_filter($checks, function (mixed $check): bool {
            if (! is_array($check)) {
                return false;
            }

            $status = $check['status'] ?? null;
            $key = $check['key'] ?? null;
            $reasonCode = $check['reason_code'] ?? null;

            if (! is_string($status) || in_array($status, ['pass', 'skip', 'running'], true)) {
                return false;
            }

            if (is_string($key) && str_starts_with($key, 'permissions.')) {
                return true;
            }

            return in_array($reasonCode, [
                ProviderReasonCodes::ProviderConsentMissing,
                ProviderReasonCodes::ProviderConsentFailed,
                ProviderReasonCodes::ProviderConsentRevoked,
                ProviderReasonCodes::ProviderPermissionMissing,
                ProviderReasonCodes::ProviderPermissionDenied,
                ProviderReasonCodes::ProviderPermissionRefreshFailed,
                ProviderReasonCodes::IntuneRbacPermissionMissing,
            ], true);
        }));
    }

    /**
     * @param  array<string, mixed>|null  $verificationReport
     */
    private function reportHasRelevantPermissionIssue(?array $verificationReport): bool
    {
        return $this->relevantChecks($verificationReport) !== [];
    }

    /**
     * @param  array<string, mixed>  $counts
     * @param  array<int, array{label:string,url:string}>  $registrySteps
     * @return array{label:string,url:?string,opens_in_new_tab:bool,available:bool}
     */
    private function grantAdminConsentAction(ManagedEnvironment $tenant, array $counts, array $registrySteps): array
    {
        $available = ($counts['missing_application'] + $counts['missing_delegated']) > 0;

        if (! $available) {
            return [
                'label' => 'Admin consent guide',
                'url' => null,
                'opens_in_new_tab' => true,
                'available' => false,
            ];
        }

        foreach ($registrySteps as $step) {
            $label = is_string($step['label'] ?? null) ? (string) $step['label'] : '';
            $url = is_string($step['url'] ?? null) ? (string) $step['url'] : '';

            if ($label !== '' && str_contains(strtolower($label), 'consent') && $url !== '') {
                return [
                    'label' => $label,
                    'url' => $url,
                    'opens_in_new_tab' => true,
                    'available' => true,
                ];
            }
        }

        return [
            'label' => 'Admin consent guide',
            'url' => RequiredPermissionsLinks::adminConsentPrimaryUrl($tenant),
            'opens_in_new_tab' => true,
            'available' => true,
        ];
    }

    /**
     * @param  array<int, array{label:string,url:string}>  $registrySteps
     * @return array{label:string,url:?string,opens_in_new_tab:bool,available:bool}
     */
    private function manageProviderConnectionAction(array $registrySteps, bool $canAccessProviderConnectionDiagnostics): array
    {
        if (! $canAccessProviderConnectionDiagnostics) {
            return [
                'label' => 'Manage provider connection',
                'url' => null,
                'opens_in_new_tab' => true,
                'available' => false,
            ];
        }

        foreach ($registrySteps as $step) {
            $label = is_string($step['label'] ?? null) ? (string) $step['label'] : '';
            $url = is_string($step['url'] ?? null) ? (string) $step['url'] : '';
            $path = parse_url($url, PHP_URL_PATH);

            if (is_string($path) && str_contains($path, '/provider-connections')) {
                return [
                    'label' => $label !== '' ? $label : 'Manage provider connection',
                    'url' => $url,
                    'opens_in_new_tab' => true,
                    'available' => true,
                ];
            }
        }

        return [
            'label' => 'Manage provider connection',
            'url' => null,
            'opens_in_new_tab' => true,
            'available' => false,
        ];
    }

    /**
     * @param  array<string, mixed>  $overview
     */
    private function normalizeOverviewOverall(mixed $overview): string
    {
        return is_string($overview) && in_array($overview, [
            VerificationReportOverall::Ready->value,
            VerificationReportOverall::NeedsAttention->value,
            VerificationReportOverall::Blocked->value,
        ], true)
            ? $overview
            : VerificationReportOverall::NeedsAttention->value;
    }

    /**
     * @param  array<string, mixed>  $counts
     * @return array{missing_application:int,missing_delegated:int,present:int,error:int}
     */
    private function normalizeCounts(array $counts): array
    {
        return [
            'missing_application' => $this->normalizeNonNegativeInteger($counts['missing_application'] ?? 0),
            'missing_delegated' => $this->normalizeNonNegativeInteger($counts['missing_delegated'] ?? 0),
            'present' => $this->normalizeNonNegativeInteger($counts['present'] ?? 0),
            'error' => $this->normalizeNonNegativeInteger($counts['error'] ?? 0),
        ];
    }

    /**
     * @param  array<string, mixed>  $freshness
     * @return array{last_refreshed_at:?string,is_stale:bool}
     */
    private function normalizeFreshness(array $freshness): array
    {
        return [
            'last_refreshed_at' => $this->normalizeOptionalString($freshness['last_refreshed_at'] ?? null),
            'is_stale' => (bool) ($freshness['is_stale'] ?? true),
        ];
    }

    /**
     * @param  array<int, array<string, mixed>>  $permissions
     * @return array<int, array{key:string,type:'application'|'delegated',description:?string,features:array<int,string>,status:'granted'|'missing'|'error',details:array<string,mixed>|null}>
     */
    private function attentionRows(array $permissions): array
    {
        return array_values(array_filter($permissions, function (mixed $row): bool {
            return is_array($row) && (($row['status'] ?? null) === 'missing' || ($row['status'] ?? null) === 'error');
        }));
    }

    /**
     * @param  array<int, array{key:string,type:'application'|'delegated',description:?string,features:array<int,string>,status:'granted'|'missing'|'error',details:array<string,mixed>|null}>  $rows
     * @return array{
     *   application: array<int, array{key:string,type:'application'|'delegated',description:?string,features:array<int,string>,status:'granted'|'missing'|'error',details:array<string,mixed>|null}>,
     *   delegated: array<int, array{key:string,type:'application'|'delegated',description:?string,features:array<int,string>,status:'granted'|'missing'|'error',details:array<string,mixed>|null}>
     * }
     */
    private function partitionRows(array $rows): array
    {
        return [
            'application' => array_values(array_filter($rows, static fn (array $row): bool => ($row['type'] ?? null) === 'application')),
            'delegated' => array_values(array_filter($rows, static fn (array $row): bool => ($row['type'] ?? null) === 'delegated')),
        ];
    }

    private function normalizeNonNegativeInteger(mixed $value): int
    {
        if (is_int($value)) {
            return max(0, $value);
        }

        if (is_numeric($value)) {
            return max(0, (int) $value);
        }

        return 0;
    }

    private function normalizeOptionalString(mixed $value): ?string
    {
        if (! is_string($value) || trim($value) === '') {
            return null;
        }

        return trim($value);
    }
}
