import {
  MigrationInterface,
  QueryRunner,
  Table,
  TableForeignKey,
  TableIndex,
  TableUnique,
} from 'typeorm';

export class ItmlsFullSchemaRewrite1748600000000 implements MigrationInterface {
  name = 'ItmlsFullSchemaRewrite1748600000000';

  public async up(queryRunner: QueryRunner): Promise<void> {
    // ── container_types ───────────────────────────────────────────────────
    await queryRunner.query(`
      DO $$ BEGIN
        ALTER TABLE freight.container_types RENAME COLUMN size_code TO code;
      EXCEPTION WHEN undefined_column THEN NULL;
      END $$;
    `);
    await queryRunner.query(`
      DO $$ BEGIN
        ALTER TABLE freight.container_types RENAME COLUMN description TO label;
      EXCEPTION WHEN undefined_column THEN NULL;
      END $$;
    `);
    await queryRunner.query(`
      ALTER TABLE freight.container_types
        ADD COLUMN IF NOT EXISTS size_ft SMALLINT,
        ADD COLUMN IF NOT EXISTS is_reefer BOOLEAN NOT NULL DEFAULT false,
        ADD COLUMN IF NOT EXISTS is_open_top BOOLEAN NOT NULL DEFAULT false,
        ADD COLUMN IF NOT EXISTS display_order INT NOT NULL DEFAULT 1;
    `);
    await queryRunner.query(`
      ALTER TABLE freight.container_types
        ADD COLUMN IF NOT EXISTS wagons_per_unit NUMERIC(4,2);
    `);
    await queryRunner.query(`
      UPDATE freight.container_types
      SET wagons_per_unit = CASE
        WHEN containers_per_wagon > 0 THEN ROUND(1.0 / containers_per_wagon, 2)
        ELSE 1.00
      END
      WHERE wagons_per_unit IS NULL;
    `);
    await queryRunner.query(`
      UPDATE freight.container_types
      SET size_ft = CASE WHEN code LIKE '40%' OR code LIKE '%40%' THEN 40 ELSE 20 END
      WHERE size_ft IS NULL;
    `);
    await queryRunner.query(`
      ALTER TABLE freight.container_types
        ALTER COLUMN wagons_per_unit SET NOT NULL,
        DROP COLUMN IF EXISTS containers_per_wagon;
    `);

    // ── weight_limit_rules ──────────────────────────────────────────────────
    await queryRunner.query(`
      DO $$ BEGIN
        ALTER TABLE freight.weight_limit_rules RENAME COLUMN max_weight_tons TO max_vgm_tons;
      EXCEPTION WHEN undefined_column THEN NULL;
      END $$;
    `);
    await queryRunner.query(`
      ALTER TABLE freight.weight_limit_rules
        ALTER COLUMN max_vgm_tons TYPE NUMERIC(8,3);
    `);
    await queryRunner.query(`
      ALTER TABLE freight.weight_limit_rules
        ADD COLUMN IF NOT EXISTS effective_from DATE NOT NULL DEFAULT CURRENT_DATE,
        ADD COLUMN IF NOT EXISTS effective_to DATE;
    `);
    await queryRunner.query(`
      ALTER TABLE freight.weight_limit_rules
        DROP COLUMN IF EXISTS warning_threshold_tons,
        DROP COLUMN IF EXISTS exceeded_action,
        DROP COLUMN IF EXISTS surcharge_id,
        DROP COLUMN IF EXISTS is_active;
    `);

    // ── priority_rules ────────────────────────────────────────────────────
    await queryRunner.query(`
      ALTER TABLE freight.priority_rules
        ADD COLUMN IF NOT EXISTS code VARCHAR(40),
        ADD COLUMN IF NOT EXISTS label VARCHAR(100),
        ADD COLUMN IF NOT EXISTS score INT NOT NULL DEFAULT 0,
        ADD COLUMN IF NOT EXISTS condition_currency VARCHAR(5);
    `);
    await queryRunner.query(`
      UPDATE freight.priority_rules
      SET code = COALESCE(code, upper(priority_type::text)),
          label = COALESCE(label, rule_name),
          score = COALESCE(score, bonus_points)
      WHERE code IS NULL OR label IS NULL;
    `);
    await queryRunner.query(`
      ALTER TABLE freight.priority_rules
        DROP COLUMN IF EXISTS priority_type,
        DROP COLUMN IF EXISTS rule_name,
        DROP COLUMN IF EXISTS bonus_points,
        DROP COLUMN IF EXISTS activation_condition,
        DROP COLUMN IF EXISTS description;
    `);
    await queryRunner.query(`
      ALTER TABLE freight.priority_rules
        ALTER COLUMN code SET NOT NULL,
        ALTER COLUMN label SET NOT NULL;
    `);
    await queryRunner.createIndex(
      'freight.priority_rules',
      new TableIndex({ name: 'UQ_priority_rules_code', columnNames: ['code'], isUnique: true }),
    );

    // ── rates (before surcharge_types.rate_id) ────────────────────────────
    await queryRunner.createTable(
      new Table({
        name: 'rates',
        schema: 'freight',
        columns: [
          { name: 'id', type: 'uuid', isPrimary: true, generationStrategy: 'uuid', default: 'uuid_generate_v4()' },
          { name: 'rate_type', type: 'varchar', length: '50' },
          { name: 'container_type_id', type: 'uuid', isNullable: true },
          { name: 'trade_direction', type: 'varchar', length: '10', isNullable: true },
          { name: 'currency', type: 'varchar', length: '5' },
          { name: 'rate_value', type: 'numeric', precision: 14, scale: 4 },
          { name: 'rate_unit', type: 'varchar', length: '30' },
          { name: 'status', type: 'varchar', length: '20', default: "'DRAFT'" },
          { name: 'proposed_by_staff_id', type: 'uuid' },
          { name: 'approved_by_ceo_id', type: 'uuid', isNullable: true },
          { name: 'approved_at', type: 'timestamptz', isNullable: true },
          { name: 'effective_from', type: 'date' },
          { name: 'effective_to', type: 'date', isNullable: true },
          { name: 'created_at', type: 'timestamptz', default: 'now()' },
          { name: 'updated_at', type: 'timestamptz', default: 'now()' },
          { name: 'deleted_at', type: 'timestamptz', isNullable: true },
        ],
      }),
      true,
    );

    // ── surcharge_types ───────────────────────────────────────────────────
    await queryRunner.query(`
      DO $$ BEGIN
        ALTER TABLE freight.surcharge_types RENAME COLUMN name TO label;
      EXCEPTION WHEN undefined_column THEN NULL;
      END $$;
    `);
    await queryRunner.query(`
      ALTER TABLE freight.surcharge_types
        ADD COLUMN IF NOT EXISTS trigger_condition VARCHAR(50),
        ADD COLUMN IF NOT EXISTS rate_id UUID;
    `);
    await queryRunner.query(`ALTER TABLE freight.surcharge_types DROP COLUMN IF EXISTS description`);

    await queryRunner.query(`DROP TABLE IF EXISTS freight.surcharges CASCADE`);

    // ── yards ─────────────────────────────────────────────────────────────
    await queryRunner.createTable(
      new Table({
        name: 'yards',
        schema: 'freight',
        columns: [
          { name: 'id', type: 'uuid', isPrimary: true, generationStrategy: 'uuid', default: 'uuid_generate_v4()' },
          { name: 'code', type: 'varchar', length: '20' },
          { name: 'label', type: 'varchar', length: '100' },
          { name: 'country', type: 'varchar', length: '50' },
          { name: 'is_active', type: 'boolean', default: true },
          { name: 'display_order', type: 'int', default: 1 },
          { name: 'created_at', type: 'timestamptz', default: 'now()' },
          { name: 'updated_at', type: 'timestamptz', default: 'now()' },
          { name: 'deleted_at', type: 'timestamptz', isNullable: true },
        ],
      }),
      true,
    );
    await queryRunner.createIndex(
      'freight.yards',
      new TableIndex({ name: 'UQ_yards_code', columnNames: ['code'], isUnique: true }),
    );

    // ── shipping_lines ────────────────────────────────────────────────────
    await queryRunner.createTable(
      new Table({
        name: 'shipping_lines',
        schema: 'freight',
        columns: [
          { name: 'id', type: 'uuid', isPrimary: true, generationStrategy: 'uuid', default: 'uuid_generate_v4()' },
          { name: 'code', type: 'varchar', length: '20' },
          { name: 'label', type: 'varchar', length: '100' },
          { name: 'mapped_to_code', type: 'varchar', length: '20', isNullable: true },
          { name: 'show_extra_fee_notice', type: 'boolean', default: false },
          { name: 'is_active', type: 'boolean', default: true },
          { name: 'created_at', type: 'timestamptz', default: 'now()' },
          { name: 'updated_at', type: 'timestamptz', default: 'now()' },
          { name: 'deleted_at', type: 'timestamptz', isNullable: true },
        ],
      }),
      true,
    );

    // ── approval_rules ────────────────────────────────────────────────────
    await queryRunner.createTable(
      new Table({
        name: 'approval_rules',
        schema: 'freight',
        columns: [
          { name: 'id', type: 'uuid', isPrimary: true, generationStrategy: 'uuid', default: 'uuid_generate_v4()' },
          { name: 'requires_director_approval', type: 'boolean' },
          { name: 'step_order', type: 'smallint' },
          { name: 'required_role', type: 'varchar', length: '30' },
          { name: 'action_label', type: 'varchar', length: '50' },
          { name: 'blocks_role', type: 'varchar', length: '30', isNullable: true },
          { name: 'created_at', type: 'timestamptz', default: 'now()' },
          { name: 'updated_at', type: 'timestamptz', default: 'now()' },
          { name: 'deleted_at', type: 'timestamptz', isNullable: true },
        ],
      }),
      true,
    );
    await queryRunner.createUniqueConstraint(
      'freight.approval_rules',
      new TableUnique({
        name: 'UQ_approval_rules_chain_step',
        columnNames: ['requires_director_approval', 'step_order'],
      }),
    );

    // ── bookings ────────────────────────────────────────────────────────
    await queryRunner.query(`
      ALTER TABLE freight.bookings
        ADD COLUMN IF NOT EXISTS origin_yard_id UUID,
        ADD COLUMN IF NOT EXISTS destination_yard_id UUID,
        ADD COLUMN IF NOT EXISTS service_type_id UUID,
        ADD COLUMN IF NOT EXISTS cargo_type_id UUID,
        ADD COLUMN IF NOT EXISTS cargo_free_text VARCHAR(200),
        ADD COLUMN IF NOT EXISTS shipping_line_id UUID,
        ADD COLUMN IF NOT EXISTS pnr_code VARCHAR(50),
        ADD COLUMN IF NOT EXISTS customer_signed_at TIMESTAMPTZ,
        ADD COLUMN IF NOT EXISTS fully_executed_at TIMESTAMPTZ;
    `);

    await queryRunner.query(`
      INSERT INTO freight.yards (id, code, label, country, is_active, display_order, created_at, updated_at)
      VALUES
        (uuid_generate_v4(), 'KALITY', 'Kality Rail Terminal', 'Ethiopia', true, 1, now(), now()),
        (uuid_generate_v4(), 'MOJO', 'Mojo Dry Port', 'Ethiopia', true, 2, now(), now()),
        (uuid_generate_v4(), 'DIRE_DAWA', 'Dire Dawa Yard', 'Ethiopia', true, 3, now(), now()),
        (uuid_generate_v4(), 'DJIB_PORT', 'Djibouti Port Terminal', 'Djibouti', true, 4, now(), now()),
        (uuid_generate_v4(), 'NAGAD', 'Nagad Terminal, Djibouti', 'Djibouti', true, 5, now(), now()),
        (uuid_generate_v4(), 'LEGACY_ORIGIN', 'Legacy Origin', 'Ethiopia', true, 99, now(), now()),
        (uuid_generate_v4(), 'LEGACY_DEST', 'Legacy Destination', 'Ethiopia', true, 100, now(), now())
      ON CONFLICT (code) DO NOTHING;
    `);

    await queryRunner.query(`
      INSERT INTO freight.service_types (id, code, service_name, can_be_booked_alone, includes_first_mile, includes_last_mile, includes_customs, priority_bonus_points, is_active, display_order, created_at, updated_at)
      SELECT uuid_generate_v4(), 'RAIL', 'Rail Transport Only', true, false, false, false, 0, true, 1, now(), now()
      WHERE NOT EXISTS (SELECT 1 FROM freight.service_types LIMIT 1);
    `);
    await queryRunner.query(`
      INSERT INTO freight.cargo_types (id, code, cargo_type_name, requires_director_approval, show_free_text_box, is_active, display_order, created_at, updated_at)
      SELECT uuid_generate_v4(), 'GENERAL', 'General Cargo', false, false, true, 1, now(), now()
      WHERE NOT EXISTS (SELECT 1 FROM freight.cargo_types LIMIT 1);
    `);

    const hasServiceTypeCol = await queryRunner.query(`
      SELECT 1 FROM information_schema.columns
      WHERE table_schema = 'freight' AND table_name = 'bookings' AND column_name = 'service_type'
      LIMIT 1;
    `);

    if (hasServiceTypeCol.length > 0) {
      await queryRunner.query(`
        UPDATE freight.bookings b
        SET service_type_id = st.id
        FROM freight.service_types st
        WHERE b.service_type_id IS NULL
          AND (
            st.code = b.service_type
            OR upper(replace(st.service_name, ' ', '_')) = upper(b.service_type)
            OR st.code = upper(replace(b.service_type, ' ', '_'))
          );
      `);
      await queryRunner.query(`
        UPDATE freight.bookings b
        SET cargo_type_id = ct.id
        FROM freight.cargo_types ct
        WHERE b.cargo_type_id IS NULL
          AND (
            ct.code = upper(b.freight_type)
            OR ct.code = upper(concat(b.freight_type, '_', coalesce(b.freight_subtype, '')))
          );
      `);
      await queryRunner.query(`
        UPDATE freight.bookings b
        SET cargo_free_text = b.freight_subtype
        WHERE b.cargo_free_text IS NULL AND b.freight_subtype IS NOT NULL;
      `);
      await queryRunner.query(`
        UPDATE freight.bookings b
        SET origin_yard_id = y.id
        FROM freight.yards y
        WHERE b.origin_yard_id IS NULL
          AND (y.label ILIKE b.origin_station OR y.code = upper(replace(b.origin_station, ' ', '_')));
      `);
      await queryRunner.query(`
        UPDATE freight.bookings b
        SET destination_yard_id = y.id
        FROM freight.yards y
        WHERE b.destination_yard_id IS NULL
          AND (y.label ILIKE b.destination_station OR y.code = upper(replace(b.destination_station, ' ', '_')));
      `);
    }

    const defaultServiceTypeId = await queryRunner.query(
      `SELECT id FROM freight.service_types ORDER BY display_order LIMIT 1`,
    );
    const defaultCargoTypeId = await queryRunner.query(
      `SELECT id FROM freight.cargo_types ORDER BY display_order LIMIT 1`,
    );
    const legacyOriginId = await queryRunner.query(
      `SELECT id FROM freight.yards WHERE code = 'LEGACY_ORIGIN' LIMIT 1`,
    );
    const legacyDestId = await queryRunner.query(
      `SELECT id FROM freight.yards WHERE code = 'LEGACY_DEST' LIMIT 1`,
    );

    if (defaultServiceTypeId[0]?.id) {
      await queryRunner.query(
        `UPDATE freight.bookings SET service_type_id = $1 WHERE service_type_id IS NULL`,
        [defaultServiceTypeId[0].id],
      );
    }
    if (defaultCargoTypeId[0]?.id) {
      await queryRunner.query(
        `UPDATE freight.bookings SET cargo_type_id = $1 WHERE cargo_type_id IS NULL`,
        [defaultCargoTypeId[0].id],
      );
    }
    if (legacyOriginId[0]?.id) {
      await queryRunner.query(
        `UPDATE freight.bookings SET origin_yard_id = $1 WHERE origin_yard_id IS NULL`,
        [legacyOriginId[0].id],
      );
    }
    if (legacyDestId[0]?.id) {
      await queryRunner.query(
        `UPDATE freight.bookings SET destination_yard_id = $1 WHERE destination_yard_id IS NULL`,
        [legacyDestId[0].id],
      );
    }

    const nullBookings = await queryRunner.query(
      `SELECT COUNT(*)::int AS cnt FROM freight.bookings WHERE service_type_id IS NULL OR cargo_type_id IS NULL OR origin_yard_id IS NULL OR destination_yard_id IS NULL`,
    );
    if (nullBookings[0]?.cnt > 0) {
      await queryRunner.query(`DELETE FROM freight.bookings WHERE service_type_id IS NULL OR cargo_type_id IS NULL OR origin_yard_id IS NULL OR destination_yard_id IS NULL`);
    }

    await queryRunner.query(`
      ALTER TABLE freight.bookings
        ALTER COLUMN service_type_id SET NOT NULL,
        ALTER COLUMN cargo_type_id SET NOT NULL,
        ALTER COLUMN origin_yard_id SET NOT NULL,
        ALTER COLUMN destination_yard_id SET NOT NULL;
    `);

    await queryRunner.query(`
      ALTER TABLE freight.bookings
        DROP COLUMN IF EXISTS origin_station,
        DROP COLUMN IF EXISTS destination_station,
        DROP COLUMN IF EXISTS service_type,
        DROP COLUMN IF EXISTS freight_type,
        DROP COLUMN IF EXISTS freight_subtype,
        DROP COLUMN IF EXISTS containers,
        DROP COLUMN IF EXISTS first_mile_enabled,
        DROP COLUMN IF EXISTS last_mile_enabled,
        DROP COLUMN IF EXISTS is_refrigerated;
    `);

    // ── booking_container ─────────────────────────────────────────────────
    await queryRunner.createTable(
      new Table({
        name: 'booking_container',
        schema: 'freight',
        columns: [
          { name: 'id', type: 'uuid', isPrimary: true, generationStrategy: 'uuid', default: 'uuid_generate_v4()' },
          { name: 'booking_id', type: 'uuid' },
          { name: 'container_type_id', type: 'uuid' },
          { name: 'quantity', type: 'smallint' },
          { name: 'vgm_per_unit_tons', type: 'numeric', precision: 10, scale: 3 },
          { name: 'total_vgm_tons', type: 'numeric', precision: 12, scale: 3 },
          { name: 'wagons_required', type: 'numeric', precision: 6, scale: 2 },
          { name: 'weight_limit_rule_id', type: 'uuid', isNullable: true },
          { name: 'is_overweight', type: 'boolean', default: false },
          { name: 'overweight_excess_tons', type: 'numeric', precision: 10, scale: 3, isNullable: true },
          { name: 'created_at', type: 'timestamptz', default: 'now()' },
          { name: 'updated_at', type: 'timestamptz', default: 'now()' },
          { name: 'deleted_at', type: 'timestamptz', isNullable: true },
        ],
      }),
      true,
    );

    await queryRunner.createTable(
      new Table({
        name: 'booking_rate_snapshot',
        schema: 'freight',
        columns: [
          { name: 'id', type: 'uuid', isPrimary: true, generationStrategy: 'uuid', default: 'uuid_generate_v4()' },
          { name: 'booking_id', type: 'uuid' },
          { name: 'rate_id', type: 'uuid' },
          { name: 'rate_type', type: 'varchar', length: '50' },
          { name: 'rate_value', type: 'numeric', precision: 14, scale: 4 },
          { name: 'rate_unit', type: 'varchar', length: '30' },
          { name: 'currency', type: 'varchar', length: '5' },
          { name: 'snapshotted_at', type: 'timestamptz' },
          { name: 'created_at', type: 'timestamptz', default: 'now()' },
          { name: 'updated_at', type: 'timestamptz', default: 'now()' },
          { name: 'deleted_at', type: 'timestamptz', isNullable: true },
        ],
      }),
      true,
    );

    await queryRunner.createTable(
      new Table({
        name: 'booking_cargo_modifier',
        schema: 'freight',
        columns: [
          { name: 'id', type: 'uuid', isPrimary: true, generationStrategy: 'uuid', default: 'uuid_generate_v4()' },
          { name: 'booking_id', type: 'uuid' },
          { name: 'surcharge_type_id', type: 'uuid' },
          { name: 'trigger_value', type: 'numeric', precision: 14, scale: 4, isNullable: true },
          { name: 'calculated_amount', type: 'numeric', precision: 14, scale: 2 },
          { name: 'rate_snapshot_id', type: 'uuid' },
          { name: 'created_at', type: 'timestamptz', default: 'now()' },
          { name: 'updated_at', type: 'timestamptz', default: 'now()' },
          { name: 'deleted_at', type: 'timestamptz', isNullable: true },
        ],
      }),
      true,
    );

    await queryRunner.createTable(
      new Table({
        name: 'booking_approval_step',
        schema: 'freight',
        columns: [
          { name: 'id', type: 'uuid', isPrimary: true, generationStrategy: 'uuid', default: 'uuid_generate_v4()' },
          { name: 'booking_id', type: 'uuid' },
          { name: 'approval_rule_id', type: 'uuid' },
          { name: 'step_order', type: 'smallint' },
          { name: 'required_role', type: 'varchar', length: '30' },
          { name: 'status', type: 'varchar', length: '20', default: "'PENDING'" },
          { name: 'actioned_by_staff_id', type: 'uuid', isNullable: true },
          { name: 'actioned_at', type: 'timestamptz', isNullable: true },
          { name: 'remarks', type: 'text', isNullable: true },
          { name: 'created_at', type: 'timestamptz', default: 'now()' },
          { name: 'updated_at', type: 'timestamptz', default: 'now()' },
          { name: 'deleted_at', type: 'timestamptz', isNullable: true },
        ],
      }),
      true,
    );

    // Foreign keys
    await queryRunner.createForeignKey(
      'freight.surcharge_types',
      new TableForeignKey({
        name: 'FK_surcharge_types_rate_id',
        columnNames: ['rate_id'],
        referencedTableName: 'rates',
        referencedSchema: 'freight',
        referencedColumnNames: ['id'],
      }),
    );
    await queryRunner.createForeignKey(
      'freight.booking_container',
      new TableForeignKey({
        columnNames: ['booking_id'],
        referencedTableName: 'bookings',
        referencedSchema: 'freight',
        referencedColumnNames: ['id'],
        onDelete: 'CASCADE',
      }),
    );
    await queryRunner.createForeignKey(
      'freight.bookings',
      new TableForeignKey({
        columnNames: ['origin_yard_id'],
        referencedTableName: 'yards',
        referencedSchema: 'freight',
        referencedColumnNames: ['id'],
      }),
    );
    await queryRunner.createForeignKey(
      'freight.bookings',
      new TableForeignKey({
        columnNames: ['destination_yard_id'],
        referencedTableName: 'yards',
        referencedSchema: 'freight',
        referencedColumnNames: ['id'],
      }),
    );
  }

