<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Schema;
use Illuminate\Support\Str;

/**
 * Invoices, frozen at the moment they are issued.
 *
 * The PDF is never stored. It is rendered on demand — but never from today's
 * settings. Everything a finished invoice says is copied into `snapshot` when
 * the number is assigned: the issuer's address, its VAT number and bank
 * details, the customer's address, every line, the rate and the totals. Change
 * the company address in 2028 and an invoice from 2026 still shows the address
 * it was issued under, because that is what a tax document is.
 *
 * Rendering from live settings would have been simpler and wrong. It is the
 * reason this table is wide: the width IS the feature.
 *
 * Numbers come from `invoice_series` — one row per Rechnungskreis (invoice,
 * credit note, cancellation, deposit), each with its own prefix and its own
 * gapless counter. Gapless is a legal requirement, not a preference, which is
 * why the counter lives in the database under a row lock rather than being
 * derived from whatever rows happen to exist.
 */
return new class extends Migration
{
    public function up(): void
    {
        Schema::create('invoice_series', function (Blueprint $table) {
            $table->id();
            $table->uuid()->unique();

            // 'invoice' | 'credit_note' | 'cancellation' | 'deposit'. A string
            // rather than an enum column: adding a kind must not be a migration
            // on a table that holds legal records.
            $table->string('kind', 32);
            $table->string('name');

            // "RE", "GS", "ST" — what the number starts with.
            $table->string('prefix', 12);

            // The year is part of the number and resets the counter, or it is
            // not and does not. Both are lawful; mixing them is not, so it is
            // recorded per series rather than assumed.
            $table->boolean('yearly_reset')->default(true);
            $table->unsignedSmallInteger('digits')->default(4);

            // The counter itself. Never derived from the invoices table: a
            // deleted or failed row would silently reuse a number that has
            // already been on a document somebody has.
            $table->unsignedInteger('next_number')->default(1);
            $table->unsignedSmallInteger('counter_year')->nullable();

            $table->boolean('active')->default(true);
            $table->timestamps();

            $table->unique(['kind', 'prefix']);
        });

        Schema::create('invoices', function (Blueprint $table) {
            $table->id();
            $table->uuid()->unique();

            $table->foreignId('invoice_series_id')->constrained()->restrictOnDelete();
            $table->foreignId('customer_id')->nullable()->constrained()->nullOnDelete();
            $table->foreignId('order_id')->nullable()->constrained()->nullOnDelete();

            // The number as it appears on the document, and its parts, so a
            // list can be sorted and filtered without parsing the string back.
            $table->string('number')->unique();
            $table->unsignedSmallInteger('number_year')->nullable();
            $table->unsignedInteger('number_sequence');

            $table->date('issued_on');
            $table->date('due_on')->nullable();

            // Everything the document says. See the class comment: this is what
            // makes the PDF reproducible without storing the PDF.
            $table->json('snapshot');

            // Denormalised out of the snapshot, because a list of invoices has
            // to be sortable by money without opening every JSON column.
            $table->integer('net_cents');
            $table->integer('tax_cents');
            $table->integer('gross_cents');
            $table->string('currency', 3);

            // A cancellation points at what it cancels. Nothing is ever
            // deleted or edited — a wrong invoice is corrected by issuing
            // another document, which is the only lawful way to correct one.
            $table->foreignId('cancels_invoice_id')->nullable()->constrained('invoices')->nullOnDelete();

            $table->timestamp('sent_at')->nullable();
            $table->timestamps();

            $table->index(['customer_id', 'issued_on']);
        });

        // The four an Austrian business needs on day one. Seeded rather than
        // left to the operator: an installation with no series cannot issue an
        // invoice at all, and discovering that at the moment of the first sale
        // is the worst possible time.
        $now = now();

        foreach ([
            ['invoice', 'Rechnung', 'RE'],
            ['credit_note', 'Gutschrift', 'GS'],
            ['cancellation', 'Storno', 'ST'],
            ['deposit', 'Anzahlung', 'AZ'],
        ] as [$kind, $name, $prefix]) {
            DB::table('invoice_series')->insert([
                'uuid' => (string) Str::uuid(),
                'kind' => $kind,
                'name' => $name,
                'prefix' => $prefix,
                'yearly_reset' => true,
                'digits' => 4,
                'next_number' => 1,
                'counter_year' => null,
                'active' => true,
                'created_at' => $now,
                'updated_at' => $now,
            ]);
        }
    }

    public function down(): void
    {
        Schema::dropIfExists('invoices');
        Schema::dropIfExists('invoice_series');
    }
};
