<?php

namespace App\Console\Commands;

use App\Actions\VerifyCustomerVatId;
use App\Models\Customer;
use Illuminate\Console\Command;

/**
 * Re-ask the register about the VAT numbers we are relying on.
 *
 * A number is not verified once and for ever. A company is wound up, a
 * registration is withdrawn, a member state removes it — and from that moment we
 * are zero-rating an invoice against a number that no longer exists, which is our
 * liability and not the customer's. So the ones reverse charge actually rests on
 * are asked again.
 *
 * It also picks up the numbers nobody has checked yet: every number entered
 * before this check existed is unverified, so those customers are on the domestic
 * rate today whether or not they are entitled to reverse charge.
 *
 * ## Scheduled monthly, and why that is not the owner's decision to inherit
 *
 * This used to say it was deliberately NOT scheduled, and to point at a note in
 * routes/console.php that did not exist — so an owner reading the schedule could
 * not learn the command was there at all, and reverse charge went on resting on a
 * one-off answer for ever.
 *
 * It is scheduled now, because the exposure is the SELLER's. A registration that
 * is withdrawn keeps earning rate 0 on every invoice we issue afterwards, and the
 * unpaid VAT on those is ours to make good, not the customer's — so the cadence is
 * not a preference but the width of the window in which that can happen unnoticed.
 * Monthly, on the first, because that is the rhythm the VAT return is filed on: a
 * number that lapsed in March is caught before the March return is prepared.
 *
 * The public service's concurrency limit is answered by `--limit` rather than by
 * not running: the query takes the longest-unchecked first, so a customer base
 * larger than one run rotates through it instead of starving. `--dry-run` and
 * `--customer` are for the by-hand run before a return.
 */
class VerifyVatIds extends Command
{
    protected $signature = 'clupilot:verify-vat-ids
        {--dry-run : Show what would be asked without touching the register or any record}
        {--customer= : One customer, by uuid}
        {--limit=200 : How many to look at in one run}';

    protected $description = 'Check customer VAT numbers against the EU VIES register';

    public function handle(VerifyCustomerVatId $verify): int
    {
        $customers = Customer::query()
            ->whereNotNull('vat_id')
            ->where('vat_id', '!=', '')
            ->when($this->option('customer'), fn ($q, $uuid) => $q->where('uuid', $uuid))
            // Longest unchecked first, so a short run covers the ones we know
            // least about rather than the ones checked yesterday. A plain
            // ascending sort does that on its own — never-checked rows are NULL
            // and both engines sort those first — which is why there is no raw
            // SQL here: MariaDB and SQLite would need different expressions for
            // it, and a query that only runs on one of them makes the test
            // database a different product from the live one.
            ->orderBy('vat_id_verified_at')
            ->limit((int) $this->option('limit'))
            ->get();

        if ($customers->isEmpty()) {
            $this->info('No VAT numbers on record.');

            return self::SUCCESS;
        }

        $counts = ['verified' => 0, 'rejected' => 0, 'unchecked' => 0];

        foreach ($customers as $customer) {
            if ($this->option('dry-run')) {
                $this->line(sprintf('would ask: %s (%s)', $customer->normalisedVatId(), $customer->name));

                continue;
            }

            $check = $verify($customer);

            match (true) {
                $check === null => null,
                $check->isValid() => $counts['verified']++,
                $check->isInvalid() => $counts['rejected']++,
                default => $counts['unchecked']++,
            };

            if ($check?->isInvalid()) {
                // Named individually, because this one needs a person: an invoice
                // already issued at 0 % against a number the register now rejects
                // has to be looked at, and a count in a summary hides that.
                $this->warn(sprintf(
                    'not registered: %s (%s) — reverse charge no longer applies',
                    $customer->normalisedVatId(),
                    $customer->name,
                ));
            }
        }

        if ($this->option('dry-run')) {
            $this->info($customers->count().' number(s) would be asked about.');

            return self::SUCCESS;
        }

        $this->info(sprintf(
            '%d confirmed, %d rejected, %d could not be checked (register unavailable — nothing changed for those).',
            $counts['verified'],
            $counts['rejected'],
            $counts['unchecked'],
        ));

        return self::SUCCESS;
    }
}
