<?php

declare(strict_types=1);

namespace App\Jobs\Alerts;

use App\Models\AlertDelivery;
use App\Models\OperationRun;
use App\Models\Workspace;
use App\Services\Alerts\AlertSender;
use App\Services\OperationRunService;
use App\Support\OperationRunOutcome;
use App\Support\OperationRunStatus;
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 Throwable;

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

    public function __construct(
        public int $workspaceId,
        public ?int $operationRunId = null,
    ) {}

    public function handle(AlertSender $alertSender, OperationRunService $operationRuns): void
    {
        $workspace = Workspace::query()->whereKey($this->workspaceId)->first();

        if (! $workspace instanceof Workspace) {
            return;
        }

        $operationRun = $this->resolveOperationRun($workspace, $operationRuns);

        if (! $operationRun instanceof OperationRun) {
            return;
        }

        if ($operationRun->status === OperationRunStatus::Completed->value) {
            return;
        }

        $operationRuns->updateRun(
            $operationRun,
            status: OperationRunStatus::Running->value,
            outcome: OperationRunOutcome::Pending->value,
        );

        $now = CarbonImmutable::now('UTC');
        $batchSize = max(1, (int) config('tenantpilot.alerts.deliver_batch_size', 200));
        $maxAttempts = max(1, (int) config('tenantpilot.alerts.delivery_max_attempts', 3));

        $deliveries = AlertDelivery::query()
            ->where('workspace_id', (int) $workspace->getKey())
            ->whereIn('status', [
                AlertDelivery::STATUS_QUEUED,
                AlertDelivery::STATUS_DEFERRED,
            ])
            ->where(function ($query) use ($now): void {
                $query->whereNull('send_after')
                    ->orWhere('send_after', '<=', $now);
            })
            ->orderBy('id')
            ->limit($batchSize)
            ->get();

        $processed = 0;
        $succeeded = 0;
        $terminalFailures = 0;
        $requeued = 0;

        foreach ($deliveries as $delivery) {
            if ($delivery->isTerminal()) {
                continue;
            }

            $processed++;

            try {
                $alertSender->send($delivery);

                $delivery->forceFill([
                    'attempt_count' => (int) $delivery->attempt_count + 1,
                    'status' => AlertDelivery::STATUS_SENT,
                    'send_after' => null,
                    'sent_at' => $now,
                    'last_error_code' => null,
                    'last_error_message' => null,
                ])->save();

                $succeeded++;
            } catch (Throwable $exception) {
                $attemptCount = (int) $delivery->attempt_count + 1;
                $errorCode = $this->sanitizeErrorCode($exception);
                $errorMessage = $this->sanitizeErrorMessage($exception);

                if ($attemptCount >= $maxAttempts) {
                    $delivery->forceFill([
                        'attempt_count' => $attemptCount,
                        'status' => AlertDelivery::STATUS_FAILED,
                        'send_after' => null,
                        'last_error_code' => $errorCode,
                        'last_error_message' => $errorMessage,
                    ])->save();

                    $terminalFailures++;

                    continue;
                }

                $delivery->forceFill([
                    'attempt_count' => $attemptCount,
                    'status' => AlertDelivery::STATUS_QUEUED,
                    'send_after' => $now->addSeconds($this->backoffSeconds($attemptCount)),
                    'last_error_code' => $errorCode,
                    'last_error_message' => $errorMessage,
                ])->save();

                $requeued++;
            }
        }

        $outcome = OperationRunOutcome::Succeeded->value;

        if ($terminalFailures > 0 && $succeeded === 0 && $requeued === 0) {
            $outcome = OperationRunOutcome::Failed->value;
        } elseif ($terminalFailures > 0 || $requeued > 0) {
            $outcome = OperationRunOutcome::PartiallySucceeded->value;
        }

        $operationRuns->updateRun(
            $operationRun,
            status: OperationRunStatus::Completed->value,
            outcome: $outcome,
            summaryCounts: [
                'total' => count($deliveries),
                'processed' => $processed,
                'succeeded' => $succeeded,
                'failed' => $terminalFailures,
                'skipped' => $requeued,
            ],
        );
    }

    private function resolveOperationRun(Workspace $workspace, OperationRunService $operationRuns): ?OperationRun
    {
        if (is_int($this->operationRunId) && $this->operationRunId > 0) {
            $operationRun = OperationRun::query()
                ->whereKey($this->operationRunId)
                ->where('workspace_id', (int) $workspace->getKey())
                ->where('type', 'alerts.deliver')
                ->first();

            if ($operationRun instanceof OperationRun) {
                return $operationRun;
            }
        }

        $slotKey = CarbonImmutable::now('UTC')->format('YmdHi').'Z';

        return $operationRuns->ensureWorkspaceRunWithIdentity(
            workspace: $workspace,
            type: 'alerts.deliver',
            identityInputs: [
                'slot_key' => $slotKey,
            ],
            context: [
                'trigger' => 'job',
                'slot_key' => $slotKey,
            ],
            initiator: null,
        );
    }

    private function backoffSeconds(int $attemptCount): int
    {
        $baseSeconds = max(1, (int) config('tenantpilot.alerts.delivery_retry_base_seconds', 60));
        $maxSeconds = max($baseSeconds, (int) config('tenantpilot.alerts.delivery_retry_max_seconds', 900));

        $exponent = max(0, $attemptCount - 1);
        $delaySeconds = $baseSeconds * (2 ** $exponent);

        return (int) min($maxSeconds, $delaySeconds);
    }

    private function sanitizeErrorCode(Throwable $exception): string
    {
        $shortName = class_basename($exception);
        $shortName = trim((string) $shortName);

        if ($shortName === '') {
            return 'delivery_exception';
        }

        return strtolower(preg_replace('/[^a-z0-9]+/i', '_', $shortName) ?? 'delivery_exception');
    }

    private function sanitizeErrorMessage(Throwable $exception): string
    {
        $message = trim($exception->getMessage());

        if ($message === '') {
            return 'Alert delivery failed.';
        }

        $message = preg_replace('/https?:\/\/\S+/i', '[redacted-url]', $message) ?? $message;

        return mb_substr($message, 0, 500);
    }
}
