import { MigrationInterface, QueryRunner } from 'typeorm';

/**
 * Fold warehouse fee invoices into the central billing system.
 *
 * Warehouse fee invoices are no longer a standalone aggregate: each becomes a
 * global `freight.invoices` row (`source = 'warehouse'`, `source_id =
 * inventory_id`) with its items as `freight.invoice_lines`. The warehouse
 * service is now a thin layer over `BillingService`. This migration backfills the
 * existing rows (preserving ids, numbers, status, amounts and payment history),
 * then drops the two legacy tables.
 *
 * Rows that cannot be billed centrally — no company to bill (`company_id` /
 * `company_profile_id` underivable from the customer or the booking) — are not
 * migrated; they could never have been charged through the gateway and are
 * dropped with the table.
 */
export class CentralizeWarehouseInvoices1829000000000 implements MigrationInterface {
  name = 'CentralizeWarehouseInvoices1829000000000';

  public async up(queryRunner: QueryRunner): Promise<void> {
    await queryRunner.query(`
      DO $$
      BEGIN
        IF EXISTS (
          SELECT 1
            FROM information_schema.columns
           WHERE table_schema = 'freight'
             AND table_name = 'invoices'
             AND column_name = 'booking_id'
        ) THEN
          ALTER TABLE freight.invoices ALTER COLUMN booking_id DROP NOT NULL;
        END IF;

        IF EXISTS (
          SELECT 1
            FROM information_schema.columns
           WHERE table_schema = 'freight'
             AND table_name = 'invoices'
             AND column_name = 'amount'
        ) THEN
          ALTER TABLE freight.invoices ALTER COLUMN amount DROP NOT NULL;
        END IF;
      END $$;
    `);

    // 1. Invoice headers. Keep the same id so items still link, and so any
    //    external reference to the invoice id stays valid.
    await queryRunner.query(`
      INSERT INTO freight.invoices (
        id, invoice_number, company_id, company_profile_id,
        subtotal_amount, tax_amount, total_amount, paid_amount, balance_amount,
        currency, status, source, source_id, type,
        issued_at, paid_at, payments, payment_id, due_at,
        created_at, updated_at, deleted_at
      )
      SELECT
        fee.id,
        fee.invoice_number,
        COALESCE(fee.customer_id, b.company_id),
        COALESCE(
          b.company_profile_id,
          (SELECT cp.id
             FROM freight.company_profiles cp
            WHERE cp.company_id = COALESCE(fee.customer_id, b.company_id)
              AND cp.deleted_at IS NULL
            ORDER BY cp.created_at ASC
            LIMIT 1)
        ),
        fee.subtotal_amount, fee.tax_amount, fee.total_amount, fee.paid_amount, fee.balance_amount,
        fee.currency,
        fee.status::freight.invoices_status_enum,
        'warehouse',
        fee.inventory_id,
        fee.invoice_type,
        fee.issued_at,
        fee.paid_at,
        COALESCE(fee.payments, '[]'::jsonb),
        NULL,
        COALESCE(fee.due_date, fee.issued_at, fee.created_at),
        fee.created_at, fee.updated_at, fee.deleted_at
      FROM freight.warehouse_fee_invoices fee
      LEFT JOIN freight.bookings b ON b.id = fee.booking_id
      WHERE COALESCE(fee.customer_id, b.company_id) IS NOT NULL
        AND COALESCE(
          b.company_profile_id,
          (SELECT cp.id
             FROM freight.company_profiles cp
            WHERE cp.company_id = COALESCE(fee.customer_id, b.company_id)
              AND cp.deleted_at IS NULL
            ORDER BY cp.created_at ASC
            LIMIT 1)
        ) IS NOT NULL
      ON CONFLICT (id) DO NOTHING;
    `);

    // 2. Invoice lines — only for items whose parent invoice migrated. Warehouse
    //    fee fields (fee_rule_id / chargeable_days / free_days) move into the
    //    line's jsonb metadata.
    await queryRunner.query(`
      INSERT INTO freight.invoice_lines (
        id, invoice_id, charge_type, description, quantity, unit_rate, amount,
        currency, metadata, created_at, updated_at, deleted_at
      )
      SELECT
        item.id,
        item.invoice_id,
        item.fee_type,
        item.description,
        item.quantity,
        item.unit_rate,
        item.amount,
        item.currency,
        jsonb_build_object(
          'feeRuleId', item.fee_rule_id,
          'chargeableDays', item.chargeable_days,
          'freeDays', item.free_days
        ),
        item.created_at, item.updated_at, item.deleted_at
      FROM freight.warehouse_fee_invoice_items item
      JOIN freight.invoices i ON i.id = item.invoice_id AND i.source = 'warehouse'
      ON CONFLICT (id) DO NOTHING;
    `);

    // 3. Drop the legacy tables (items first — FK to invoices).
    await queryRunner.query(`DROP TABLE IF EXISTS freight.warehouse_fee_invoice_items;`);
    await queryRunner.query(`DROP TABLE IF EXISTS freight.warehouse_fee_invoices;`);
  }

