<?php

namespace App\Services\Intune;

use App\Models\Tenant;
use App\Models\TenantPermission;
use App\Services\Graph\GraphClientInterface;

class TenantPermissionService
{
    public function __construct(private readonly GraphClientInterface $graphClient) {}

    /**
     * @return array<int, array{key:string,type:string,description:?string,features:array<int,string>}>
     */
    public function getRequiredPermissions(): array
    {
        return config('intune_permissions.permissions', []);
    }

    /**
     * @return array<string, array{status:string,details:array<string,mixed>|null,last_checked_at:?\Illuminate\Support\Carbon}>
     */
    public function getGrantedPermissions(Tenant $tenant): array
    {
        return TenantPermission::query()
            ->where('tenant_id', $tenant->id)
            ->get()
            ->keyBy('permission_key')
            ->map(fn (TenantPermission $permission) => [
                'status' => $permission->status,
                'details' => $permission->details,
                'last_checked_at' => $permission->last_checked_at,
            ])
            ->all();
    }

    /**
     * @param  array<string, array{status:string,details?:array<string,mixed>|null}|string>|null  $grantedStatuses
     * @param  bool  $persist  Persist comparison results to tenant_permissions
     * @param  bool  $liveCheck  If true, fetch actual permissions from Graph API
     * @param  bool  $useConfiguredStub  Include configured stub permissions when no live check is used
     * @return array{overall_status:string,permissions:array<int,array{key:string,type:string,description:?string,features:array<int,string>,status:string,details:array<string,mixed>|null}>}
     */
    public function compare(
        Tenant $tenant,
        ?array $grantedStatuses = null,
        bool $persist = true,
        bool $liveCheck = false,
        bool $useConfiguredStub = true
    ): array {
        $required = $this->getRequiredPermissions();
        $liveCheckFailed = false;
        $liveCheckDetails = null;

        // If liveCheck is requested, fetch actual permissions from Graph
        if ($liveCheck && $grantedStatuses === null) {
            $grantedStatuses = $this->fetchLivePermissions($tenant);

            if (isset($grantedStatuses['__error'])) {
                $liveCheckFailed = true;
                $liveCheckDetails = $grantedStatuses['__error']['details'] ?? null;
                unset($grantedStatuses['__error']);
            }
        }

        $storedStatuses = $this->getGrantedPermissions($tenant);

        if (! $useConfiguredStub) {
            $storedStatuses = $this->dropConfiguredStatuses($storedStatuses);
        }

        $granted = $this->normalizeGrantedStatuses(
            $grantedStatuses ?? array_replace_recursive(
                $useConfiguredStub && ! $liveCheck ? $this->configuredGrantedStatuses() : [],
                $storedStatuses
            )
        );
        $results = [];
        $hasMissing = false;
        $hasErrors = false;
        $checkedAt = now();

        foreach ($required as $permission) {
            $key = $permission['key'];
            $status = $liveCheckFailed
                ? 'error'
                : ($granted[$key]['status'] ?? 'missing');
            $details = $liveCheckFailed
                ? ($liveCheckDetails ?? ['source' => 'graph_api'])
                : ($granted[$key]['details'] ?? null);

            if ($persist) {
                TenantPermission::updateOrCreate(
                    [
                        'tenant_id' => $tenant->id,
                        'permission_key' => $key,
                    ],
                    [
                        'status' => $status,
                        'details' => $details,
                        'last_checked_at' => $checkedAt,
                    ]
                );
            }

            $results[] = [
                'key' => $key,
                'type' => $permission['type'] ?? 'application',
                'description' => $permission['description'] ?? null,
                'features' => $permission['features'] ?? [],
                'status' => $status,
                'details' => $details,
            ];

            $hasMissing = $hasMissing || $status === 'missing';
            $hasErrors = $hasErrors || $status === 'error';
        }

        $overall = match (true) {
            $hasErrors => 'error',
            $hasMissing => 'missing',
            default => 'granted',
        };

        return [
            'overall_status' => $overall,
            'permissions' => $results,
        ];
    }

    /**
     * @param  array<string, array{status:string,details?:array<string,mixed>|null}|string>  $granted
     * @return array<string, array{status:string,details:array<string,mixed>|null}>
     */
    private function normalizeGrantedStatuses(array $granted): array
    {
        $normalized = [];

        foreach ($granted as $key => $value) {
            if (is_string($value)) {
                $normalized[$key] = ['status' => $value, 'details' => null];

                continue;
            }

            $normalized[$key] = [
                'status' => $value['status'] ?? 'missing',
                'details' => $value['details'] ?? null,
            ];
        }

        return $normalized;
    }

    /**
     * @param  array<string, array{status:string,details:array<string,mixed>|null,last_checked_at:?\Illuminate\Support\Carbon}>  $granted
     * @return array<string, array{status:string,details:array<string,mixed>|null,last_checked_at:?\Illuminate\Support\Carbon}>
     */
    private function dropConfiguredStatuses(array $granted): array
    {
        foreach ($granted as $key => $value) {
            $source = $value['details']['source'] ?? null;

            if ($source === 'configured') {
                unset($granted[$key]);
            }
        }

        return $granted;
    }

    /**
     * @return array<string, array{status:string,details:array<string,mixed>|null}>
     */
    public function configuredGrantedStatuses(): array
    {
        $configured = $this->configuredGrantedKeys();
        $normalized = [];

        foreach ($configured as $key) {
            $normalized[$key] = [
                'status' => 'granted',
                'details' => ['source' => 'configured'],
            ];
        }

        return $normalized;
    }

    /**
     * @return array<int, string>
     */
    private function configuredGrantedKeys(): array
    {
        $env = env('INTUNE_GRANTED_PERMISSIONS');

        if (is_string($env) && filled($env)) {
            return collect(explode(',', $env))
                ->map(fn (string $key) => trim($key))
                ->filter()
                ->values()
                ->all();
        }

        return config('intune_permissions.granted_stub', []);
    }

    /**
     * Fetch actual granted permissions from Graph API.
     *
     * @return array<string, array{status:string,details:array<string,mixed>|null}>
     */
    private function fetchLivePermissions(Tenant $tenant): array
    {
        try {
            $response = $this->graphClient->getServicePrincipalPermissions(
                $tenant->graphOptions()
            );

            if (! $response->success) {
                return [
                    '__error' => [
                        'status' => 'error',
                        'details' => [
                            'source' => 'graph_api',
                            'status' => $response->status,
                            'errors' => $response->errors,
                        ],
                    ],
                ];
            }

            $grantedPermissions = $response->data['permissions'] ?? [];
            $normalized = [];

            foreach ($grantedPermissions as $permission) {
                $normalized[$permission] = [
                    'status' => 'granted',
                    'details' => ['source' => 'graph_api', 'checked_at' => now()->toIso8601String()],
                ];
            }

            return $normalized;
        } catch (\Throwable $e) {
            // Log error but don't fail - fall back to config
            \Log::warning('Failed to fetch live permissions from Graph', [
                'tenant_id' => $tenant->id,
                'error' => $e->getMessage(),
            ]);

            return [
                '__error' => [
                    'status' => 'error',
                    'details' => [
                        'source' => 'graph_api',
                        'message' => $e->getMessage(),
                    ],
                ],
            ];
        }
    }
}
