<?php

namespace App\Jobs;

use App\Jobs\Middleware\TrackOperationRun;
use App\Models\OperationRun;
use App\Models\Policy;
use App\Models\ManagedEnvironment;
use App\Models\User;
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;
use Throwable;

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

    public ?OperationRun $operationRun = null;

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

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

    public function handle(OperationRunService $operationRunService): void
    {
        $tenant = ManagedEnvironment::query()->find($this->tenantId);
        if (! $tenant instanceof ManagedEnvironment) {
            throw new \RuntimeException('ManagedEnvironment not found.');
        }

        $user = User::query()->find($this->userId);
        if (! $user instanceof User) {
            throw new \RuntimeException('User not found.');
        }

        try {
            $succeeded = 0;
            $failed = 0;
            $skipped = 0;
            $failures = [];

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

            foreach ($ids as $policyId) {

                try {
                    $policy = Policy::query()
                        ->where('managed_environment_id', $tenant->getKey())
                        ->find($policyId);

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

                        continue;
                    }

                    if (! $policy->ignored_at) {
                        $skipped++;

                        continue;
                    }

                    $policy->unignore();
                    $succeeded++;
                } catch (Throwable $e) {
                    $failed++;
                    $failures[] = [
                        'code' => 'policy.unignore.failed',
                        'message' => $e->getMessage(),
                    ];
                }
            }

            $total = count($ids);

            $outcome = OperationRunOutcome::Succeeded->value;

            if ($failed > 0 && $failed < $total) {
                $outcome = OperationRunOutcome::PartiallySucceeded->value;
            }

            if ($failed >= $total && $total > 0) {
                $outcome = OperationRunOutcome::Failed->value;
            }

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

        } catch (Throwable $e) {
            if ($this->operationRun) {
                $operationRunService->updateRun(
                    $this->operationRun,
                    status: OperationRunStatus::Completed->value,
                    outcome: OperationRunOutcome::Failed->value,
                    failures: [
                        ['code' => 'exception.unhandled', 'message' => $e->getMessage()],
                    ],
                );
            }

            throw $e;
        }
    }
}
