<?php

namespace App\Services\Intune;

use App\Models\SettingsCatalogDefinition;
use App\Services\Graph\GraphClientInterface;
use App\Services\Graph\GraphContractRegistry;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Str;

class SettingsCatalogDefinitionResolver
{
    private const CACHE_TTL = 60 * 60 * 24 * 30; // 30 days

    private const MEMORY_CACHE_PREFIX = 'settings_catalog_def_';

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

    /**
     * Resolve multiple definition IDs to their metadata.
     *
     * @param  array<string>  $definitionIds
     * @return array<string, array> Map of definitionId => metadata
     */
    public function resolve(array $definitionIds): array
    {
        if (empty($definitionIds)) {
            return [];
        }

        $definitions = [];
        $missingIds = [];

        // Step 1: Check memory cache
        foreach ($definitionIds as $id) {
            $cached = Cache::get(self::MEMORY_CACHE_PREFIX.$id);
            if ($cached) {
                $definitions[$id] = $cached;
            } else {
                $missingIds[] = $id;
            }
        }

        if (empty($missingIds)) {
            return $definitions;
        }

        // Step 2: Check database cache
        $dbDefinitions = SettingsCatalogDefinition::findByDefinitionIds($missingIds);
        foreach ($dbDefinitions as $definitionId => $dbDef) {
            $metadata = $this->transformToMetadata($dbDef);
            $definitions[$definitionId] = $metadata;

            // Cache in memory
            Cache::put(
                self::MEMORY_CACHE_PREFIX.$definitionId,
                $metadata,
                now()->addSeconds(self::CACHE_TTL)
            );

            $missingIds = array_diff($missingIds, [$definitionId]);
        }

        if (empty($missingIds)) {
            return $definitions;
        }

        // Step 3: Fetch from Graph API
        try {
            $graphDefinitions = $this->fetchFromGraph($missingIds);

            foreach ($graphDefinitions as $definitionId => $metadata) {
                // Store in database
                $this->storeInDatabase($definitionId, $metadata);

                // Cache in memory
                Cache::put(
                    self::MEMORY_CACHE_PREFIX.$definitionId,
                    $metadata,
                    now()->addSeconds(self::CACHE_TTL)
                );

                $definitions[$definitionId] = $metadata;
            }
        } catch (\Exception $e) {
            Log::error('Failed to fetch setting definitions from Graph API', [
                'definition_ids' => $missingIds,
                'error' => $e->getMessage(),
            ]);
        }

        // Step 4: Fallback for still missing definitions
        foreach ($missingIds as $id) {
            if (! isset($definitions[$id])) {
                $fallback = $this->getFallbackMetadata($id);
                $definitions[$id] = $fallback;

                // Cache fallback in memory too (short TTL since it's not real data)
                Cache::put(
                    self::MEMORY_CACHE_PREFIX.$id,
                    $fallback,
                    now()->addMinutes(5) // Shorter TTL for fallbacks
                );
            }
        }

        return $definitions;
    }

    /**
     * Resolve a single definition ID.
     */
    public function resolveOne(string $definitionId): ?array
    {
        $result = $this->resolve([$definitionId]);

        return $result[$definitionId] ?? null;
    }

    /**
     * Warm cache for definition IDs without returning data.
     * Non-blocking: catches and logs errors.
     */
    public function warmCache(array $definitionIds): void
    {
        try {
            $this->resolve($definitionIds);
        } catch (\Exception $e) {
            Log::warning('Failed to warm cache for setting definitions', [
                'definition_ids' => $definitionIds,
                'error' => $e->getMessage(),
            ]);
        }
    }

    /**
     * Clear cache for a specific definition or all definitions.
     */
    public function clearCache(?string $definitionId = null): void
    {
        if ($definitionId) {
            Cache::forget(self::MEMORY_CACHE_PREFIX.$definitionId);
            SettingsCatalogDefinition::where('definition_id', $definitionId)->delete();
        } else {
            // Clear all memory cache (prefix-based)
            Cache::flush();
            SettingsCatalogDefinition::truncate();
        }
    }

    /**
     * Fetch definitions from Graph API.
     *
     * @param  array<string>  $definitionIds
     * @return array<string, array>
     */
    private function fetchFromGraph(array $definitionIds): array
    {
        $definitions = [];

        // Note: Microsoft Graph API does not support "in" operator for $filter.
        // We fetch each definition individually.
        foreach ($definitionIds as $definitionId) {
            // Skip template IDs with placeholders - these are not real definition IDs
            if (str_contains($definitionId, '{') || str_contains($definitionId, '}')) {
                Log::info('Skipping template definition ID', ['definitionId' => $definitionId]);

                continue;
            }

            try {
                $path = $this->contracts->settingsCatalogDefinitionItemPath($definitionId);
                $response = $this->graphClient->request(
                    'GET',
                    $path
                );

                if ($response->successful() && isset($response->data)) {
                    $item = $response->data;
                    $definitions[$definitionId] = [
                        'displayName' => $item['displayName'] ?? $this->prettifyDefinitionId($definitionId),
                        'description' => $item['description'] ?? null,
                        'helpText' => $item['helpText'] ?? null,
                        'categoryId' => $item['categoryId'] ?? null,
                        'uxBehavior' => $item['uxBehavior'] ?? null,
                        'raw' => $item,
                    ];
                }
            } catch (\Exception $e) {
                Log::warning('Failed to fetch definition from Graph API', [
                    'definitionId' => $definitionId,
                    'error' => $e->getMessage(),
                ]);
                // Continue with other definitions
            }
        }

        return $definitions;
    }