  public async down(queryRunner: QueryRunner): Promise<void> {
    await queryRunner.dropTable('freight.booking_approval_step', true);
    await queryRunner.dropTable('freight.booking_cargo_modifier', true);
    await queryRunner.dropTable('freight.booking_rate_snapshot', true);
    await queryRunner.dropTable('freight.booking_container', true);

    await queryRunner.query(`
      ALTER TABLE freight.bookings
        ADD COLUMN IF NOT EXISTS origin_station VARCHAR(255),
        ADD COLUMN IF NOT EXISTS destination_station VARCHAR(255),
        ADD COLUMN IF NOT EXISTS service_type VARCHAR(30),
        ADD COLUMN IF NOT EXISTS freight_type VARCHAR(20),
        ADD COLUMN IF NOT EXISTS freight_subtype VARCHAR(100),
        ADD COLUMN IF NOT EXISTS containers JSONB,
        ADD COLUMN IF NOT EXISTS first_mile_enabled BOOLEAN DEFAULT false,
        ADD COLUMN IF NOT EXISTS last_mile_enabled BOOLEAN DEFAULT false,
        ADD COLUMN IF NOT EXISTS is_refrigerated BOOLEAN DEFAULT false;
    `);
    await queryRunner.query(`
      ALTER TABLE freight.bookings
        DROP COLUMN IF EXISTS origin_yard_id,
        DROP COLUMN IF EXISTS destination_yard_id,
        DROP COLUMN IF EXISTS service_type_id,
        DROP COLUMN IF EXISTS cargo_type_id,
        DROP COLUMN IF EXISTS cargo_free_text,
        DROP COLUMN IF EXISTS shipping_line_id,
        DROP COLUMN IF EXISTS pnr_code,
        DROP COLUMN IF EXISTS customer_signed_at,
        DROP COLUMN IF EXISTS fully_executed_at;
    `);

    await queryRunner.dropTable('freight.approval_rules', true);
    await queryRunner.dropTable('freight.shipping_lines', true);
    await queryRunner.dropTable('freight.yards', true);
    await queryRunner.dropTable('freight.rates', true);
  }
}
