<?php

namespace App\Jobs;

use App\Jobs\Concerns\BridgesFailedOperationRun;
use App\Jobs\Middleware\EnsureQueuedExecutionLegitimate;
use App\Jobs\Middleware\TrackOperationRun;
use App\Models\OperationRun;
use App\Models\ManagedEnvironment;
use App\Models\User;
use App\Services\Intune\AuditLogger;
use App\Services\Inventory\InventorySyncService;
use App\Services\OperationRunService;
use App\Support\Inventory\InventoryCoverage;
use App\Support\Inventory\InventoryPolicyTypeMeta;
use App\Support\OperationRunOutcome;
use App\Support\OperationRunStatus;
use App\Support\Providers\ProviderReasonCodes;
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 RunInventorySyncJob implements ShouldQueue
{
    use BridgesFailedOperationRun;
    use Dispatchable;
    use InteractsWithQueue;
    use Queueable;
    use SerializesModels;

    public int $timeout = 240;

    public bool $failOnTimeout = true;

    public ?OperationRun $operationRun = null;

    /**
     * Create a new job instance.
     */
    public function __construct(
        public int $tenantId,
        public int $userId,
        ?OperationRun $operationRun = null
    ) {
        $this->operationRun = $operationRun;
    }

    /**
     * Get the middleware the job should pass through.
     *
     * @return array<int, object>
     */
    public function middleware(): array
    {
        return [new EnsureQueuedExecutionLegitimate, new TrackOperationRun];
    }

    /**
     * Execute the job.
     */
    public function handle(InventorySyncService $inventorySyncService, AuditLogger $auditLogger, OperationRunService $operationRunService): void
    {
        if (! $this->operationRun) {
            $this->fail(new RuntimeException('OperationRun context is required for RunInventorySyncJob.'));

            return;
        }

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

        $context = is_array($this->operationRun->context) ? $this->operationRun->context : [];
        $policyTypes = $context['policy_types'] ?? [];
        $policyTypes = is_array($policyTypes) ? array_values(array_filter(array_map('strval', $policyTypes))) : [];
        $includeFoundations = (bool) ($context['include_foundations'] ?? false);

        $foundationTypes = collect(InventoryPolicyTypeMeta::foundations())
            ->map(fn (array $row): mixed => $row['type'] ?? null)
            ->filter(fn (mixed $type): bool => is_string($type) && $type !== '')
            ->values()
            ->all();

        $attemptedTypes = $includeFoundations
            ? array_values(array_unique(array_merge($policyTypes, $foundationTypes)))
            : array_values(array_diff($policyTypes, $foundationTypes));

        $this->operationRun = $operationRunService->updateRun(
            $this->operationRun,
            status: OperationRunStatus::Running->value,
            outcome: OperationRunOutcome::Pending->value,
            summaryCounts: [
                'total' => count($attemptedTypes),
                'processed' => 0,
                'succeeded' => 0,
                'failed' => 0,
                'skipped' => 0,
            ],
        );

        $processedPolicyTypes = [];
        $coverageStatusByType = [];
        $successCount = 0;
        $failedCount = 0;

        $result = $inventorySyncService->executeSelection(
            $this->operationRun,
            $tenant,
            $context,
            function (string $policyType, bool $success, ?string $errorCode, int $itemCount) use ($operationRunService, &$processedPolicyTypes, &$coverageStatusByType, &$successCount, &$failedCount): void {
                $processedPolicyTypes[] = $policyType;
                $coverageStatusByType[$policyType] = array_filter([
                    'status' => $success
                        ? InventoryCoverage::StatusSucceeded
                        : InventoryCoverage::StatusFailed,
                    'item_count' => $itemCount,
                    'error_code' => $success ? null : $errorCode,
                ], static fn (mixed $value): bool => $value !== null);

                if ($success) {
                    $successCount++;

                    $this->operationRun = $operationRunService->incrementSummaryCounts($this->operationRun, [
                        'processed' => 1,
                        'succeeded' => 1,
                    ]);

                    return;
                }

                $failedCount++;

                $this->operationRun = $operationRunService->incrementSummaryCounts($this->operationRun, [
                    'processed' => 1,
                    'failed' => 1,
                ]);
            },
        );

        $statusByType = [];

        foreach ($attemptedTypes as $type) {
            if (! is_string($type) || $type === '') {
                continue;
            }

            $statusByType[$type] = [
                'status' => InventoryCoverage::StatusSkipped,
                'item_count' => 0,
            ];
        }

        foreach ($coverageStatusByType as $type => $status) {
            if (! is_string($type) || $type === '') {
                continue;
            }

            $statusByType[$type] = $status;
        }

        if ((string) ($result['status'] ?? '') === 'skipped') {
            $skippedErrorCode = is_string($result['error_codes'][0] ?? null)
                ? (string) $result['error_codes'][0]
                : null;

            foreach ($statusByType as $type => $status) {
                $statusByType[$type] = array_filter([
                    'status' => InventoryCoverage::StatusSkipped,
                    'item_count' => 0,
                    'error_code' => $skippedErrorCode,
                ], static fn (mixed $value): bool => $value !== null);
            }
        }

        $updatedContext = is_array($this->operationRun->context) ? $this->operationRun->context : [];
        $updatedContext['inventory'] = array_merge(
            is_array($updatedContext['inventory'] ?? null) ? $updatedContext['inventory'] : [],
            [
                'coverage' => InventoryCoverage::buildPayload($statusByType, $foundationTypes),
            ],
        );
        $updatedContext['result'] = [
            'had_errors' => (bool) ($result['had_errors'] ?? true),
            'error_codes' => is_array($result['error_codes'] ?? null) ? array_values($result['error_codes']) : [],
            'error_context' => is_array($result['error_context'] ?? null) ? $result['error_context'] : null,
        ];

        $this->operationRun->update([
            'context' => $updatedContext,
        ]);

        $this->operationRun->refresh();

        $status = (string) ($result['status'] ?? 'failed');
        $errorCodes = is_array($result['error_codes'] ?? null) ? $result['error_codes'] : [];
        $reason = (string) ($errorCodes[0] ?? $status);

        $errorContext = is_array($result['error_context'] ?? null) ? $result['error_context'] : [];
        $sanitizedErrorMessage = is_string($errorContext['message'] ?? null) ? (string) $errorContext['message'] : null;

        $reasonCode = null;
        $sanitizedMessageWithoutReasonCode = null;

        if (is_string($sanitizedErrorMessage)) {
            $sanitizedMessageWithoutReasonCode = preg_replace('/^\[[^\]]+\]\s*/', '', $sanitizedErrorMessage);
            $sanitizedMessageWithoutReasonCode = is_string($sanitizedMessageWithoutReasonCode)
                ? trim($sanitizedMessageWithoutReasonCode)
                : null;

            if (preg_match('/^\[(?<code>[^\]]+)\]/', $sanitizedErrorMessage, $m)) {
                $candidate = (string) ($m['code'] ?? '');
                if ($candidate !== '' && ProviderReasonCodes::isKnown($candidate)) {
                    $reasonCode = $candidate;
                }
            }
        }

        if ($reason === 'unexpected_exception' && is_string($sanitizedErrorMessage) && $sanitizedErrorMessage !== '') {
            $reason = is_string($sanitizedMessageWithoutReasonCode) && $sanitizedMessageWithoutReasonCode !== ''
                ? $sanitizedMessageWithoutReasonCode
                : $sanitizedErrorMessage;
        }

        $itemsObserved = (int) ($result['items_observed_count'] ?? 0);
        $itemsUpserted = (int) ($result['items_upserted_count'] ?? 0);
        $errorsCount = (int) ($result['errors_count'] ?? 0);
        $attemptedTypeCount = count($attemptedTypes);

        if ($status === 'success') {
            $operationRunService->updateRun(
                $this->operationRun,
                status: OperationRunStatus::Completed->value,
                outcome: OperationRunOutcome::Succeeded->value,
                summaryCounts: [
                    'total' => $attemptedTypeCount,
                    'processed' => $attemptedTypeCount,
                    'succeeded' => $attemptedTypeCount,
                    'failed' => 0,
                    'items' => $itemsObserved,
                    'updated' => $itemsUpserted,
                ],
            );

            $auditLogger->log(
                tenant: $tenant,
                action: 'inventory.sync.completed',
                context: [
                    'metadata' => [
                        'operation_run_id' => (int) $this->operationRun->getKey(),
                        'observed' => $itemsObserved,
                        'upserted' => $itemsUpserted,
                    ],
                ],
                actorId: $user->id,
                actorEmail: $user->email,
                actorName: $user->name,
                resourceType: 'operation_run',
                resourceId: (string) $this->operationRun->getKey(),
            );

            return;
        }

        if ($status === 'partial') {
            $failedUnits = max($failedCount, $errorsCount);

            $operationRunService->updateRun(
                $this->operationRun,
                status: OperationRunStatus::Completed->value,
                outcome: OperationRunOutcome::PartiallySucceeded->value,
                summaryCounts: [
                    'total' => $attemptedTypeCount,
                    'processed' => $attemptedTypeCount,
                    'succeeded' => max(0, $attemptedTypeCount - $failedUnits),
                    'failed' => $failedUnits,
                    'items' => $itemsObserved,
                    'updated' => $itemsUpserted,
                ],
                failures: [
                    ['code' => 'inventory.partial', 'message' => "Errors: {$errorsCount}"],
                ],
            );

            $auditLogger->log(
                tenant: $tenant,
                action: 'inventory.sync.partial',
                context: [
                    'metadata' => [
                        'operation_run_id' => (int) $this->operationRun->getKey(),
                        'observed' => $itemsObserved,
                        'upserted' => $itemsUpserted,
                        'errors' => $errorsCount,
                    ],
                ],
                actorId: $user->id,
                actorEmail: $user->email,
                actorName: $user->name,
                status: 'failure',
                resourceType: 'operation_run',
                resourceId: (string) $this->operationRun->getKey(),
            );

            return;
        }

        if ($status === 'skipped') {
            $operationRunService->updateRun(
                $this->operationRun,
                status: OperationRunStatus::Completed->value,
                outcome: OperationRunOutcome::Failed->value,
                summaryCounts: [
                    'total' => $attemptedTypeCount,
                    'processed' => $attemptedTypeCount,
                    'succeeded' => 0,
                    'failed' => 0,
                    'skipped' => $attemptedTypeCount,
                ],
                failures: [
                    ['code' => 'inventory.skipped', 'message' => $reason],
                ],
            );

            $auditLogger->log(
                tenant: $tenant,
                action: 'inventory.sync.skipped',
                context: [
                    'metadata' => [
                        'operation_run_id' => (int) $this->operationRun->getKey(),
                        'reason' => $reason,
                    ],
                ],
                actorId: $user->id,
                actorEmail: $user->email,
                actorName: $user->name,
                resourceType: 'operation_run',
                resourceId: (string) $this->operationRun->getKey(),
            );

            return;
        }

        $missingPolicyTypes = array_values(array_diff($attemptedTypes, array_unique($processedPolicyTypes)));
        $failedUnits = $failedCount + count($missingPolicyTypes);

        $operationRunService->updateRun(
            $this->operationRun,
            status: OperationRunStatus::Completed->value,
            outcome: OperationRunOutcome::Failed->value,
            summaryCounts: [
                'total' => $attemptedTypeCount,
                'processed' => $successCount + $failedUnits,
                'succeeded' => $successCount,
                'failed' => $failedUnits,
            ],
            failures: [
                ['code' => 'inventory.failed', 'reason_code' => $reasonCode, 'message' => $reason],
            ],
        );

        $auditLogger->log(
            tenant: $tenant,
            action: 'inventory.sync.failed',
            context: [
                'metadata' => [
                    'operation_run_id' => (int) $this->operationRun->getKey(),
                    'reason' => $reason,
                ],
            ],
            actorId: $user->id,
            actorEmail: $user->email,
            actorName: $user->name,
            status: 'failure',
            resourceType: 'operation_run',
            resourceId: (string) $this->operationRun->getKey(),
        );
    }
}
