<?php

namespace Database\Seeders;

use App\Models\Central\Tenant;
use App\Models\Lookups\AdjustmentReason;
use App\Models\Lookups\CashMovementReason;
use App\Models\Lookups\DamageReason;
use App\Models\Lookups\DiscountType;
use App\Models\Lookups\LedgerEntryType;
use App\Models\Lookups\LoyaltyTransactionType;
use App\Models\Lookups\MovementType;
use App\Models\Lookups\PaymentMethod;
use App\Models\Lookups\PaymentTerm;
use App\Models\Lookups\ReturnReason;
use App\Models\Lookups\Status;
use App\Models\Lookups\StockTransferReason;
use App\Models\Lookups\SupplierAddressType;
use App\Models\Lookups\UnitOfMeasure;
use App\Models\Lookups\VoucherType;
use App\Models\Returns\RefundMethod;
use App\Services\CurrentContext;
use Illuminate\Database\Seeder;

/**
 * Idempotent lookup data seed.
 *
 * Run `php artisan db:seed --class=LookupSeeder` twice — second run produces no
 * duplicates. Codes are immutable; if you need to rename a label, edit the
 * `name` column directly (this seeder will pick up new rows but won't overwrite
 * existing labels — see ::firstOrCreate guard).
 */
class LookupSeeder extends Seeder
{
    public function run(): void
    {
        $context = app(CurrentContext::class);

        if ($context->hasTenant()) {
            $this->seedAll();
        } else {
            Tenant::all()->each(function (Tenant $tenant) use ($context): void {
                $context->runForTenant($tenant, fn () => $this->seedAll());
            });
        }
    }

    private function seedAll(): void
    {
        $this->seedStatuses();
        $this->seedPaymentMethods();
        $this->seedReturnReasons();
        $this->seedDamageReasons();
        $this->seedCashMovementReasons();
        $this->seedMovementTypes();
        $this->seedAdjustmentReasons();
        $this->seedStockTransferReasons();
        $this->seedUnitOfMeasures();
        $this->seedPaymentTerms();
        $this->seedLedgerEntryTypes();
        $this->seedSupplierAddressTypes();
        $this->seedDiscountTypes();
        $this->seedVoucherTypes();
        $this->seedLoyaltyTransactionTypes();
        $this->seedRefundMethods();
    }

    /**
     * Reference-data completion for already-written, already-called code — the
     * same class of gap as the missing *_reversal movement types (Decisions Log
     * #25), found while building Phase 6's ReturnResource.
     *
     * `refund_methods` rows are only ever created by the migration that added
     * the table (2026_06_25_114722), which ran BEFORE `tenant_id` existed on it;
     * the later tenancy migration then backfilled those rows to the FIRST tenant
     * only. Nothing seeds them per-tenant, and `TenantProvisioner::provision()`
     * runs this seeder — so every tenant provisioned after the first one has an
     * empty `refund_methods` table. `RefundMethod::idOf()` then returns 0 and
     * `returns.refund_method_id`'s foreign key rejects the insert, meaning
     * creating ANY return fails outright for those tenants. Same gap exists in
     * leolanka-inertia (out of scope to change there; flagged in the plan).
     */
    private function seedRefundMethods(): void
    {
        $rows = [
            [RefundMethod::CASH, 'Cash', 1],
            [RefundMethod::CREDIT_NOTE, 'Credit Note', 2],
        ];

        foreach ($rows as [$code, $name, $sort]) {
            RefundMethod::query()->firstOrCreate(['code' => $code], ['name' => $name, 'sort_order' => $sort]);
        }
    }

