<?php

namespace App\Jobs;

use App\Models\NotificationChannel;
use App\Models\NotificationDelivery;
use App\Services\NotificationSender;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Queue\Queueable as QueueableTrait;
use Illuminate\Queue\SerializesModels;

/**
 * Processes a single NotificationDelivery row.
 *
 * The dispatcher creates rows in `queued` status; this job picks them up,
 * calls the right provider, and marks the row sent/failed/retrying.
 *
 * Why per-row jobs:
 *   - One slow Telegram timeout doesn't block the email recipient.
 *   - Retries with exponential backoff are isolated.
 *   - Horizon's "failed jobs" UI maps cleanly to "failed deliveries".
 */
class SendNotification implements ShouldQueue
{
    use QueueableTrait, Queueable, SerializesModels;

    public int $timeout = 30;
    public int $tries = 1; // retries are managed via NotificationDelivery, not the job

    public function __construct(public int $deliveryId) {}

    public function handle(NotificationSender $sender): void
    {
        $delivery = NotificationDelivery::find($this->deliveryId);
        if (!$delivery) return;

        if ($delivery->status === NotificationDelivery::STATUS_SENT) return;

        $channel = NotificationChannel::where('key', $delivery->channel_key)->first();
        if (!$channel || !$channel->enabled) {
            $delivery->markFailed("Channel '{$delivery->channel_key}' disabled or removed", false);
            return;
        }

        $bodies = $delivery->payload ?? [];
        if (empty($bodies)) {
            $delivery->markFailed('No payload to send', false);
            return;
        }

        $result = $sender->send($channel, $delivery, $bodies);
        if (($result['ok'] ?? false) === true) {
            $delivery->markSent($result['response'] ?? null);
        } else {
            // Fatal vs retryable — auth/credential errors don't get retried, transient ones do.
            $error = (string) ($result['error'] ?? 'unknown');
            $shouldRetry = !$this->looksFatal($error);
            $delivery->markFailed($error, $shouldRetry);
        }
    }

    /**
     * Heuristic: 401/403/422/credential-related errors are unlikely to fix
     * themselves on retry. Anything else (timeout, 5xx, network) gets retried.
     */
    protected function looksFatal(string $error): bool
    {
        $patterns = ['401', '403', '422', 'invalid', 'unauthorized', 'forbidden', 'not authenticated', 'authentication', 'token'];
        $lower = strtolower($error);
        foreach ($patterns as $p) {
            if (str_contains($lower, $p)) return true;
        }
        return false;
    }
}
