<?php

namespace App\Jobs;

use App\Jobs\Middleware\TrackOperationRun;
use App\Models\BulkOperationRun;
use App\Models\InventorySyncRun;
use App\Models\OperationRun;
use App\Models\Tenant;
use App\Services\BulkOperationService;
use App\Services\Drift\DriftFindingGenerator;
use App\Services\OperationRunService;
use App\Support\OperationRunLinks;
use Filament\Actions\Action;
use Filament\Notifications\Notification;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Facades\Log;
use RuntimeException;
use Throwable;

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

    public ?OperationRun $operationRun = null;

    public function __construct(
        public int $tenantId,
        public int $userId,
        public int $baselineRunId,
        public int $currentRunId,
        public string $scopeKey,
        public int $bulkOperationRunId,
        ?OperationRun $operationRun = null
    ) {
        $this->operationRun = $operationRun;
    }

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

    /**
     * Execute the job.
     */
    public function handle(DriftFindingGenerator $generator, BulkOperationService $bulkOperationService): void
    {
        Log::info('GenerateDriftFindingsJob: started', [
            'tenant_id' => $this->tenantId,
            'baseline_run_id' => $this->baselineRunId,
            'current_run_id' => $this->currentRunId,
            'scope_key' => $this->scopeKey,
            'bulk_operation_run_id' => $this->bulkOperationRunId,
        ]);

        $tenant = Tenant::query()->find($this->tenantId);
        if (! $tenant instanceof Tenant) {
            throw new RuntimeException('Tenant not found.');
        }

        $baseline = InventorySyncRun::query()->find($this->baselineRunId);
        if (! $baseline instanceof InventorySyncRun) {
            throw new RuntimeException('Baseline run not found.');
        }

        $current = InventorySyncRun::query()->find($this->currentRunId);
        if (! $current instanceof InventorySyncRun) {
            throw new RuntimeException('Current run not found.');
        }

        $run = BulkOperationRun::query()
            ->where('tenant_id', $tenant->getKey())
            ->find($this->bulkOperationRunId);

        if (! $run instanceof BulkOperationRun) {
            throw new RuntimeException('Bulk operation run not found.');
        }

        $bulkOperationService->start($run);

        try {
            $created = $generator->generate(
                tenant: $tenant,
                baseline: $baseline,
                current: $current,
                scopeKey: $this->scopeKey,
            );

            Log::info('GenerateDriftFindingsJob: completed', [
                'tenant_id' => $this->tenantId,
                'baseline_run_id' => $this->baselineRunId,
                'current_run_id' => $this->currentRunId,
                'scope_key' => $this->scopeKey,
                'bulk_operation_run_id' => $this->bulkOperationRunId,
                'created_findings_count' => $created,
            ]);

            $bulkOperationService->recordSuccess($run);
            $bulkOperationService->complete($run);

            if ($this->operationRun) {
                /** @var OperationRunService $opService */
                $opService = app(OperationRunService::class);
                $opService->updateRun(
                    $this->operationRun,
                    'completed',
                    'succeeded',
                    ['findings_created' => $created]
                );
            }

            $this->notifyStatus($run->refresh());
        } catch (Throwable $e) {
            Log::error('GenerateDriftFindingsJob: failed', [
                'tenant_id' => $this->tenantId,
                'baseline_run_id' => $this->baselineRunId,
                'current_run_id' => $this->currentRunId,
                'scope_key' => $this->scopeKey,
                'bulk_operation_run_id' => $this->bulkOperationRunId,
                'error' => $e->getMessage(),
            ]);

            $bulkOperationService->recordFailure(
                run: $run,
                itemId: $this->scopeKey,
                reason: $e->getMessage(),
                reasonCode: 'unknown',
            );

            $bulkOperationService->fail($run, $e->getMessage());

            // TrackOperationRun middleware might catch this, but explicit fail ensures structure
            if ($this->operationRun) {
                /** @var OperationRunService $opService */
                $opService = app(OperationRunService::class);
                $opService->failRun($this->operationRun, $e);
            }

            $this->notifyStatus($run->refresh());

            throw $e;
        }
    }

    private function notifyStatus(BulkOperationRun $run): void
    {
        try {
            if (! $run->relationLoaded('user')) {
                $run->loadMissing('user');
            }

            if (! $run->user) {
                return;
            }

            $tenant = Tenant::query()->find((int) $run->tenant_id);

            if (! $tenant instanceof Tenant) {
                return;
            }

            $status = $run->statusBucket();

            $title = match ($status) {
                'queued' => 'Drift generation queued',
                'running' => 'Drift generation started',
                'succeeded' => 'Drift generation completed',
                'partially succeeded' => 'Drift generation completed (partial)',
                default => 'Drift generation failed',
            };

            $body = sprintf(
                'Total: %d, processed: %d, succeeded: %d, failed: %d, skipped: %d.',
                (int) $run->total_items,
                (int) $run->processed_items,
                (int) $run->succeeded,
                (int) $run->failed,
                (int) $run->skipped,
            );

            $notification = Notification::make()
                ->title($title)
                ->body($body)
                ->actions([
                    Action::make('view_run')
                        ->label('View run')
                        ->url($this->operationRun ? OperationRunLinks::view($this->operationRun, $tenant) : OperationRunLinks::index($tenant)),
                ]);

            match ($status) {
                'succeeded' => $notification->success(),
                'partially succeeded' => $notification->warning(),
                'queued', 'running' => $notification->info(),
                default => $notification->danger(),
            };

            $notification
                ->sendToDatabase($run->user)
                ->send();
        } catch (Throwable $e) {
            Log::warning('GenerateDriftFindingsJob: status notification failed', [
                'tenant_id' => (int) $run->tenant_id,
                'bulk_operation_run_id' => (int) $run->getKey(),
                'error' => $e->getMessage(),
            ]);
        }
    }
}