    private function seedStatuses(): void
    {
        $rows = [
            // invoice_order
            [Status::CTX_INVOICE_ORDER, Status::CREATED,            'Created',            'gray',   10],
            [Status::CTX_INVOICE_ORDER, Status::INVOICED,           'Invoiced',           'blue',   20],
            [Status::CTX_INVOICE_ORDER, Status::PACKING,            'Packing',            'amber',  30],
            [Status::CTX_INVOICE_ORDER, Status::READY_FOR_DELIVERY, 'Ready for Delivery', 'indigo', 40],
            [Status::CTX_INVOICE_ORDER, Status::OUT_FOR_DELIVERY,   'Out for Delivery',   'purple', 50],
            [Status::CTX_INVOICE_ORDER, Status::UNDELIVERED,        'Undelivered',        'red',    55],
            [Status::CTX_INVOICE_ORDER, Status::DELIVERED,          'Delivered',          'green',  60],
            [Status::CTX_INVOICE_ORDER, Status::CANCELLED,          'Cancelled',          'slate',  90],

            // invoice_payment
            [Status::CTX_INVOICE_PAYMENT, Status::UNPAID,  'Unpaid',  'red',    10],
            [Status::CTX_INVOICE_PAYMENT, Status::PARTIAL, 'Partial', 'amber',  20],
            [Status::CTX_INVOICE_PAYMENT, Status::PAID,    'Paid',    'green',  30],

            // transaction
            [Status::CTX_TRANSACTION, Status::PENDING,  'Pending',  'amber',  10],
            [Status::CTX_TRANSACTION, Status::APPROVED, 'Approved', 'green',  20],
            [Status::CTX_TRANSACTION, Status::UTILIZED, 'Utilized', 'purple', 30],
            [Status::CTX_TRANSACTION, Status::REJECTED, 'Rejected', 'red',    40],

            // credit_note
            [Status::CTX_CREDIT_NOTE, Status::DRAFT,   'Draft',   'gray',  10],
            [Status::CTX_CREDIT_NOTE, Status::ISSUED,  'Issued',  'blue',  20],
            [Status::CTX_CREDIT_NOTE, Status::CLAIMED, 'Claimed', 'green', 30],
            [Status::CTX_CREDIT_NOTE, Status::VOID,    'Void',    'red',   40],

            // return
            [Status::CTX_RETURN, Status::RETURN_PENDING,   'Pending',   'amber',  10],
            [Status::CTX_RETURN, Status::RETURN_APPROVED,  'Approved',  'blue',   20],
            [Status::CTX_RETURN, Status::RETURN_CREDITED,  'Credited',  'green',  30],
            [Status::CTX_RETURN, Status::RETURN_REPLACED,  'Replaced',  'indigo', 35],
            [Status::CTX_RETURN, Status::RETURN_CANCELLED, 'Cancelled', 'red',    40],

            // pos_session
            [Status::CTX_POS_SESSION, Status::SESSION_OPEN,   'Open',   'green', 10],
            [Status::CTX_POS_SESSION, Status::SESSION_CLOSED, 'Closed', 'gray',  20],

            // sales_order — the order desk's board, from taking the order to
            // handing it over. Statuses between "confirmed" and "delivered" are
            // set by hand as work progresses; "invoiced" is only ever reached by
            // converting the order.
            [Status::CTX_SALES_ORDER, Status::SO_DRAFT,       'Draft',       'gray',   10],
            [Status::CTX_SALES_ORDER, Status::SO_CONFIRMED,   'Confirmed',   'blue',   20],
            [Status::CTX_SALES_ORDER, Status::SO_IN_PROGRESS, 'In Progress', 'amber',  30],
            [Status::CTX_SALES_ORDER, Status::SO_READY,       'Ready',       'indigo', 40],
            [Status::CTX_SALES_ORDER, Status::SO_DELIVERED,   'Delivered',   'purple', 50],
            [Status::CTX_SALES_ORDER, Status::SO_INVOICED,    'Invoiced',    'green',  60],
            [Status::CTX_SALES_ORDER, Status::SO_CANCELLED,   'Cancelled',   'red',    90],

            // stock_batch
            [Status::CTX_STOCK_BATCH, Status::BATCH_ACTIVE,   'Active',   'green',  10],
            [Status::CTX_STOCK_BATCH, Status::BATCH_EXPIRED,  'Expired',  'red',    20],
            [Status::CTX_STOCK_BATCH, Status::BATCH_INACTIVE, 'Inactive', 'gray',   25],
            [Status::CTX_STOCK_BATCH, Status::BATCH_RECALLED, 'Recalled', 'orange', 30],
            [Status::CTX_STOCK_BATCH, Status::BATCH_LOCKED,   'Locked',   'slate',  40],

            // product_serial (in_stock kept for backward compat; new code is 'available')
            [Status::CTX_PRODUCT_SERIAL, Status::SERIAL_IN_STOCK,    'In Stock',    'green',  10],
            [Status::CTX_PRODUCT_SERIAL, Status::SERIAL_AVAILABLE,   'Available',   'green',  15],
            [Status::CTX_PRODUCT_SERIAL, Status::SERIAL_RESERVED,    'Reserved',    'amber',  20],
            [Status::CTX_PRODUCT_SERIAL, Status::SERIAL_SOLD,        'Sold',        'blue',   30],
            [Status::CTX_PRODUCT_SERIAL, Status::SERIAL_RETURNED,    'Returned',    'indigo', 40],
            [Status::CTX_PRODUCT_SERIAL, Status::SERIAL_UNDER_REPAIR, 'Under Repair', 'purple', 50],
            [Status::CTX_PRODUCT_SERIAL, Status::SERIAL_DAMAGED,     'Damaged',     'red',    60],
            [Status::CTX_PRODUCT_SERIAL, Status::SERIAL_LOST,        'Lost',        'slate',  70],
            [Status::CTX_PRODUCT_SERIAL, Status::SERIAL_WRITTEN_OFF, 'Written Off', 'gray',   80],
            [Status::CTX_PRODUCT_SERIAL, Status::SERIAL_RETURNED_TO_SUPPLIER, 'Returned to Supplier', 'slate', 90],

            // supplier
            [Status::CTX_SUPPLIER, Status::SUPPLIER_ACTIVE,      'Active',      'green', 10],
            [Status::CTX_SUPPLIER, Status::SUPPLIER_INACTIVE,    'Inactive',    'gray',  20],
            [Status::CTX_SUPPLIER, Status::SUPPLIER_BLACKLISTED, 'Blacklisted', 'red',   30],

            // purchase_order
            [Status::CTX_PURCHASE_ORDER, Status::PO_DRAFT,     'Draft',     'gray',  10],
            [Status::CTX_PURCHASE_ORDER, Status::PO_SENT,      'Sent',      'blue',  20],
            [Status::CTX_PURCHASE_ORDER, Status::PO_PARTIAL,   'Partial',   'amber', 30],
            [Status::CTX_PURCHASE_ORDER, Status::PO_RECEIVED,  'Received',  'green', 40],
            [Status::CTX_PURCHASE_ORDER, Status::PO_CANCELLED, 'Cancelled', 'red',   50],

            // voucher
            [Status::CTX_VOUCHER, Status::VOUCHER_ACTIVE,    'Active',    'green', 10],
            [Status::CTX_VOUCHER, Status::VOUCHER_EXPIRED,   'Expired',   'gray',  20],
            [Status::CTX_VOUCHER, Status::VOUCHER_VOID,      'Void',      'red',   30],
            [Status::CTX_VOUCHER, Status::VOUCHER_EXHAUSTED, 'Exhausted', 'amber', 40],
        ];

        foreach ($rows as [$context, $code, $name, $color, $sortOrder]) {
            Status::query()->firstOrCreate(
                ['context' => $context, 'code' => $code],
                ['name' => $name, 'color' => $color, 'sort_order' => $sortOrder, 'is_active' => true],
            );
        }
    }