  public async down(queryRunner: QueryRunner): Promise<void> {
    // Recreate the legacy tables …
    await queryRunner.query(`
      CREATE TABLE IF NOT EXISTS freight.warehouse_fee_invoices (
        id uuid NOT NULL DEFAULT uuid_generate_v4(),
        created_at timestamptz NOT NULL DEFAULT now(),
        updated_at timestamptz NOT NULL DEFAULT now(),
        deleted_at timestamptz,
        invoice_number varchar(40) NOT NULL,
        booking_id uuid,
        customer_id uuid,
        inventory_id uuid NOT NULL,
        facility_id uuid,
        warehouse_id uuid,
        yard_id uuid,
        zone_id uuid,
        invoice_type varchar(32) NOT NULL DEFAULT 'MIXED_WAREHOUSE_FEES',
        status varchar(20) NOT NULL DEFAULT 'DRAFT',
        subtotal_amount numeric(14,2) NOT NULL DEFAULT 0,
        tax_amount numeric(14,2) NOT NULL DEFAULT 0,
        total_amount numeric(14,2) NOT NULL DEFAULT 0,
        paid_amount numeric(14,2) NOT NULL DEFAULT 0,
        balance_amount numeric(14,2) NOT NULL DEFAULT 0,
        currency varchar(8) NOT NULL DEFAULT 'USD',
        period_start timestamptz,
        period_end timestamptz,
        issued_at timestamptz,
        due_date timestamptz,
        paid_at timestamptz,
        cancelled_at timestamptz,
        payments jsonb NOT NULL DEFAULT '[]',
        notes text,
        CONSTRAINT "PK_warehouse_fee_invoices" PRIMARY KEY (id),
        CONSTRAINT "UQ_warehouse_fee_invoices_invoice_number" UNIQUE (invoice_number)
      );
    `);
    await queryRunner.query(
      `CREATE INDEX IF NOT EXISTS "IDX_warehouse_fee_invoices_booking_id" ON freight.warehouse_fee_invoices (booking_id);`,
    );
    await queryRunner.query(
      `CREATE INDEX IF NOT EXISTS "IDX_warehouse_fee_invoices_inventory_id" ON freight.warehouse_fee_invoices (inventory_id);`,
    );
    await queryRunner.query(
      `CREATE INDEX IF NOT EXISTS "IDX_warehouse_fee_invoices_status" ON freight.warehouse_fee_invoices (status);`,
    );
    await queryRunner.query(`
      CREATE TABLE IF NOT EXISTS freight.warehouse_fee_invoice_items (
        id uuid NOT NULL DEFAULT uuid_generate_v4(),
        created_at timestamptz NOT NULL DEFAULT now(),
        updated_at timestamptz NOT NULL DEFAULT now(),
        deleted_at timestamptz,
        invoice_id uuid NOT NULL,
        fee_rule_id uuid,
        fee_type varchar(32) NOT NULL,
        description varchar(255) NOT NULL,
        quantity numeric(12,2) NOT NULL DEFAULT 1,
        unit_rate numeric(14,2) NOT NULL DEFAULT 0,
        amount numeric(14,2) NOT NULL DEFAULT 0,
        currency varchar(8) NOT NULL DEFAULT 'USD',
        chargeable_days int,
        free_days int,
        CONSTRAINT "PK_warehouse_fee_invoice_items" PRIMARY KEY (id),
        CONSTRAINT "FK_warehouse_fee_invoice_items_invoice"
          FOREIGN KEY (invoice_id) REFERENCES freight.warehouse_fee_invoices (id) ON DELETE CASCADE
      );
    `);
    await queryRunner.query(
      `CREATE INDEX IF NOT EXISTS "IDX_warehouse_fee_invoice_items_invoice_id" ON freight.warehouse_fee_invoice_items (invoice_id);`,
    );

    // … then copy the warehouse-source invoices back, deriving the typed FKs and
    // period from the linked inventory item.
    await queryRunner.query(`
      INSERT INTO freight.warehouse_fee_invoices (
        id, created_at, updated_at, deleted_at, invoice_number,
        booking_id, customer_id, inventory_id, facility_id, warehouse_id, yard_id, zone_id,
        invoice_type, status, subtotal_amount, tax_amount, total_amount, paid_amount, balance_amount,
        currency, period_start, period_end, issued_at, due_date, paid_at, cancelled_at, payments, notes
      )
      SELECT
        i.id, i.created_at, i.updated_at, i.deleted_at, i.invoice_number,
        inv.booking_id, i.company_id, i.source_id, w.facility_id, inv.warehouse_id, inv.yard_id, inv.zone_id,
        i.type, i.status::text, i.subtotal_amount, i.tax_amount, i.total_amount, i.paid_amount, i.balance_amount,
        i.currency, inv.arrived_at, i.issued_at, i.issued_at, i.due_at, i.paid_at,
        CASE WHEN i.status::text = 'CANCELLED' THEN i.updated_at ELSE NULL END,
        i.payments, NULL
      FROM freight.invoices i
      LEFT JOIN freight.warehouse_inventory inv ON inv.id = i.source_id
      LEFT JOIN freight.warehouses w ON w.id = inv.warehouse_id
      WHERE i.source = 'warehouse'
      ON CONFLICT (id) DO NOTHING;
    `);
    await queryRunner.query(`
      INSERT INTO freight.warehouse_fee_invoice_items (
        id, created_at, updated_at, deleted_at, invoice_id, fee_rule_id, fee_type,
        description, quantity, unit_rate, amount, currency, chargeable_days, free_days
      )
      SELECT
        l.id, l.created_at, l.updated_at, l.deleted_at, l.invoice_id,
        NULLIF(l.metadata->>'feeRuleId', '')::uuid,
        l.charge_type,
        COALESCE(l.description, ''),
        l.quantity, l.unit_rate, l.amount, l.currency,
        NULLIF(l.metadata->>'chargeableDays', '')::int,
        NULLIF(l.metadata->>'freeDays', '')::int
      FROM freight.invoice_lines l
      JOIN freight.invoices i ON i.id = l.invoice_id AND i.source = 'warehouse'
      ON CONFLICT (id) DO NOTHING;
    `);

    // Remove the migrated rows from the central tables.
    await queryRunner.query(`
      DELETE FROM freight.invoice_lines
       WHERE invoice_id IN (SELECT id FROM freight.invoices WHERE source = 'warehouse');
    `);
    await queryRunner.query(`DELETE FROM freight.invoices WHERE source = 'warehouse';`);
  }
}
