<?php

namespace App\Services\Intune;

use App\Models\ManagedEnvironment;
use App\Services\Graph\GraphClientInterface;
use App\Services\Graph\GraphContractRegistry;
use App\Services\Providers\MicrosoftGraphOptionsResolver;
use Illuminate\Support\Arr;

class ConfigurationPolicyTemplateResolver
{
    /**
     * @var array<string, array<string, array{success:bool,template:?array,reason:?string}>>
     */
    private array $templateCache = [];

    /**
     * @var array<string, array<string, array{success:bool,templates:array<int,array>,reason:?string}>>
     */
    private array $familyCache = [];

    /**
     * @var array<string, array<string, array{success:bool,definition_ids:array<int,string>,reason:?string}>>
     */
    private array $templateDefinitionCache = [];

    public function __construct(
        private readonly GraphClientInterface $graphClient,
        private readonly GraphContractRegistry $contracts,
        private readonly MicrosoftGraphOptionsResolver $graphOptionsResolver,
    ) {}

    /**
     * @param  array<string, mixed>  $templateReference
     * @param  array<string, mixed>  $graphOptions
     * @return array{success:bool,template_id:?string,template_reference:?array,reason:?string,warnings:array<int,string>}
     */
    public function resolveTemplateReference(ManagedEnvironment $tenant, array $templateReference, array $graphOptions = []): array
    {
        $warnings = [];

        $templateId = $this->extractString($templateReference, ['templateId', 'TemplateId']);
        $templateFamily = $this->extractString($templateReference, ['templateFamily', 'TemplateFamily']);
        $templateDisplayName = $this->extractString($templateReference, ['templateDisplayName', 'TemplateDisplayName']);
        $templateDisplayVersion = $this->extractString($templateReference, ['templateDisplayVersion', 'TemplateDisplayVersion']);

        if ($templateId !== null) {
            $templateOutcome = $this->getTemplate($tenant, $templateId, $graphOptions);

            if ($templateOutcome['success']) {
                return [
                    'success' => true,
                    'template_id' => $templateId,
                    'template_reference' => $templateReference,
                    'reason' => null,
                    'warnings' => $warnings,
                ];
            }

            if ($templateFamily === null) {
                return [
                    'success' => false,
                    'template_id' => null,
                    'template_reference' => null,
                    'reason' => $templateOutcome['reason'] ?? "Template '{$templateId}' is not available in the tenant.",
                    'warnings' => $warnings,
                ];
            }
        }

        if ($templateFamily === null) {
            return [
                'success' => false,
                'template_id' => null,
                'template_reference' => null,
                'reason' => 'Template reference is missing templateFamily and cannot be resolved.',
                'warnings' => $warnings,
            ];
        }

        $listOutcome = $this->listTemplatesByFamily($tenant, $templateFamily, $graphOptions);

        if (! $listOutcome['success']) {
            return [
                'success' => false,
                'template_id' => null,
                'template_reference' => null,
                'reason' => $listOutcome['reason'] ?? "Unable to list templates for family '{$templateFamily}'.",
                'warnings' => $warnings,
            ];
        }

        $candidates = $this->chooseTemplateCandidate(
            templates: $listOutcome['templates'],
            templateDisplayName: $templateDisplayName,
            templateDisplayVersion: $templateDisplayVersion,
        );

        if (count($candidates) !== 1) {
            $reason = count($candidates) === 0
                ? "No templates found for family '{$templateFamily}'."
                : "Multiple templates found for family '{$templateFamily}' (cannot resolve automatically).";

            return [
                'success' => false,
                'template_id' => null,
                'template_reference' => null,
                'reason' => $reason,
                'warnings' => $warnings,
            ];
        }

        $candidate = $candidates[0];
        $resolvedId = is_array($candidate) ? ($candidate['id'] ?? null) : null;

        if (! is_string($resolvedId) || $resolvedId === '') {
            return [
                'success' => false,
                'template_id' => null,
                'template_reference' => null,
                'reason' => "Template candidate for family '{$templateFamily}' is missing an id.",
                'warnings' => $warnings,
            ];
        }

        if ($templateId !== null && $templateId !== $resolvedId) {
            $warnings[] = sprintf("TemplateId '%s' not found; mapped to '%s' via templateFamily.", $templateId, $resolvedId);
        }

        $templateReference['templateId'] = $resolvedId;

        if (! isset($templateReference['templateDisplayName']) && isset($candidate['displayName'])) {
            $templateReference['templateDisplayName'] = $candidate['displayName'];
        }

        if (! isset($templateReference['templateDisplayVersion']) && isset($candidate['displayVersion'])) {
            $templateReference['templateDisplayVersion'] = $candidate['displayVersion'];
        }

        return [
            'success' => true,
            'template_id' => $resolvedId,
            'template_reference' => $templateReference,
            'reason' => null,
            'warnings' => $warnings,
        ];
    }

