<?php

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

/**
 * Applicant billing (§27), installments (§28) and collection (§29, §29A).
 *
 * Every money column is an integer in minor units with its own currency
 * alongside (§112). Floating point is never used for money.
 */
return new class extends Migration
{
    public function up(): void
    {
        Schema::create('invoices', function (Blueprint $table) {
            $table->id();
            $table->uuid('uuid')->unique();
            $table->string('invoice_no', 40);

            $table->foreignId('company_id')->constrained()->cascadeOnUpdate()->restrictOnDelete();
            $table->foreignId('branch_id')->nullable()->constrained()->nullOnDelete();
            $table->foreignId('applicant_id')->constrained()->cascadeOnUpdate()->restrictOnDelete();
            $table->foreignId('job_demand_id')->nullable()->constrained()->nullOnDelete();

            $table->string('status', 20)->default('draft');
            $table->char('currency', 3);

            // Totals in minor units, derived from the lines and never edited directly.
            $table->unsignedBigInteger('subtotal_amount')->default(0);
            $table->unsignedBigInteger('discount_amount')->default(0);
            $table->unsignedBigInteger('tax_amount')->default(0);
            $table->unsignedBigInteger('total_amount')->default(0);
            $table->unsignedBigInteger('paid_amount')->default(0);
            $table->unsignedBigInteger('refunded_amount')->default(0);

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

            $table->foreignId('issued_by')->nullable()->constrained('users')->nullOnDelete();
            $table->timestamp('issued_at')->nullable();
            $table->foreignId('created_by')->nullable()->constrained('users')->nullOnDelete();
            $table->timestamps();
            $table->softDeletes();

            $table->unique(['company_id', 'invoice_no']);
            $table->index(['applicant_id', 'status']);
            $table->index(['company_id', 'status', 'due_on']);
        });

        Schema::create('invoice_lines', function (Blueprint $table) {
            $table->id();
            $table->uuid('uuid')->unique();
            $table->foreignId('invoice_id')->constrained()->cascadeOnDelete();

            // registration, processing, medical, visa, ticket … (§27).
            $table->string('component', 40);
            $table->string('description');
            $table->unsignedInteger('quantity')->default(1);
            $table->unsignedBigInteger('unit_amount');
            $table->unsignedBigInteger('discount_amount')->default(0);
            $table->unsignedBigInteger('tax_amount')->default(0);
            $table->unsignedBigInteger('line_total');

            // What the agency itself pays out, kept apart from what is charged
            // so margin is measurable (§27, §103).
            $table->unsignedBigInteger('external_cost_amount')->default(0);
            $table->boolean('is_waived')->default(false);
            $table->text('waiver_reason')->nullable();

            $table->unsignedSmallInteger('sort_order')->default(0);
            $table->timestamps();

            $table->index('invoice_id');
        });

        Schema::create('installments', function (Blueprint $table) {
            $table->id();
            $table->uuid('uuid')->unique();
            $table->foreignId('invoice_id')->constrained()->cascadeOnDelete();

            $table->unsignedSmallInteger('sequence');
            $table->string('label');
            // Registration, selection, visa submission, before ticketing … (§28).
            $table->string('due_trigger', 40)->nullable();
            $table->date('due_on')->nullable();

            $table->unsignedBigInteger('amount');
            $table->unsignedBigInteger('paid_amount')->default(0);
            $table->string('status', 20)->default('upcoming');

            // A stage the applicant cannot pass until this is settled (§28).
            $table->foreignId('blocking_stage_id')->nullable()->constrained('pipeline_stages')->nullOnDelete();
            $table->text('waiver_reason')->nullable();
            $table->timestamps();

            $table->unique(['invoice_id', 'sequence']);
            $table->index(['status', 'due_on']);
        });

        Schema::create('payments', function (Blueprint $table) {
            $table->id();
            $table->uuid('uuid')->unique();
            $table->string('payment_no', 40);
            $table->string('receipt_no', 40)->nullable();

            $table->foreignId('company_id')->constrained()->cascadeOnUpdate()->restrictOnDelete();
            $table->foreignId('branch_id')->nullable()->constrained()->nullOnDelete();
            $table->foreignId('applicant_id')->constrained()->cascadeOnUpdate()->restrictOnDelete();
            $table->foreignId('invoice_id')->nullable()->constrained()->nullOnDelete();
            $table->foreignId('installment_id')->nullable()->constrained()->nullOnDelete();

            $table->unsignedBigInteger('amount');
            $table->char('currency', 3);
            $table->string('method', 30);
            $table->string('status', 20)->default('pending');

            // Bank/wallet reference. Unique per company where present, so the
            // same deposit slip cannot be recorded twice (§70).
            $table->string('transaction_reference', 100)->nullable();
            $table->string('bank_or_wallet', 120)->nullable();
            $table->string('depositor_name')->nullable();
            $table->date('deposit_date')->nullable();

            $table->timestamp('received_at');
            $table->foreignId('received_by')->nullable()->constrained('users')->nullOnDelete();

            // A bank deposit is only money once someone has checked the slip.
            $table->foreignId('verified_by')->nullable()->constrained('users')->nullOnDelete();
            $table->timestamp('verified_at')->nullable();

            // Reversal never edits the original (§30, §70).
            $table->foreignId('reverses_payment_id')->nullable()->constrained('payments')->nullOnDelete();
            $table->text('reversal_reason')->nullable();
            $table->timestamp('reversed_at')->nullable();
            $table->foreignId('reversed_by')->nullable()->constrained('users')->nullOnDelete();

            $table->text('notes')->nullable();
            $table->timestamps();

            $table->unique(['company_id', 'payment_no']);
            $table->unique(['company_id', 'receipt_no']);
            $table->unique(['company_id', 'transaction_reference']);
            $table->index(['applicant_id', 'status']);
            $table->index(['company_id', 'status', 'received_at']);
        });
    }

    public function down(): void
    {
        Schema::dropIfExists('payments');
        Schema::dropIfExists('installments');
        Schema::dropIfExists('invoice_lines');
        Schema::dropIfExists('invoices');
    }
};
