<?php

namespace App\Jobs;

use App\Jobs\Middleware\TrackOperationRun;
use App\Models\EntraGroupSyncRun;
use App\Models\OperationRun;
use App\Models\Tenant;
use App\Services\Directory\EntraGroupSyncService;
use App\Services\Intune\AuditLogger;
use App\Services\OperationRunService;
use Carbon\CarbonImmutable;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use RuntimeException;

class EntraGroupSyncJob implements ShouldQueue
{
    use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;

    public ?OperationRun $operationRun = null;

    public function __construct(
        public int $tenantId,
        public string $selectionKey,
        public ?string $slotKey = null,
        public ?int $runId = null,
        ?OperationRun $operationRun = null
    ) {
        $this->operationRun = $operationRun;
    }

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

    public function handle(EntraGroupSyncService $syncService, AuditLogger $auditLogger): void
    {
        $tenant = Tenant::query()->find($this->tenantId);
        if (! $tenant instanceof Tenant) {
            throw new RuntimeException('Tenant not found.');
        }

        $run = $this->resolveRun($tenant);

        if ($run->status !== EntraGroupSyncRun::STATUS_PENDING) {
            // Already ran?
            return;
        }

        $run->update([
            'status' => EntraGroupSyncRun::STATUS_RUNNING,
            'started_at' => CarbonImmutable::now('UTC'),
        ]);

        $auditLogger->log(
            tenant: $tenant,
            action: 'directory_groups.sync.started',
            context: [
                'selection_key' => $run->selection_key,
                'run_id' => $run->getKey(),
                'slot_key' => $run->slot_key,
            ],
            actorId: $run->initiator_user_id,
            status: 'success',
            resourceType: 'entra_group_sync_run',
            resourceId: (string) $run->getKey(),
        );

        $result = $syncService->sync($tenant, $run);

        $terminalStatus = EntraGroupSyncRun::STATUS_SUCCEEDED;

        if ($result['error_code'] !== null) {
            $terminalStatus = EntraGroupSyncRun::STATUS_FAILED;
        } elseif ($result['safety_stop_triggered'] === true) {
            $terminalStatus = EntraGroupSyncRun::STATUS_PARTIAL;
        }

        $run->update([
            'status' => $terminalStatus,
            'pages_fetched' => $result['pages_fetched'],
            'items_observed_count' => $result['items_observed_count'],
            'items_upserted_count' => $result['items_upserted_count'],
            'error_count' => $result['error_count'],
            'safety_stop_triggered' => $result['safety_stop_triggered'],
            'safety_stop_reason' => $result['safety_stop_reason'],
            'error_code' => $result['error_code'],
            'error_category' => $result['error_category'],
            'error_summary' => $result['error_summary'],
            'finished_at' => CarbonImmutable::now('UTC'),
        ]);

        // Update OperationRun with stats
        if ($this->operationRun) {
            /** @var OperationRunService $opService */
            $opService = app(OperationRunService::class);

            $opOutcome = match ($terminalStatus) {
                EntraGroupSyncRun::STATUS_SUCCEEDED => 'succeeded',
                EntraGroupSyncRun::STATUS_PARTIAL => 'partially_succeeded',
                EntraGroupSyncRun::STATUS_FAILED => 'failed',
                default => 'failed'
            };

            $opService->updateRun(
                $this->operationRun,
                'completed',
                $opOutcome,
                [
                    'fetched' => $result['items_observed_count'],
                    'upserted' => $result['items_upserted_count'],
                    'errors' => $result['error_count'],
                ],
                $result['error_summary'] ? [['code' => $result['error_code'] ?? 'ERR', 'message' => json_encode($result['error_summary'])]] : []
            );
        }

        $auditLogger->log(
            tenant: $tenant,
            action: $terminalStatus === EntraGroupSyncRun::STATUS_SUCCEEDED
                ? 'directory_groups.sync.succeeded'
                : ($terminalStatus === EntraGroupSyncRun::STATUS_PARTIAL
                    ? 'directory_groups.sync.partial'
                    : 'directory_groups.sync.failed'),
            context: [
                'selection_key' => $run->selection_key,
                'run_id' => $run->getKey(),
                'slot_key' => $run->slot_key,
                'pages_fetched' => $run->pages_fetched,
                'items_observed_count' => $run->items_observed_count,
                'items_upserted_count' => $run->items_upserted_count,
                'error_code' => $run->error_code,
                'error_category' => $run->error_category,
            ],
            actorId: $run->initiator_user_id,
            status: $terminalStatus === EntraGroupSyncRun::STATUS_FAILED ? 'failed' : 'success',
            resourceType: 'entra_group_sync_run',
            resourceId: (string) $run->getKey(),
        );
    }

    private function resolveRun(Tenant $tenant): EntraGroupSyncRun
    {
        if ($this->runId !== null) {
            $run = EntraGroupSyncRun::query()
                ->whereKey($this->runId)
                ->where('tenant_id', $tenant->getKey())
                ->first();

            if ($run instanceof EntraGroupSyncRun) {
                return $run;
            }

            throw new RuntimeException('EntraGroupSyncRun not found.');
        }

        if ($this->slotKey !== null) {
            $run = EntraGroupSyncRun::query()
                ->where('tenant_id', $tenant->getKey())
                ->where('selection_key', $this->selectionKey)
                ->where('slot_key', $this->slotKey)
                ->first();

            if ($run instanceof EntraGroupSyncRun) {
                return $run;
            }

            throw new RuntimeException('EntraGroupSyncRun not found for slot.');
        }

        throw new RuntimeException('Job missing runId/slotKey.');
    }
}