    /**
     * @param  array<string, mixed>  $graphOptions
     * @return array{success:bool,template:?array,reason:?string}
     */
    public function getTemplate(ManagedEnvironment $tenant, string $templateId, array $graphOptions = []): array
    {
        $tenantKey = $this->tenantKey($tenant, $graphOptions);

        if (isset($this->templateCache[$tenantKey][$templateId])) {
            return $this->templateCache[$tenantKey][$templateId];
        }

        $context = array_merge(
            $this->graphOptionsResolver->resolveForTenant($tenant),
            Arr::except($graphOptions, ['platform']),
        );
        $path = $this->contracts->configurationPolicyTemplateItemPath($templateId);
        $response = $this->graphClient->request('GET', $path, $context);

        if ($response->failed()) {
            return $this->templateCache[$tenantKey][$templateId] = [
                'success' => false,
                'template' => null,
                'reason' => $response->meta['error_message'] ?? 'Template lookup failed.',
            ];
        }

        return $this->templateCache[$tenantKey][$templateId] = [
            'success' => true,
            'template' => $response->data,
            'reason' => null,
        ];
    }

    /**
     * @param  array<string, mixed>  $graphOptions
     * @return array{success:bool,templates:array<int,array>,reason:?string}
     */
    public function listTemplatesByFamily(ManagedEnvironment $tenant, string $templateFamily, array $graphOptions = []): array
    {
        $tenantKey = $this->tenantKey($tenant, $graphOptions);
        $cacheKey = strtolower($templateFamily);

        if (isset($this->familyCache[$tenantKey][$cacheKey])) {
            return $this->familyCache[$tenantKey][$cacheKey];
        }

        $escapedFamily = str_replace("'", "''", $templateFamily);

        $context = array_merge($this->graphOptionsResolver->resolveForTenant($tenant), Arr::except($graphOptions, ['platform']), [
            'query' => [
                '$filter' => "templateFamily eq '{$escapedFamily}'",
                '$top' => 999,
            ],
        ]);

        $response = $this->graphClient->request('GET', $this->contracts->configurationPolicyTemplateListPath(), $context);

        if ($response->failed()) {
            return $this->familyCache[$tenantKey][$cacheKey] = [
                'success' => false,
                'templates' => [],
                'reason' => $response->meta['error_message'] ?? 'Template list failed.',
            ];
        }

        $value = $response->data['value'] ?? [];
        $templates = is_array($value) ? array_values(array_filter($value, static fn ($item) => is_array($item))) : [];

        return $this->familyCache[$tenantKey][$cacheKey] = [
            'success' => true,
            'templates' => $templates,
            'reason' => null,
        ];
    }

