<?php

namespace App\Services\Intune;

use App\Models\Policy;
use App\Models\PolicyVersion;
use App\Models\Tenant;
use App\Services\Graph\GraphClientInterface;
use App\Services\Graph\GraphErrorMapper;
use App\Services\Graph\GraphLogger;
use Illuminate\Support\Arr;
use RuntimeException;
use Throwable;

class PolicySyncService
{
    public function __construct(
        private readonly GraphClientInterface $graphClient,
        private readonly GraphLogger $graphLogger,
    ) {}

    /**
     * Sync supported policies for a tenant from Microsoft Graph.
     *
     * @return array<int> IDs of policies synced or created
     */
    public function syncPolicies(Tenant $tenant, ?array $supportedTypes = null): array
    {
        $result = $this->syncPoliciesWithReport($tenant, $supportedTypes);

        return $result['synced'];
    }

    /**
     * Sync supported policies for a tenant from Microsoft Graph.
     *
     * @param  array<int, array{type: string, platform?: string|null, filter?: string|null}>|null  $supportedTypes
     * @return array{synced: array<int>, failures: array<int, array{policy_type: string, status: int|null, errors: array, meta: array}>}
     */
    public function syncPoliciesWithReport(Tenant $tenant, ?array $supportedTypes = null): array
    {
        if (! $tenant->isActive()) {
            throw new \RuntimeException('Tenant is archived or inactive.');
        }

        $types = $supportedTypes ?? config('tenantpilot.supported_policy_types', []);
        $synced = [];
        $failures = [];
        $tenantIdentifier = $tenant->tenant_id ?? $tenant->external_id;

        foreach ($types as $typeConfig) {
            $policyType = $typeConfig['type'];
            $platform = $typeConfig['platform'] ?? null;
            $filter = $typeConfig['filter'] ?? null;

            $this->graphLogger->logRequest('list_policies', [
                'tenant' => $tenantIdentifier,
                'policy_type' => $policyType,
                'platform' => $platform,
                'filter' => $filter,
            ]);

            try {
                $response = $this->graphClient->listPolicies($policyType, [
                    'tenant' => $tenantIdentifier,
                    'client_id' => $tenant->app_client_id,
                    'client_secret' => $tenant->app_client_secret,
                    'platform' => $platform,
                    'filter' => $filter,
                ]);
            } catch (Throwable $throwable) {
                throw GraphErrorMapper::fromThrowable($throwable, [
                    'policy_type' => $policyType,
                    'tenant_id' => $tenant->id,
                    'tenant_identifier' => $tenantIdentifier,
                ]);
            }

            $this->graphLogger->logResponse('list_policies', $response, [
                'policy_type' => $policyType,
                'tenant_id' => $tenant->id,
                'tenant' => $tenantIdentifier,
            ]);

            if ($response->failed()) {
                $failures[] = [
                    'policy_type' => $policyType,
                    'status' => $response->status,
                    'errors' => $response->errors,
                    'meta' => $response->meta,
                ];

                continue;
            }

            foreach ($response->data as $policyData) {
                $externalId = $policyData['id'] ?? $policyData['external_id'] ?? null;

                if ($externalId === null) {
                    continue;
                }

                $canonicalPolicyType = $this->resolveCanonicalPolicyType($policyType, $policyData);

                if ($canonicalPolicyType !== $policyType) {
                    continue;
                }

                if ($policyType === 'appProtectionPolicy') {
                    $odataType = $policyData['@odata.type'] ?? $policyData['@OData.Type'] ?? null;

                    if (is_string($odataType) && strtolower($odataType) === '#microsoft.graph.targetedmanagedappconfiguration') {
                        Policy::query()
                            ->where('tenant_id', $tenant->id)
                            ->where('external_id', $externalId)
                            ->where('policy_type', $policyType)
                            ->whereNull('ignored_at')
                            ->update(['ignored_at' => now()]);

                        continue;
                    }
                }

                $displayName = $policyData['displayName'] ?? $policyData['name'] ?? 'Unnamed policy';
                $policyPlatform = $platform ?? ($policyData['platform'] ?? null);

                $this->reclassifyEnrollmentConfigurationPoliciesIfNeeded(
                    tenantId: $tenant->id,
                    externalId: $externalId,
                    policyType: $policyType,
                );

                $this->reclassifyConfigurationPoliciesIfNeeded(
                    tenantId: $tenant->id,
                    externalId: $externalId,
                    policyType: $policyType,
                );

                $policy = Policy::updateOrCreate(
                    [
                        'tenant_id' => $tenant->id,
                        'external_id' => $externalId,
                        'policy_type' => $policyType,
                    ],
                    [
                        'display_name' => $displayName,
                        'platform' => $policyPlatform,
                        'last_synced_at' => now(),
                        'ignored_at' => null,
                        'metadata' => Arr::except($policyData, ['id', 'external_id', 'displayName', 'name', 'platform']),
                    ]
                );

                $synced[] = $policy->id;
            }
        }

        return [
            'synced' => $synced,
            'failures' => $failures,
        ];
    }

