<?php

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

/**
 * The tenant backfill. Adds `tenant_id` to every owned table, creates the first
 * tenant (Vellix Global) from the existing single-tenant data, backfills every
 * row to it, enforces NOT NULL (MySQL), swaps global unique keys to per-tenant
 * composites, grants the tenant the full entitlement set, and seeds its branding
 * from the previously hard-coded company constants.
 *
 * Excluded (stay global): permissions, menu_items (the module vocabulary/catalog);
 * provinces/districts/cities (shared geography); the RBAC pivots role_permissions,
 * user_roles, user_permission_overrides, user_warehouse_access (isolation inherited
 * from their tenant-scoped parents); framework + central tables.
 *
 * No DB-level FK on tenant_id: integrity comes from the BelongsToTenant trait and
 * the fail-closed TenantScope, and it keeps the migration portable to sqlite.
 */
return new class extends Migration
{
    /**
     * @var array<int, string>
     */
    private array $ownedTables = [
        'adjustment_reasons', 'audit_logs', 'branch_stock', 'bundle_items', 'cash_movements',
        'credit_notes', 'customers', 'customer_contacts', 'customer_credit_balances',
        'customer_credit_ledger', 'customer_loyalty_balances', 'damages', 'damage_reasons',
        'delivery_addresses', 'discount_types', 'document_sequences', 'grns', 'grn_items',
        'grn_statuses', 'invoices', 'invoice_audit_logs', 'invoice_items',
        'ledger_entry_types', 'loyalty_point_transactions', 'loyalty_programs', 'loyalty_tiers',
        'loyalty_transaction_types', 'main_categories', 'movement_types', 'passkeys',
        'payment_methods', 'payment_terms', 'pos_sessions', 'products', 'product_bundles',
        'product_serials', 'product_units', 'product_variants', 'purchase_orders',
        'purchase_order_items', 'refund_methods', 'returns', 'return_items',
        'return_reasons', 'roles', 'sales_reps', 'serial_ownership_history', 'serial_repair_history',
        'statuses', 'stock_adjustments', 'stock_batches',
        'stock_movements', 'stock_transfers', 'stock_transfer_reasons', 'sub_categories',
        'supplier_address_types', 'supplier_addresses', 'supplier_bank_accounts', 'supplier_contacts',
        'supplier_documents', 'supplier_ledgers', 'suppliers', 'tills', 'transactions',
        'transaction_claims', 'unit_of_measures', 'users', 'voucher_redemptions', 'voucher_types',
        'vouchers', 'warehouses',
    ];

    /**
     * Global unique key => composite replacement (tenant_id prefixed).
     *
     * @var array<string, array{0:string, 1:array<int,string>}>
     */
    private array $uniqueSwaps = [
        'users' => ['users_email_unique', ['email']],
        'roles' => ['roles_name_unique', ['name']],
        'products_sku' => ['products_sku_unique', ['sku']],
        'products_barcode' => ['products_barcode_unique', ['barcode']],
        'main_categories' => ['main_categories_name_unique', ['name']],
        'credit_notes' => ['credit_notes_cn_number_unique', ['cn_number']],
        'damages' => ['damages_damage_number_unique', ['damage_number']],
        'invoices' => ['invoices_invoice_number_unique', ['invoice_number']],
        'returns' => ['returns_return_number_unique', ['return_number']],
        'supplier_documents' => ['supplier_documents_document_number_unique', ['document_number']],
        'tills' => ['tills_identifier_unique', ['identifier']],
        'transactions' => ['transactions_transaction_number_unique', ['transaction_number']],
        'transaction_claims' => ['transaction_claims_claim_number_unique', ['claim_number']],
        'sales_reps' => ['sales_reps_employee_id_unique', ['employee_id']],
        'document_sequences' => ['document_sequences_sequence_key_unique', ['sequence_key']],
        'adjustment_reasons' => ['adjustment_reasons_code_unique', ['code']],
        'damage_reasons' => ['damage_reasons_code_unique', ['code']],
        'movement_types' => ['movement_types_code_unique', ['code']],
        'payment_methods' => ['payment_methods_code_unique', ['code']],
        'return_reasons' => ['return_reasons_code_unique', ['code']],
        'stock_transfer_reasons' => ['stock_transfer_reasons_code_unique', ['code']],
        'statuses' => ['statuses_context_code_unique', ['context', 'code']],
        'grn_statuses' => ['grn_statuses_code_unique', ['code']],
        'refund_methods' => ['refund_methods_code_unique', ['code']],
        'payment_terms' => ['payment_terms_code_unique', ['code']],
        'ledger_entry_types' => ['ledger_entry_types_code_unique', ['code']],
        'supplier_address_types' => ['supplier_address_types_code_unique', ['code']],
        'discount_types' => ['discount_types_code_unique', ['code']],
        'voucher_types' => ['voucher_types_code_unique', ['code']],
        'loyalty_transaction_types' => ['loyalty_transaction_types_code_unique', ['code']],
        'unit_of_measures' => ['unit_of_measures_code_unique', ['code']],
        'vouchers' => ['vouchers_code_unique', ['code']],
        'product_variants_sku' => ['product_variants_sku_unique', ['sku']],
        'product_variants_barcode' => ['product_variants_barcode_unique', ['barcode']],
        'product_units_barcode' => ['product_units_barcode_unique', ['barcode']],
        'product_bundles_sku' => ['product_bundles_sku_unique', ['sku']],
        'purchase_orders' => ['purchase_orders_po_number_unique', ['po_number']],
    ];

    public function up(): void
    {
        // 1. Add nullable tenant_id (+ index) to every owned table.
        foreach ($this->ownedTables as $table) {
            if (! Schema::hasColumn($table, 'tenant_id')) {
                Schema::table($table, function (Blueprint $t) {
                    $t->unsignedBigInteger('tenant_id')->nullable()->after('id')->index();
                });
            }
        }

        // 2. Create the first tenant from the existing single-tenant deployment.
        $tenantId = DB::table('tenants')->insertGetId([
            'name' => 'Vellix Global',
            'slug' => 'vellix',
            'status' => 'active',
            'created_at' => now(),
            'updated_at' => now(),
        ]);

        DB::table('tenant_settings')->insert(
            ['tenant_id' => $tenantId, 'created_at' => now(), 'updated_at' => now()] + $this->seedSettings()
        );

        // 3. Backfill every existing row to that tenant.
        foreach ($this->ownedTables as $table) {
            DB::table($table)->whereNull('tenant_id')->update(['tenant_id' => $tenantId]);
        }

        // 4. Enforce NOT NULL on MySQL (sqlite test tables are empty here).
        if (DB::getDriverName() === 'mysql') {
            foreach ($this->ownedTables as $table) {
                DB::statement("ALTER TABLE `{$table}` MODIFY `tenant_id` BIGINT UNSIGNED NOT NULL");
            }
        }

        // 5. Swap global unique keys to per-tenant composites.
        foreach ($this->uniqueSwaps as [$indexName, $columns]) {
            $table = $this->tableForSwap($indexName);
            Schema::table($table, function (Blueprint $t) use ($indexName, $columns) {
                $t->dropUnique($indexName);
                $t->unique(array_merge(['tenant_id'], $columns));
            });
        }

        // 6. Grant the tenant the full entitlement ceiling (everything it has today).
        $now = now();
        $rows = DB::table('permissions')->pluck('id')->map(fn ($pid) => [
            'tenant_id' => $tenantId,
            'permission_id' => $pid,
            'created_at' => $now,
            'updated_at' => $now,
        ])->all();

        if ($rows !== []) {
            DB::table('tenant_permissions')->insert($rows);
        }
    }

    public function down(): void
    {
        foreach ($this->uniqueSwaps as [$indexName, $columns]) {
            $table = $this->tableForSwap($indexName);
            Schema::table($table, function (Blueprint $t) use ($indexName, $columns) {
                $t->dropUnique(array_merge(['tenant_id'], $columns));
                $t->unique($columns, $indexName);
            });
        }

        foreach ($this->ownedTables as $table) {
            if (Schema::hasColumn($table, 'tenant_id')) {
                Schema::table($table, function (Blueprint $t) {
                    $t->dropIndex([$t->getTable().'_tenant_id_index']);
                    $t->dropColumn('tenant_id');
                });
            }
        }

        DB::table('tenant_permissions')->delete();
        DB::table('tenants')->where('slug', 'vellix')->delete();
    }

    private function tableForSwap(string $indexName): string
    {
        return match (true) {
            str_starts_with($indexName, 'users_') => 'users',
            str_starts_with($indexName, 'roles_') => 'roles',
            str_starts_with($indexName, 'products_') => 'products',
            str_starts_with($indexName, 'main_categories_') => 'main_categories',
            str_starts_with($indexName, 'credit_notes_') => 'credit_notes',
            str_starts_with($indexName, 'damages_') => 'damages',
            str_starts_with($indexName, 'invoices_') => 'invoices',
            str_starts_with($indexName, 'returns_') => 'returns',
            str_starts_with($indexName, 'supplier_documents_') => 'supplier_documents',
            str_starts_with($indexName, 'tills_') => 'tills',
            str_starts_with($indexName, 'transactions_') => 'transactions',
            str_starts_with($indexName, 'transaction_claims_') => 'transaction_claims',
            str_starts_with($indexName, 'sales_reps_') => 'sales_reps',
            str_starts_with($indexName, 'document_sequences_') => 'document_sequences',
            str_starts_with($indexName, 'adjustment_reasons_') => 'adjustment_reasons',
            str_starts_with($indexName, 'damage_reasons_') => 'damage_reasons',
            str_starts_with($indexName, 'movement_types_') => 'movement_types',
            str_starts_with($indexName, 'payment_methods_') => 'payment_methods',
            str_starts_with($indexName, 'return_reasons_') => 'return_reasons',
            str_starts_with($indexName, 'stock_transfer_reasons_') => 'stock_transfer_reasons',
            str_starts_with($indexName, 'statuses_') => 'statuses',
            str_starts_with($indexName, 'grn_statuses_') => 'grn_statuses',
            str_starts_with($indexName, 'refund_methods_') => 'refund_methods',
            str_starts_with($indexName, 'payment_terms_') => 'payment_terms',
            str_starts_with($indexName, 'ledger_entry_types_') => 'ledger_entry_types',
            str_starts_with($indexName, 'supplier_address_types_') => 'supplier_address_types',
            str_starts_with($indexName, 'discount_types_') => 'discount_types',
            str_starts_with($indexName, 'voucher_types_') => 'voucher_types',
            str_starts_with($indexName, 'loyalty_transaction_types_') => 'loyalty_transaction_types',
            str_starts_with($indexName, 'unit_of_measures_') => 'unit_of_measures',
            str_starts_with($indexName, 'vouchers_') => 'vouchers',
            str_starts_with($indexName, 'product_variants_') => 'product_variants',
            str_starts_with($indexName, 'product_units_') => 'product_units',
            str_starts_with($indexName, 'product_bundles_') => 'product_bundles',
            str_starts_with($indexName, 'purchase_orders_') => 'purchase_orders',
            default => throw new RuntimeException("Unmapped unique swap: {$indexName}"),
        };
    }

    /**
     * Branding/company settings carried over from the previously hard-coded
     * constants so prints and the login page render identically post-migration.
     *
     * @return array<string, mixed>
     */
    private function seedSettings(): array
    {
        return [
            'branding_app_name' => 'Vellix Global',
            'branding_logo_url' => '/images/vellix-global-logo.png',
            'branding_login_heading' => 'Welcome back',
            'branding_login_subtitle' => 'Sign in to your account to continue',

            'company_name' => 'VELLIX GLOBAL',
            'company_address' => 'No 31/B, Kumarasinghe Mawatha, Badulla',
            'company_email' => 'info@vellixglobal.com',
            'company_registration_no' => 'PV00348376',
            'company_phone' => json_encode(['0761726337', '0774707807']),

            'invoice_bank_acc' => '221010007221',
            'invoice_bank_name' => 'VELLIX GLOBAL',
            'invoice_bank' => 'HNB BANK',
            'invoice_footer_addr' => '31/D, Kumarasinghe Mw, Badulla',
            'invoice_footer_phone' => '011 2505406 / 0777284011 / 0773187737',
            'invoice_note' => 'None of the items are refundable except damaged items!',

            'locale_currency' => 'LKR',
            'locale_currency_symbol' => 'Rs.',
            'locale_locale' => 'en-LK',
            'locale_timezone' => 'Asia/Colombo',
        ];
    }
};
