<?php

namespace App\Jobs;

use App\Jobs\Concerns\BridgesFailedOperationRun;
use App\Jobs\Middleware\EnsureQueuedExecutionLegitimate;
use App\Jobs\Middleware\TrackOperationRun;
use App\Models\OperationRun;
use App\Models\Policy;
use App\Models\ManagedEnvironment;
use App\Services\Graph\GraphClientInterface;
use App\Services\Graph\NullGraphClient;
use App\Services\Intune\PolicySyncService;
use App\Services\OperationRunService;
use App\Support\OperationRunOutcome;
use App\Support\OperationRunStatus;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;

class SyncPoliciesJob implements ShouldQueue
{
    use BridgesFailedOperationRun;
    use Dispatchable;
    use InteractsWithQueue;
    use Queueable;
    use SerializesModels;

    public int $timeout = 180;

    public bool $failOnTimeout = true;

    public ?OperationRun $operationRun = null;

    /**
     * @param  array<int, string>|array<int, array{type: string, platform?: string|null, filter?: string|null}>|null  $types
     * @param  array<int, int>|null  $policyIds
     */
    public function __construct(
        public readonly int $tenantId,
        public readonly ?array $types = null,
        public readonly ?array $policyIds = null,
        ?OperationRun $operationRun = null
    ) {
        $this->operationRun = $operationRun;
    }

    public function middleware(): array
    {
        return [new EnsureQueuedExecutionLegitimate, new TrackOperationRun];
    }

    public function handle(PolicySyncService $service, OperationRunService $operationRunService): void
    {
        $graph = app(GraphClientInterface::class);

        if ($graph instanceof NullGraphClient) {
            if ($this->operationRun) {
                $operationRunService->updateRun(
                    $this->operationRun,
                    status: OperationRunStatus::Completed->value,
                    outcome: OperationRunOutcome::Failed->value,
                    failures: [
                        [
                            'code' => 'graph.disabled',
                            'message' => 'Microsoft Graph is not enabled. Set GRAPH_ENABLED=true (and/or GRAPH_TENANT_ID) in .env to use the real Graph client.',
                        ],
                    ],
                );

                return;
            }

            throw new \RuntimeException('Microsoft Graph is not enabled (GRAPH_ENABLED/GRAPH_TENANT_ID missing).');
        }

        $tenant = ManagedEnvironment::findOrFail($this->tenantId);

        if ($this->policyIds !== null) {
            $ids = collect($this->policyIds)
                ->map(static fn ($id): int => (int) $id)
                ->unique()
                ->sort()
                ->values();

            $syncedCount = 0;
            $skippedCount = 0;
            $failureSummary = [];

            foreach ($ids as $policyId) {
                $policy = Policy::query()
                    ->whereKey($policyId)
                    ->where('managed_environment_id', $tenant->getKey())
                    ->first();

                if (! $policy) {
                    $failureSummary[] = [
                        'code' => 'policy.not_found',
                        'message' => "Policy {$policyId} not found",
                    ];

                    continue;
                }

                if ($policy->ignored_at !== null) {
                    $skippedCount++;

                    continue;
                }

                try {
                    $service->syncPolicy($tenant, $policy);

                    $syncedCount++;
                } catch (\Throwable $e) {
                    $failureSummary[] = [
                        'code' => 'policy.sync_failed',
                        'message' => $e->getMessage(),
                    ];
                }
            }

            $failureCount = count($failureSummary);
            $outcome = match (true) {
                $failureCount === 0 => OperationRunOutcome::Succeeded->value,
                $syncedCount > 0 => OperationRunOutcome::PartiallySucceeded->value,
                default => OperationRunOutcome::Failed->value,
            };

            if ($this->operationRun) {
                $operationRunService->updateRun(
                    $this->operationRun,
                    status: OperationRunStatus::Completed->value,
                    outcome: $outcome,
                    summaryCounts: [
                        'total' => $ids->count(),
                        'processed' => $ids->count(),
                        'succeeded' => $syncedCount,
                        'failed' => $failureCount,
                        'skipped' => $skippedCount,
                    ],
                    failures: $failureSummary,
                );
            }

            return;
        }

        $supported = config('tenantpilot.supported_policy_types', []);

        if ($this->types !== null) {
            $first = $this->types[0] ?? null;
            $typesLookLikeSupportedConfig = is_array($first) && array_key_exists('type', $first);

            if ($typesLookLikeSupportedConfig) {
                $supported = array_values(array_filter(
                    $this->types,
                    static fn ($type): bool => is_array($type) && isset($type['type']) && is_string($type['type']) && $type['type'] !== ''
                ));
            } else {
                $requestedTypes = array_values(array_unique(array_filter(array_map(
                    static fn ($type): ?string => is_string($type) ? $type : (is_array($type) ? (string) ($type['type'] ?? '') : null),
                    $this->types,
                ), static fn ($type): bool => is_string($type) && $type !== '')));

                $supported = array_values(array_filter(
                    $supported,
                    static fn ($type): bool => is_array($type)
                        && isset($type['type'])
                        && is_string($type['type'])
                        && in_array($type['type'], $requestedTypes, true)
                ));
            }
        }

        if ($supported === []) {
            if ($this->operationRun) {
                $operationRunService->updateRun(
                    $this->operationRun,
                    status: OperationRunStatus::Completed->value,
                    outcome: OperationRunOutcome::Failed->value,
                    failures: [
                        [
                            'code' => $this->types === null
                                ? 'tenantpilot.supported_policy_types.empty'
                                : 'tenantpilot.supported_policy_types.no_match',
                            'message' => $this->types === null
                                ? 'No supported policy types configured (tenantpilot.supported_policy_types is empty).'
                                : 'No requested policy types matched the supported policy type configuration.',
                        ],
                    ],
                );
            }

            return;
        }

        $result = $service->syncPoliciesWithReport($tenant, $supported);
        $syncedCount = count($result['synced'] ?? []);
        $failures = $result['failures'] ?? [];
        $failureCount = count($failures);

        $outcome = match (true) {
            $failureCount === 0 => OperationRunOutcome::Succeeded->value,
            $syncedCount > 0 => OperationRunOutcome::PartiallySucceeded->value,
            default => OperationRunOutcome::Failed->value,
        };

        $failureSummary = [];

        foreach ($failures as $failure) {
            if (! is_array($failure)) {
                continue;
            }

            $policyType = (string) ($failure['policy_type'] ?? 'unknown');
            $status = is_numeric($failure['status'] ?? null) ? (int) $failure['status'] : null;
            $errors = $failure['errors'] ?? null;

            $firstErrorMessage = null;
            if (is_array($errors) && isset($errors[0]) && is_array($errors[0])) {
                $firstErrorMessage = $errors[0]['message'] ?? null;
            }

            $message = $status !== null
                ? "{$policyType}: Graph returned {$status}"
                : "{$policyType}: Graph request failed";

            if (is_string($firstErrorMessage) && $firstErrorMessage !== '') {
                $message .= ' - '.trim($firstErrorMessage);
            }

            $failureSummary[] = [
                'code' => $status !== null ? "GRAPH_HTTP_{$status}" : 'GRAPH_ERROR',
                'message' => $message,
            ];
        }

        if ($this->operationRun) {
            $total = $syncedCount + $failureCount;

            $operationRunService->updateRun(
                $this->operationRun,
                status: OperationRunStatus::Completed->value,
                outcome: $outcome,
                summaryCounts: [
                    'total' => $total,
                    'processed' => $total,
                    'succeeded' => $syncedCount,
                    'failed' => $failureCount,
                ],
                failures: $failureSummary,
            );
        }
    }
}