    private function seedPaymentMethods(): void
    {
        $rows = [
            [PaymentMethod::CASH,          'Cash',          false, false, true,  10],
            [PaymentMethod::CARD,          'Card',          false, false, true,  20],
            [PaymentMethod::BANK_TRANSFER, 'Bank Transfer', true,  false, true,  30],
            [PaymentMethod::CHEQUE,        'Cheque',        false, true,  false, 40],
            [PaymentMethod::CREDIT,        'Credit',        false, false, true,  50],
            [PaymentMethod::OTHER,         'Other',         false, false, true,  90],
        ];

        foreach ($rows as [$code, $name, $reqRef, $reqCheque, $autoApprove, $sort]) {
            PaymentMethod::query()->firstOrCreate(
                ['code' => $code],
                [
                    'name' => $name,
                    'requires_reference' => $reqRef,
                    'requires_cheque_fields' => $reqCheque,
                    'auto_approve' => $autoApprove,
                    'sort_order' => $sort,
                    'is_active' => true,
                ],
            );
        }
    }

    private function seedReturnReasons(): void
    {
        $rows = [
            [ReturnReason::WRONG_ITEM,         'Wrong Item Delivered',     false, false, 10],
            [ReturnReason::OVERSTOCK,          'Customer Overstock',       false, false, 20],
            [ReturnReason::QUALITY,            'Quality Issue (not damaged)', false, false, 30],
            [ReturnReason::EXPIRED,            'Expired Product',          false, false, 40],
            [ReturnReason::MISTAKENLY_ORDERED, 'Mistakenly Ordered',       false, false, 45],
            [ReturnReason::DAMAGE_TRANSIT,     'Damaged in Transit',       true,  false, 50],
            [ReturnReason::DAMAGE_WAREHOUSE,   'Damaged in Branch',     true,  false, 55],
            [ReturnReason::DAMAGE_CUSTOMER,    'Damaged by Customer',      true,  false, 60],
            [ReturnReason::OTHER,              'Other (specify in note)',  false, true,  90],
        ];

        foreach ($rows as [$code, $name, $triggersDamage, $requiresNote, $sort]) {
            ReturnReason::query()->firstOrCreate(
                ['code' => $code],
                [
                    'name' => $name,
                    'triggers_damage' => $triggersDamage,
                    'requires_note' => $requiresNote,
                    'sort_order' => $sort,
                    'is_active' => true,
                ],
            );
        }
    }

