<?php

namespace App\Jobs;

use App\Jobs\Middleware\TrackOperationRun;
use App\Models\BackupItem;
use App\Models\BackupSet;
use App\Models\OperationRun;
use App\Models\ManagedEnvironment;
use App\Models\User;
use App\Services\Intune\AuditLogger;
use App\Services\OperationRunService;
use App\Support\OpsUx\RunFailureSanitizer;
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 RemovePoliciesFromBackupSetJob implements ShouldQueue
{
    use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;

    public int $timeout = 240;

    public bool $failOnTimeout = true;

    public ?OperationRun $operationRun = null;

    /**
     * @param  array<int,int>  $backupItemIds
     */
    public function __construct(
        public int $backupSetId,
        public array $backupItemIds,
        public int $initiatorUserId,
        ?OperationRun $operationRun = null,
    ) {
        $this->operationRun = $operationRun;
    }

    /**
     * @return array<int, object>
     */
    public function middleware(): array
    {
        return [new TrackOperationRun];
    }

    public function handle(
        AuditLogger $auditLogger,
    ): void {
        $backupSet = BackupSet::query()->with(['tenant'])->find($this->backupSetId);

        if (! $backupSet instanceof BackupSet) {
            if ($this->operationRun) {
                /** @var OperationRunService $opService */
                $opService = app(OperationRunService::class);
                $opService->updateRun(
                    $this->operationRun,
                    'completed',
                    'failed',
                    ['failed' => 1],
                    [['code' => 'backup_set.not_found', 'message' => 'Backup set not found.']]
                );
            }

            return;
        }

        $tenant = $backupSet->tenant;

        $initiator = User::query()->find($this->initiatorUserId);

        $requestedIds = collect($this->backupItemIds)
            ->map(fn (mixed $value): int => (int) $value)
            ->filter(fn (int $value): bool => $value > 0)
            ->unique()
            ->sort()
            ->values()
            ->all();

        $requestedCount = count($requestedIds);

        $failures = [];

        try {
            /** @var \Illuminate\Database\Eloquent\Collection<int, BackupItem> $items */
            $items = BackupItem::query()
                ->where('backup_set_id', $backupSet->getKey())
                ->whereIn('id', $requestedIds)
                ->get();

            $foundIds = $items->pluck('id')->map(fn (mixed $value): int => (int) $value)->all();
            $missingIds = array_values(array_diff($requestedIds, $foundIds));

            foreach ($missingIds as $missingId) {
                $failures[] = [
                    'code' => 'backup_item.not_found',
                    'message' => RunFailureSanitizer::sanitizeMessage("Backup item {$missingId} not found (already removed?)."),
                ];
            }

            $removed = 0;
            $policyIds = [];
            $policyIdentifiers = [];

            foreach ($items as $item) {
                $item->delete();
                $removed++;

                if ($item->policy_id) {
                    $policyIds[] = (int) $item->policy_id;
                }

                if ($item->policy_identifier) {
                    $policyIdentifiers[] = (string) $item->policy_identifier;
                }
            }

            $backupSet->update([
                'item_count' => $backupSet->items()->count(),
            ]);

            if ($tenant instanceof ManagedEnvironment) {
                $auditLogger->log(
                    tenant: $tenant,
                    action: 'backup.items_removed',
                    resourceType: 'backup_set',
                    resourceId: (string) $backupSet->getKey(),
                    status: 'success',
                    context: [
                        'metadata' => [
                            'removed_count' => $removed,
                            'requested_count' => $requestedCount,
                            'missing_count' => count($missingIds),
                            'policy_ids' => array_values(array_unique($policyIds)),
                            'policy_identifiers' => array_values(array_unique($policyIdentifiers)),
                            'backup_set_id' => (int) $backupSet->getKey(),
                            'initiator_user_id' => $initiator?->getKey(),
                        ],
                    ],
                    actorId: $initiator?->getKey(),
                );
            }

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

                $this->operationRun->update([
                    'context' => array_merge($this->operationRun->context ?? [], [
                        'backup_set_id' => (int) $backupSet->getKey(),
                        'requested_count' => $requestedCount,
                        'removed_count' => $removed,
                        'missing_count' => count($missingIds),
                        'remaining_count' => (int) $backupSet->item_count,
                    ]),
                ]);

                $outcome = 'succeeded';
                if ($removed === 0) {
                    $outcome = 'failed';
                } elseif ($failures !== []) {
                    $outcome = 'partially_succeeded';
                }

                $opService->updateRun(
                    $this->operationRun,
                    'completed',
                    $outcome,
                    [
                        'total' => $requestedCount,
                        'processed' => $requestedCount,
                        'succeeded' => $removed,
                        'failed' => count($missingIds),
                        'deleted' => $removed,
                        'items' => $requestedCount,
                    ],
                    $failures,
                );
            }

        } catch (Throwable $throwable) {
            if ($tenant instanceof ManagedEnvironment) {
                $auditLogger->log(
                    tenant: $tenant,
                    action: 'backup.items_removed',
                    resourceType: 'backup_set',
                    resourceId: (string) $backupSet->getKey(),
                    status: 'failed',
                    context: [
                        'metadata' => [
                            'requested_count' => $requestedCount,
                            'backup_set_id' => (int) $backupSet->getKey(),
                        ],
                    ],
                    actorId: $initiator?->getKey(),
                );
            }

            if ($this->operationRun) {
                /** @var OperationRunService $opService */
                $opService = app(OperationRunService::class);
                $opService->failRun($this->operationRun, $throwable);
            }

            throw $throwable;
        }
    }
}