    private function resolveCanonicalPolicyType(string $policyType, array $policyData): string
    {
        if (in_array($policyType, ['settingsCatalogPolicy', 'endpointSecurityPolicy', 'securityBaselinePolicy'], true)) {
            return $this->resolveConfigurationPolicyType($policyData);
        }

        $enrollmentConfigurationTypes = [
            'enrollmentRestriction',
            'windowsEnrollmentStatusPage',
            'deviceEnrollmentLimitConfiguration',
            'deviceEnrollmentPlatformRestrictionsConfiguration',
            'deviceEnrollmentNotificationConfiguration',
        ];

        if (! in_array($policyType, $enrollmentConfigurationTypes, true)) {
            return $policyType;
        }

        if ($this->isEnrollmentStatusPageItem($policyData)) {
            return 'windowsEnrollmentStatusPage';
        }

        if ($this->isEnrollmentNotificationItem($policyData)) {
            return 'deviceEnrollmentNotificationConfiguration';
        }

        if ($this->isEnrollmentLimitItem($policyData)) {
            return 'deviceEnrollmentLimitConfiguration';
        }

        if ($this->isEnrollmentPlatformRestrictionsItem($policyData)) {
            return 'deviceEnrollmentPlatformRestrictionsConfiguration';
        }

        return 'enrollmentRestriction';
    }

    private function resolveConfigurationPolicyType(array $policyData): string
    {
        if ($this->isSecurityBaselineConfigurationPolicy($policyData)) {
            return 'securityBaselinePolicy';
        }

        if ($this->isEndpointSecurityConfigurationPolicy($policyData)) {
            return 'endpointSecurityPolicy';
        }

        return 'settingsCatalogPolicy';
    }

    private function isEndpointSecurityConfigurationPolicy(array $policyData): bool
    {
        $technologies = $policyData['technologies'] ?? null;

        if (is_string($technologies)) {
            if (strcasecmp(trim($technologies), 'endpointSecurity') === 0) {
                return true;
            }
        }

        if (is_array($technologies)) {
            foreach ($technologies as $technology) {
                if (is_string($technology) && strcasecmp(trim($technology), 'endpointSecurity') === 0) {
                    return true;
                }
            }
        }

        $templateReference = $policyData['templateReference'] ?? null;

        if (! is_array($templateReference)) {
            return false;
        }

        $templateFamily = $templateReference['templateFamily'] ?? null;

        return is_string($templateFamily) && str_starts_with(strtolower(trim($templateFamily)), 'endpointsecurity');
    }

    private function isSecurityBaselineConfigurationPolicy(array $policyData): bool
    {
        $templateReference = $policyData['templateReference'] ?? null;

        if (! is_array($templateReference)) {
            return false;
        }

        $templateFamily = $templateReference['templateFamily'] ?? null;

        return is_string($templateFamily) && strcasecmp(trim($templateFamily), 'securityBaseline') === 0;
    }