    private function seedCashMovementReasons(): void
    {
        $rows = [
            [CashMovementReason::PETTY_CASH, 'Petty Cash', false, 10],
            [CashMovementReason::BANK_DEPOSIT, 'Bank Deposit', false, 20],
            [CashMovementReason::SUPPLIER_PAYMENT, 'Supplier Payment', false, 30],
            [CashMovementReason::OWNER_WITHDRAWAL, 'Owner Withdrawal', false, 40],
            [CashMovementReason::EXPENSE, 'Expense', false, 50],
            [CashMovementReason::OTHER, 'Other (specify in note)', true, 90],
        ];

        foreach ($rows as [$code, $name, $requiresNote, $sort]) {
            CashMovementReason::query()->firstOrCreate(
                ['code' => $code],
                [
                    'name' => $name,
                    'requires_note' => $requiresNote,
                    'sort_order' => $sort,
                    'is_active' => true,
                ],
            );
        }
    }

    private function seedDamageReasons(): void
    {
        $rows = [
            [DamageReason::TRANSIT,   'Damaged in Transit',   10],
            [DamageReason::WAREHOUSE, 'Damaged in Branch', 20],
            [DamageReason::CUSTOMER,  'Damaged by Customer',  30],
            [DamageReason::EXPIRED,   'Expired',              40],
            [DamageReason::ACCIDENT,  'Accident',             50],
            [DamageReason::OTHER,     'Other',                90],
        ];

        foreach ($rows as [$code, $name, $sort]) {
            DamageReason::query()->firstOrCreate(
                ['code' => $code],
                ['name' => $name, 'sort_order' => $sort, 'is_active' => true],
            );
        }
    }