    /**
     * Store definition in database.
     */
    private function storeInDatabase(string $definitionId, array $metadata): void
    {
        try {
            SettingsCatalogDefinition::updateOrCreate(
                ['definition_id' => $definitionId],
                [
                    'display_name' => $metadata['displayName'],
                    'description' => $metadata['description'],
                    'help_text' => $metadata['helpText'],
                    'category_id' => $metadata['categoryId'],
                    'ux_behavior' => $metadata['uxBehavior'],
                    'raw' => $metadata['raw'] ?? [],
                ]
            );
            Log::info('Stored definition in database', ['definition_id' => $definitionId]);
        } catch (\Exception $e) {
            Log::error('Failed to store definition in database', [
                'definition_id' => $definitionId,
                'error' => $e->getMessage(),
                'metadata' => $metadata,
            ]);
            throw $e;
        }
    }

    /**
     * Transform database model to metadata array.
     */
    private function transformToMetadata(SettingsCatalogDefinition $definition): array
    {
        return [
            'displayName' => $definition->display_name,
            'description' => $definition->description,
            'helpText' => $definition->help_text,
            'categoryId' => $definition->category_id,
            'uxBehavior' => $definition->ux_behavior,
            'raw' => $definition->raw,
        ];
    }

    /**
     * Get fallback metadata for unknown definition.
     */
    private function getFallbackMetadata(string $definitionId): array
    {
        return [
            'displayName' => $this->prettifyDefinitionId($definitionId),
            'description' => null,
            'helpText' => null,
            'categoryId' => null,
            'uxBehavior' => null,
            'raw' => null,
            'isFallback' => true,
        ];
    }

    /**
     * Prettify definition ID for fallback display.
     * Example: "device_vendor_msft_policy_name" → "Device Vendor Msft Policy Name"
     * Special handling for {tenantid} placeholders (Microsoft template definitions).
     */
    public function prettifyDefinitionId(string $definitionId): string
    {
        // Remove {tenantid} placeholder - it's a Microsoft template variable, not part of the name
        $cleaned = str_replace(['{tenantid}', '_tenantid_', '_{tenantid}_'], ['', '_', '_'], $definitionId);

        // Remove other template placeholders, e.g. "{FirewallRuleId}"
        $cleaned = preg_replace('/\{[^}]+\}/', '', $cleaned);

        // Clean up consecutive underscores
        $cleaned = preg_replace('/_+/', '_', $cleaned);
        $cleaned = trim($cleaned, '_');

        $lowered = Str::lower($cleaned);

        if (str_starts_with($lowered, 'vendor_msft_firewall_mdmstore_firewallrules')) {
            $suffix = ltrim(substr($lowered, strlen('vendor_msft_firewall_mdmstore_firewallrules')), '_');

            if ($suffix === '') {
                return 'Firewall rule';
            }

            $known = [
                'displayname' => 'Name',
                'name' => 'Name',
                'description' => 'Description',
                'direction' => 'Direction',
                'action' => 'Action',
                'actiontype' => 'Action type',
                'profiles' => 'Profiles',
                'profile' => 'Profile',
                'protocol' => 'Protocol',
                'localport' => 'Local port',
                'remoteport' => 'Remote port',
                'localaddress' => 'Local address',
                'remoteaddress' => 'Remote address',
                'interfacetype' => 'Interface type',
                'interfacetypes' => 'Interface types',
                'edgetraversal' => 'Edge traversal',
                'enabled' => 'Enabled',
            ];

            if (isset($known[$suffix])) {
                return $known[$suffix];
            }

            return Str::headline($suffix);
        }

        // Convert to title case
        $prettified = Str::title(str_replace('_', ' ', $cleaned));

        // Remove redundant prefixes to shorten labels
        $prettified = preg_replace('/^Device Vendor Msft Passportforwork Policies\s+/', '', $prettified);
        $prettified = preg_replace('/^Device Vendor Msft Passportforwork\s+/', 'Windows Hello - ', $prettified);

        // Shorten common terms
        $prettified = str_replace('Pincomplexity', 'PIN', $prettified);
        $prettified = str_replace('Usepassportforwork', 'Enable Windows Hello', $prettified);

        return $prettified;
    }
}
