<?php

namespace App\Jobs;

use App\Models\ExportTarget;
use App\Models\Invoice;
use App\Models\InvoiceExport;
use App\Services\Billing\InvoiceArchive;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Queue\Queueable;
use Throwable;

/**
 * Put one invoice on ONE destination, and keep trying if that one is away.
 *
 * One job per pair rather than one per invoice: the reason for having a second
 * destination is that the first can fail, and a job that writes to both would
 * retry the one that worked every time the other did not — and would report a
 * single outcome for two different things.
 *
 * Queued rather than done during the request that issued the invoice: a network
 * mount can block for its whole timeout, and an invoice must not fail to be
 * issued because a NAS in an office is rebooting.
 */
class ArchiveInvoice implements ShouldQueue
{
    use Queueable;

    public int $tries = 5;

    /** A destination that is gone is usually gone for a while. */
    public array $backoff = [60, 300, 900, 3600];

    public function __construct(public Invoice $invoice, public ExportTarget $target) {}

    public function handle(InvoiceArchive $archive): void
    {
        if (! $this->target->active) {
            return;
        }

        $record = InvoiceExport::query()->firstOrNew([
            'invoice_id' => $this->invoice->id,
            'export_target_id' => $this->target->id,
        ]);

        // Already there. A retry after a timeout that in fact succeeded must
        // not write it a second time.
        if ($record->exists && $record->exported_at !== null) {
            return;
        }

        $record->attempts = (int) $record->attempts + 1;
        $record->save();

        $archive->store($this->invoice, $this->target);

        $record->forceFill(['exported_at' => now(), 'error' => null])->save();
    }

    public function failed(?Throwable $e): void
    {
        // Against the pair, not the invoice: "on the office NAS, not on the
        // off-site box" is the thing somebody needs to be told.
        InvoiceExport::query()->updateOrCreate(
            ['invoice_id' => $this->invoice->id, 'export_target_id' => $this->target->id],
            ['error' => mb_substr((string) $e?->getMessage(), 0, 250)],
        );
    }
}