    private function seedMovementTypes(): void
    {
        // NOTE (docs/migration-plan.md Decisions Log): leolanka-inertia's
        // identical seeder never seeds ANY of the 6 *_reversal codes, even
        // though GrnService::delete() (grn_reversal), InvoiceService
        // (invoice_reversal), ReturnService (return_reversal,
        // damage_reversal), StockAdjustmentController::destroy()
        // (adjustment_reversal), and StockTransferController::destroy()
        // (transfer_reversal) all reference them via
        // StockMovementService::record() — a pre-existing gap that breaks
        // GRN-deletion-with-reversal, invoice cancellation, return/damage
        // reversal, adjustment-deletion-reversal, and transfer-deletion-
        // reversal in the source app today (MovementType::idOf() returns
        // null, record() throws InvalidArgumentException). Fixed here: pure
        // reference-data completion for already-written, already-called
        // code, not a behavior/logic change. All 6 declared reversal
        // constants turned out to be referenced somewhere (4 from services,
        // 2 directly from controllers) once checked exhaustively — the
        // original fix (Decisions Log #25) grepped only app/Services/*.php
        // and incorrectly concluded adjustment_reversal/transfer_reversal
        // were dead; they are not.
        $rows = [
            [MovementType::PURCHASE,     'Purchase (GRN)',          MovementType::DIRECTION_IN,  10],
            [MovementType::SALE,         'Sale',                    MovementType::DIRECTION_OUT, 20],
            [MovementType::RETURN_IN,    'Customer Return — Restock', MovementType::DIRECTION_IN,  30],
            [MovementType::RETURN_OUT,   'Return to Supplier',      MovementType::DIRECTION_OUT, 40],
            [MovementType::ADJUSTMENT,   'Adjustment',              MovementType::DIRECTION_IN,  50],
            [MovementType::TRANSFER_IN,  'Transfer In',             MovementType::DIRECTION_IN,  60],
            [MovementType::TRANSFER_OUT, 'Transfer Out',            MovementType::DIRECTION_OUT, 70],
            [MovementType::DAMAGE,       'Damage Write-off',        MovementType::DIRECTION_OUT, 80],
            [MovementType::GRN_REVERSAL,        'GRN Reversal',        MovementType::DIRECTION_OUT, 90],
            [MovementType::INVOICE_REVERSAL,    'Invoice Reversal',    MovementType::DIRECTION_IN,  100],
            [MovementType::RETURN_REVERSAL,     'Return Reversal',     MovementType::DIRECTION_OUT, 110],
            [MovementType::DAMAGE_REVERSAL,     'Damage Reversal',     MovementType::DIRECTION_IN,  120],
            [MovementType::ADJUSTMENT_REVERSAL, 'Adjustment Reversal', MovementType::DIRECTION_OUT, 130],
            [MovementType::TRANSFER_REVERSAL,   'Transfer Reversal',   MovementType::DIRECTION_IN,  140],
            [MovementType::RETURN_OUT_REVERSAL, 'Return to Supplier Reversal', MovementType::DIRECTION_IN, 150],
        ];

        foreach ($rows as [$code, $name, $direction, $sort]) {
            MovementType::query()->firstOrCreate(
                ['code' => $code],
                ['name' => $name, 'direction' => $direction, 'sort_order' => $sort, 'is_active' => true],
            );
        }
    }

    private function seedAdjustmentReasons(): void
    {
        $rows = [
            [AdjustmentReason::COUNT_ERROR,         'Physical Count Error',  10],
            [AdjustmentReason::THEFT,               'Theft',                 20],
            [AdjustmentReason::FOUND,               'Found Stock',           30],
            [AdjustmentReason::SUPPLIER_CORRECTION, 'Supplier Correction',   40],
            [AdjustmentReason::OTHER,               'Other',                 90],
        ];

        foreach ($rows as [$code, $name, $sort]) {
            AdjustmentReason::query()->firstOrCreate(
                ['code' => $code],
                ['name' => $name, 'sort_order' => $sort, 'is_active' => true],
            );
        }
    }

    private function seedStockTransferReasons(): void
    {
        $rows = [
            [StockTransferReason::INTERNAL,   'Internal Transfer',           10],
            [StockTransferReason::REPLENISH,  'Branch Replenishment',        20],
            [StockTransferReason::RELOCATION, 'Stock Relocation',            30],
            [StockTransferReason::OTHER,      'Other',                       90],
        ];

        foreach ($rows as [$code, $name, $sort]) {
            StockTransferReason::query()->firstOrCreate(
                ['code' => $code],
                ['name' => $name, 'sort_order' => $sort, 'is_active' => true],
            );
        }
    }