    /**
     * @param  array<string, mixed>  $graphOptions
     * @return array{success:bool,definition_ids:array<int,string>,reason:?string}
     */
    public function fetchTemplateSettingDefinitionIds(ManagedEnvironment $tenant, string $templateId, array $graphOptions = []): array
    {
        $tenantKey = $this->tenantKey($tenant, $graphOptions);

        if (isset($this->templateDefinitionCache[$tenantKey][$templateId])) {
            return $this->templateDefinitionCache[$tenantKey][$templateId];
        }

        $context = array_merge($this->graphOptionsResolver->resolveForTenant($tenant), Arr::except($graphOptions, ['platform']), [
            'query' => [
                '$expand' => 'settingDefinitions',
                '$top' => 999,
            ],
        ]);

        $path = $this->contracts->configurationPolicyTemplateSettingTemplatesPath($templateId);
        $response = $this->graphClient->request('GET', $path, $context);

        if ($response->failed()) {
            return $this->templateDefinitionCache[$tenantKey][$templateId] = [
                'success' => false,
                'definition_ids' => [],
                'reason' => $response->meta['error_message'] ?? 'Template definitions lookup failed.',
            ];
        }

        $value = $response->data['value'] ?? [];
        $templates = is_array($value) ? $value : [];
        $definitionIds = [];

        foreach ($templates as $settingTemplate) {
            if (! is_array($settingTemplate)) {
                continue;
            }

            $definitions = $settingTemplate['settingDefinitions'] ?? null;

            if (! is_array($definitions)) {
                continue;
            }

            foreach ($definitions as $definition) {
                if (! is_array($definition)) {
                    continue;
                }

                $id = $definition['id'] ?? null;

                if (is_string($id) && $id !== '') {
                    $definitionIds[] = $id;
                }
            }
        }

        $definitionIds = array_values(array_unique($definitionIds));

        return $this->templateDefinitionCache[$tenantKey][$templateId] = [
            'success' => true,
            'definition_ids' => $definitionIds,
            'reason' => null,
        ];
    }

    /**
     * @param  array<int, mixed>  $settings
     * @return array<int, string>
     */
    public function extractSettingDefinitionIds(array $settings): array
    {
        $ids = [];

        $walk = function (mixed $node) use (&$walk, &$ids): void {
            if (! is_array($node)) {
                return;
            }

            foreach ($node as $key => $value) {
                if (is_string($key) && strtolower($key) === 'settingdefinitionid' && is_string($value) && $value !== '') {
                    $ids[] = $value;
                }

                $walk($value);
            }
        };

        $walk($settings);

        return array_values(array_unique($ids));
    }

    /**
     * @param  array<int, array>  $templates
     * @return array<int, array>
     */
    private function chooseTemplateCandidate(array $templates, ?string $templateDisplayName, ?string $templateDisplayVersion): array
    {
        $candidates = $templates;

        $active = array_values(array_filter($candidates, static function (array $template): bool {
            $state = $template['lifecycleState'] ?? null;

            return is_string($state) && strtolower($state) === 'active';
        }));

        if ($active !== []) {
            $candidates = $active;
        }

        if ($templateDisplayVersion !== null) {
            $byVersion = array_values(array_filter($candidates, static function (array $template) use ($templateDisplayVersion): bool {
                $version = $template['displayVersion'] ?? null;

                return is_string($version) && $version === $templateDisplayVersion;
            }));

            if ($byVersion !== []) {
                $candidates = $byVersion;
            }
        }

        if ($templateDisplayName !== null) {
            $byName = array_values(array_filter($candidates, static function (array $template) use ($templateDisplayName): bool {
                $name = $template['displayName'] ?? null;

                return is_string($name) && $name === $templateDisplayName;
            }));

            if ($byName !== []) {
                $candidates = $byName;
            }
        }

        return $candidates;
    }

    /**
     * @param  array<string, mixed>  $payload
     * @param  array<int, string>  $keys
     */
    private function extractString(array $payload, array $keys): ?string
    {
        $normalized = array_map('strtolower', $keys);

        foreach ($payload as $key => $value) {
            if (! is_string($key) || ! in_array(strtolower($key), $normalized, true)) {
                continue;
            }

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

        return null;
    }

    /**
     * @param  array<string, mixed>  $graphOptions
     */
    private function tenantKey(ManagedEnvironment $tenant, array $graphOptions): string
    {
        $tenantId = $graphOptions['tenant'] ?? $tenant->graphTenantId() ?? (string) $tenant->getKey();

        return (string) $tenantId;
    }
}