    private function isEnrollmentStatusPageItem(array $policyData): bool
    {
        $odataType = $policyData['@odata.type'] ?? $policyData['@OData.Type'] ?? null;
        $configurationType = $policyData['deviceEnrollmentConfigurationType'] ?? null;

        return (is_string($odataType) && strcasecmp($odataType, '#microsoft.graph.windows10EnrollmentCompletionPageConfiguration') === 0)
            || (is_string($configurationType) && $configurationType === 'windows10EnrollmentCompletionPageConfiguration');
    }

    private function isEnrollmentLimitItem(array $policyData): bool
    {
        $odataType = $policyData['@odata.type'] ?? $policyData['@OData.Type'] ?? null;
        $configurationType = $policyData['deviceEnrollmentConfigurationType'] ?? null;

        return (is_string($odataType) && strcasecmp($odataType, '#microsoft.graph.deviceEnrollmentLimitConfiguration') === 0)
            || (is_string($configurationType) && strcasecmp($configurationType, 'deviceEnrollmentLimitConfiguration') === 0);
    }

    private function isEnrollmentPlatformRestrictionsItem(array $policyData): bool
    {
        $odataType = $policyData['@odata.type'] ?? $policyData['@OData.Type'] ?? null;
        $configurationType = $policyData['deviceEnrollmentConfigurationType'] ?? null;

        if (is_string($odataType) && $odataType !== '') {
            $odataTypeKey = strtolower($odataType);

            if (in_array($odataTypeKey, [
                '#microsoft.graph.deviceenrollmentplatformrestrictionconfiguration',
                '#microsoft.graph.deviceenrollmentplatformrestrictionsconfiguration',
            ], true)) {
                return true;
            }
        }

        if (is_string($configurationType) && $configurationType !== '') {
            $configurationTypeKey = strtolower($configurationType);

            if (in_array($configurationTypeKey, [
                'deviceenrollmentplatformrestrictionconfiguration',
                'deviceenrollmentplatformrestrictionsconfiguration',
            ], true)) {
                return true;
            }
        }

        return false;
    }

    private function isEnrollmentNotificationItem(array $policyData): bool
    {
        $odataType = $policyData['@odata.type'] ?? $policyData['@OData.Type'] ?? null;
        $configurationType = $policyData['deviceEnrollmentConfigurationType'] ?? null;

        if (is_string($odataType) && strcasecmp($odataType, '#microsoft.graph.deviceEnrollmentNotificationConfiguration') === 0) {
            return true;
        }

        if (! is_string($configurationType) || $configurationType === '') {
            return false;
        }

        return in_array(strtolower($configurationType), [
            'enrollmentnotificationsconfiguration',
            'deviceenrollmentnotificationconfiguration',
        ], true);
    }

    private function reclassifyEnrollmentConfigurationPoliciesIfNeeded(int $tenantId, string $externalId, string $policyType): void
    {
        $enrollmentTypes = [
            'enrollmentRestriction',
            'windowsEnrollmentStatusPage',
            'deviceEnrollmentLimitConfiguration',
            'deviceEnrollmentPlatformRestrictionsConfiguration',
            'deviceEnrollmentNotificationConfiguration',
        ];

        if (! in_array($policyType, $enrollmentTypes, true)) {
            return;
        }

        $existingCorrect = Policy::query()
            ->where('tenant_id', $tenantId)
            ->where('external_id', $externalId)
            ->where('policy_type', $policyType)
            ->first();

        if ($existingCorrect) {
            Policy::query()
                ->where('tenant_id', $tenantId)
                ->where('external_id', $externalId)
                ->whereIn('policy_type', $enrollmentTypes)
                ->where('policy_type', '!=', $policyType)
                ->whereNull('ignored_at')
                ->update(['ignored_at' => now()]);

            return;
        }

        $existingWrong = Policy::query()
            ->where('tenant_id', $tenantId)
            ->where('external_id', $externalId)
            ->whereIn('policy_type', $enrollmentTypes)
            ->where('policy_type', '!=', $policyType)
            ->whereNull('ignored_at')
            ->first();

        if (! $existingWrong) {
            return;
        }

        $existingWrong->forceFill([
            'policy_type' => $policyType,
        ])->save();

        PolicyVersion::query()
            ->where('policy_id', $existingWrong->id)
            ->update(['policy_type' => $policyType]);
    }