    private function seedUnitOfMeasures(): void
    {
        $rows = [
            [UnitOfMeasure::EACH, 'Each', false, 10],
            [UnitOfMeasure::BOX,  'Box',  false, 20],
            [UnitOfMeasure::PAIR, 'Pair', false, 30],
            [UnitOfMeasure::SET,  'Set',  false, 40],
            [UnitOfMeasure::KG,   'Kilogram', true, 50],
            [UnitOfMeasure::LTR,  'Litre',    true, 60],
            [UnitOfMeasure::ML,   'Millilitre', true, 65],
            [UnitOfMeasure::MTR,  'Metre',    true, 70],
        ];

        foreach ($rows as [$code, $name, $isFractional, $sort]) {
            UnitOfMeasure::query()->firstOrCreate(
                ['code' => $code],
                ['name' => $name, 'is_fractional' => $isFractional, 'sort_order' => $sort, 'is_active' => true],
            );
        }
    }

    private function seedPaymentTerms(): void
    {
        $rows = [
            [PaymentTerm::COD,   'Cash on Delivery', 0,  10],
            [PaymentTerm::NET7,  'Net 7 Days',       7,  20],
            [PaymentTerm::NET15, 'Net 15 Days',      15, 30],
            [PaymentTerm::NET30, 'Net 30 Days',      30, 40],
            [PaymentTerm::NET60, 'Net 60 Days',      60, 50],
            [PaymentTerm::NET90, 'Net 90 Days',      90, 60],
        ];

        foreach ($rows as [$code, $name, $netDays, $sort]) {
            PaymentTerm::query()->firstOrCreate(
                ['code' => $code],
                ['name' => $name, 'net_days' => $netDays, 'sort_order' => $sort, 'is_active' => true],
            );
        }
    }

    private function seedLedgerEntryTypes(): void
    {
        $rows = [
            [LedgerEntryType::DEBIT, 'Debit', 10],
            [LedgerEntryType::CREDIT, 'Credit', 20],
        ];

        foreach ($rows as [$code, $name, $sort]) {
            LedgerEntryType::query()->firstOrCreate(['code' => $code], ['name' => $name, 'sort_order' => $sort]);
        }
    }

    private function seedSupplierAddressTypes(): void
    {
        $rows = [
            [SupplierAddressType::BILLING, 'Billing', 10],
            [SupplierAddressType::SHIPPING, 'Shipping', 20],
            [SupplierAddressType::WAREHOUSE, 'Warehouse', 30],
        ];

        foreach ($rows as [$code, $name, $sort]) {
            SupplierAddressType::query()->firstOrCreate(['code' => $code], ['name' => $name, 'sort_order' => $sort]);
        }
    }

    private function seedDiscountTypes(): void
    {
        $rows = [
            [DiscountType::PERCENTAGE, 'Percentage', 10],
            [DiscountType::FIXED, 'Fixed Amount', 20],
        ];

        foreach ($rows as [$code, $name, $sort]) {
            DiscountType::query()->firstOrCreate(['code' => $code], ['name' => $name, 'sort_order' => $sort]);
        }
    }

    private function seedVoucherTypes(): void
    {
        $rows = [
            [VoucherType::GIFT, 'Gift Voucher', true, 10],
            [VoucherType::DISCOUNT, 'Discount Voucher', false, 20],
            [VoucherType::PROMOTIONAL, 'Promotional Voucher', false, 30],
            [VoucherType::REFUND, 'Refund Voucher', true, 40],
            [VoucherType::COMPENSATION, 'Compensation Voucher', true, 50],
        ];

        foreach ($rows as [$code, $name, $isBalanceBased, $sort]) {
            VoucherType::query()->firstOrCreate(['code' => $code], ['name' => $name, 'is_balance_based' => $isBalanceBased, 'sort_order' => $sort]);
        }
    }

    private function seedLoyaltyTransactionTypes(): void
    {
        $rows = [
            [LoyaltyTransactionType::EARN, 'Earned', 10],
            [LoyaltyTransactionType::REDEEM, 'Redeemed', 20],
            [LoyaltyTransactionType::EXPIRE, 'Expired', 30],
            [LoyaltyTransactionType::ADJUST, 'Manual Adjustment', 40],
            [LoyaltyTransactionType::REVERSAL, 'Reversal', 50],
        ];

        foreach ($rows as [$code, $name, $sort]) {
            LoyaltyTransactionType::query()->firstOrCreate(['code' => $code], ['name' => $name, 'sort_order' => $sort]);
        }
    }
}
