<?php

namespace App\Jobs;

use App\Jobs\Middleware\TrackOperationRun;
use App\Models\OperationRun;
use App\Models\Policy;
use App\Models\Tenant;
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 Dispatchable, InteractsWithQueue, Queueable, SerializesModels;

    public ?OperationRun $operationRun = null;

    /**
     * @param  array<int, string>|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 TrackOperationRun];
    }

    public function handle(PolicySyncService $service, OperationRunService $operationRunService): void
    {
        $tenant = Tenant::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('tenant_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) {
            $supported = array_values(array_filter($supported, fn ($type) => in_array($type['type'], $this->types, true)));
        }

        $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,
            );
        }
    }
}