    private function reclassifyConfigurationPoliciesIfNeeded(int $tenantId, string $externalId, string $policyType): void
    {
        $configurationTypes = ['settingsCatalogPolicy', 'endpointSecurityPolicy', 'securityBaselinePolicy'];

        if (! in_array($policyType, $configurationTypes, true)) {
            return;
        }

        $existingCorrect = Policy::query()
            ->where('tenant_id', $tenantId)
            ->where('external_id', $externalId)
            ->where('policy_type', $policyType)
            ->first();

        if ($existingCorrect) {
            Policy::query()
                ->where('tenant_id', $tenantId)
                ->where('external_id', $externalId)
                ->whereIn('policy_type', $configurationTypes)
                ->where('policy_type', '!=', $policyType)
                ->whereNull('ignored_at')
                ->update(['ignored_at' => now()]);

            return;
        }

        $existingWrong = Policy::query()
            ->where('tenant_id', $tenantId)
            ->where('external_id', $externalId)
            ->whereIn('policy_type', $configurationTypes)
            ->where('policy_type', '!=', $policyType)
            ->whereNull('ignored_at')
            ->first();

        if (! $existingWrong) {
            return;
        }

        $existingWrong->forceFill([
            'policy_type' => $policyType,
        ])->save();

        PolicyVersion::query()
            ->where('policy_id', $existingWrong->id)
            ->update(['policy_type' => $policyType]);
    }

    /**
     * Re-fetch a single policy from Graph and update local metadata.
     */
    public function syncPolicy(Tenant $tenant, Policy $policy): void
    {
        if (! $tenant->isActive()) {
            throw new RuntimeException('Tenant is archived or inactive.');
        }

        $tenantIdentifier = $tenant->tenant_id ?? $tenant->external_id;

        $this->graphLogger->logRequest('get_policy', [
            'tenant' => $tenantIdentifier,
            'policy_type' => $policy->policy_type,
            'policy_id' => $policy->external_id,
            'platform' => $policy->platform,
        ]);

        try {
            $response = $this->graphClient->getPolicy($policy->policy_type, $policy->external_id, [
                'tenant' => $tenantIdentifier,
                'client_id' => $tenant->app_client_id,
                'client_secret' => $tenant->app_client_secret,
                'platform' => $policy->platform,
            ]);
        } catch (Throwable $throwable) {
            throw GraphErrorMapper::fromThrowable($throwable, [
                'policy_type' => $policy->policy_type,
                'policy_id' => $policy->external_id,
                'tenant_id' => $tenant->id,
                'tenant_identifier' => $tenantIdentifier,
            ]);
        }

        $this->graphLogger->logResponse('get_policy', $response, [
            'tenant_id' => $tenant->id,
            'tenant' => $tenantIdentifier,
            'policy_type' => $policy->policy_type,
            'policy_id' => $policy->external_id,
        ]);

        if ($response->failed()) {
            $message = $response->errors[0]['message'] ?? $response->data['error']['message'] ?? 'Graph request failed.';

            throw new RuntimeException($message);
        }

        $payload = $response->data['payload'] ?? $response->data;

        if (! is_array($payload)) {
            throw new RuntimeException('Invalid Graph response payload.');
        }

        $displayName = $payload['displayName'] ?? $payload['name'] ?? $policy->display_name;
        $platform = $payload['platform'] ?? $policy->platform;

        $policy->forceFill([
            'display_name' => $displayName,
            'platform' => $platform,
            'last_synced_at' => now(),
            'metadata' => Arr::except($payload, ['id', 'external_id', 'displayName', 'name', 'platform']),
        ])->save();
    }
}
