/**
 * Cleanup migration to drop old channels tables and channel_id columns
 * This should only run AFTER verifying the boards migration was successful
 *
 * IMPORTANT: Only run this after:
 * 1. The base rename migration (20250930000001) has completed
 * 2. The EE Citus distribution migration (20250930000002) has completed (if using EE)
 * 3. Application code has been updated to use boards instead of channels
 * 4. Application has been tested with the new boards tables
 * 5. All data has been verified
 *
 * @param { import("knex").Knex } knex
 * @returns { Promise<void> }
 */
exports.config = { transaction: false };

exports.up = async function(knex) {
  console.log('Starting cleanup of old channels tables and columns...');

  // Step 0: Update views that use channel_id to use board_id instead
  // TODO: Handle this manually - update v_ticket_details view to use boards.board_id instead of channels.channel_id
  console.log('Skipping view updates (handle manually)...');

  //console.log('Updating views to use board_id instead of channel_id...');

  //const viewExists = await knex.raw(`
  //  SELECT EXISTS (
  //    SELECT FROM pg_views
  //    WHERE viewname = 'v_ticket_details'
  //  ) as exists
  //`);

  //if (viewExists.rows[0].exists) {
  //  try {
  //    // Drop and recreate the view with correct columns
  //    await knex.raw(`DROP VIEW IF EXISTS v_ticket_details CASCADE`);

  //    // Recreate view with board_id instead of channel_id
  //    await knex.raw(`
  //      CREATE VIEW v_ticket_details AS
  //      SELECT
  //        t.tenant,
  //        t.ticket_id,
  //        t.ticket_number,
  //        t.title,
  //        t.url,
  //        c.company_name AS company,
  //        cn.full_name AS contact_name,
  //        s.name AS status,
  //        b.board_name AS channel,
  //        cat.category_name AS category,
  //        subcat.category_name AS subcategory,
  //        p.priority_name AS priority,
  //        sev.severity_name AS severity,
  //        u.urgency_name AS urgency,
  //        i.impact_name AS impact,
  //        ue.username AS entered_by,
  //        uu.username AS updated_by,
  //        ua.username AS assigned_to,
  //        uc.username AS closed_by,
  //        t.entered_at,
  //        t.updated_at,
  //        t.closed_at,
  //        t.is_closed
  //      FROM tickets t
  //        LEFT JOIN companies c ON t.tenant = c.tenant AND t.company_id = c.company_id
  //        LEFT JOIN contacts cn ON t.tenant = cn.tenant AND t.contact_name_id = cn.contact_name_id
  //        LEFT JOIN statuses s ON t.tenant = s.tenant AND t.status_id = s.status_id
  //        LEFT JOIN boards b ON t.tenant = b.tenant AND t.board_id = b.board_id
  //        LEFT JOIN categories cat ON t.tenant = cat.tenant AND t.category_id = cat.category_id
  //        LEFT JOIN categories subcat ON t.tenant = subcat.tenant AND t.subcategory_id = subcat.category_id
  //        LEFT JOIN priorities p ON t.tenant = p.tenant AND t.priority_id = p.priority_id
  //        LEFT JOIN severities sev ON t.tenant = sev.tenant AND t.severity_id = sev.severity_id
  //        LEFT JOIN urgencies u ON t.tenant = u.tenant AND t.urgency_id = u.urgency_id
  //        LEFT JOIN impacts i ON t.tenant = i.tenant AND t.impact_id = i.impact_id
  //        LEFT JOIN users ue ON t.tenant = ue.tenant AND t.entered_by = ue.user_id
  //        LEFT JOIN users uu ON t.tenant = uu.tenant AND t.updated_by = uu.user_id
  //        LEFT JOIN users ua ON t.tenant = ua.tenant AND t.assigned_to = ua.user_id
  //        LEFT JOIN users uc ON t.tenant = uc.tenant AND t.closed_by = uc.user_id
  //    `);
  //    console.log('  ✓ Recreated v_ticket_details view with board_id');
  //  } catch (error) {
  //    console.log(`  ⚠ Could not update v_ticket_details view: ${error.message}`);
  //    console.log('  You may need to manually update this view');
  //  }
  //}

  // Step 1: Make channel_id nullable in related tables (if not already)
  // This allows the app to use board_id while channel_id still exists
  console.log('Making channel_id nullable in related tables...');

  const categoriesHasChannelId = await knex.schema.hasColumn('categories', 'channel_id');
  if (categoriesHasChannelId) {
    await knex.schema.alterTable('categories', (table) => {
      table.uuid('channel_id').nullable().alter();
    });
    console.log('  ✓ categories.channel_id is now nullable');
  }

  const ticketsHasChannelId = await knex.schema.hasColumn('tickets', 'channel_id');
  if (ticketsHasChannelId) {
    await knex.schema.alterTable('tickets', (table) => {
      table.uuid('channel_id').nullable().alter();
    });
    console.log('  ✓ tickets.channel_id is now nullable');
  }

  const tagDefsExists = await knex.schema.hasTable('tag_definitions');
  if (tagDefsExists) {
    const tagDefsHasChannelId = await knex.schema.hasColumn('tag_definitions', 'channel_id');
    if (tagDefsHasChannelId) {
      await knex.schema.alterTable('tag_definitions', (table) => {
        table.uuid('channel_id').nullable().alter();
      });
      console.log('  ✓ tag_definitions.channel_id is now nullable');
    }
  }


  // Verify boards table exists before proceeding
  const boardsExists = await knex.schema.hasTable('boards');
  if (!boardsExists) {
    console.log('ERROR: boards table does not exist! Aborting cleanup.');
    throw new Error('boards table must exist before running cleanup migration');
  }

  const standardBoardsExists = await knex.schema.hasTable('standard_boards');
  if (!standardBoardsExists) {
    console.log('ERROR: standard_boards table does not exist! Aborting cleanup.');
    throw new Error('standard_boards table must exist before running cleanup migration');
  }

  // Sync any new data that was added between migrations
  const channelsExists = await knex.schema.hasTable('channels');
  if (channelsExists) {
    console.log('Syncing any new data from channels to boards...');

    // Find records in channels that don't exist in boards
    const newRecords = await knex.raw(`
      SELECT c.*
      FROM channels c
      LEFT JOIN boards b ON c.tenant = b.tenant AND c.channel_id = b.board_id
      WHERE b.board_id IS NULL
    `);

    if (newRecords.rows.length > 0) {
      console.log(`  Found ${newRecords.rows.length} new records to sync...`);

      // Insert new records into boards
      await knex.raw(`
        INSERT INTO boards (
          tenant, board_id, board_name, display_contact_name_id, display_priority,
          display_severity, display_urgency, display_impact, display_category,
          display_subcategory, display_assigned_to, display_status, display_due_date,
          is_inactive, is_default, display_itil_impact, display_itil_urgency, category_type,
          priority_type, display_order, description
        )
        SELECT
          c.tenant, c.channel_id, c.channel_name, c.display_contact_name_id, c.display_priority,
          c.display_severity, c.display_urgency, c.display_impact, c.display_category,
          c.display_subcategory, c.display_assigned_to, c.display_status, c.display_due_date,
          c.is_inactive, c.is_default, c.display_itil_impact, c.display_itil_urgency, c.category_type,
          c.priority_type, c.display_order, c.description
        FROM channels c
        LEFT JOIN boards b ON c.tenant = b.tenant AND c.channel_id = b.board_id
        WHERE b.board_id IS NULL
      `);

      console.log(`  ✓ Synced ${newRecords.rows.length} new records`);
    } else {
      console.log('  ✓ All channels data already exists in boards');
    }
  }

  // Sync standard_channels to standard_boards
  const standardChannelsExistsForSync = await knex.schema.hasTable('standard_channels');
  if (standardChannelsExistsForSync) {
    console.log('Syncing any new data from standard_channels to standard_boards...');

    const newStandardRecords = await knex.raw(`
      SELECT sc.*
      FROM standard_channels sc
      LEFT JOIN standard_boards sb ON sc.id = sb.id
      WHERE sb.id IS NULL
    `);

    if (newStandardRecords.rows.length > 0) {
      console.log(`  Found ${newStandardRecords.rows.length} new standard records to sync...`);

      await knex.raw(`
        INSERT INTO standard_boards (
          id, board_name, description, display_order, is_inactive, is_default,
          category_type, priority_type, created_at, updated_at
        )
        SELECT
          sc.id, sc.channel_name, sc.description, sc.display_order, sc.is_inactive, sc.is_default,
          sc.category_type, sc.priority_type, sc.created_at, sc.updated_at
        FROM standard_channels sc
        LEFT JOIN standard_boards sb ON sc.id = sb.id
        WHERE sb.id IS NULL
      `);

      console.log(`  ✓ Synced ${newStandardRecords.rows.length} new standard records`);
    } else {
      console.log('  ✓ All standard_channels data already exists in standard_boards');
    }
  }

  // Step 1: If Citus is enabled, check if we need to undistribute tables
  const citusEnabled = await knex.raw(`
    SELECT EXISTS (
      SELECT 1 FROM pg_extension WHERE extname = 'citus'
    ) as enabled
  `);

  if (citusEnabled.rows[0].enabled) {
    console.log('Citus detected - checking if channels table is distributed...');

    const channelsDistributed = await knex.raw(`
      SELECT EXISTS (
        SELECT 1 FROM pg_dist_partition
        WHERE logicalrelid = 'channels'::regclass
      ) as distributed
    `);

    if (channelsDistributed.rows[0].distributed) {
      console.log('Need to undistribute channels table before dropping...');

      // First, get all foreign key constraints that reference the channels table
      const foreignKeys = await knex.raw(`
        SELECT
          tc.table_name,
          tc.constraint_name
        FROM information_schema.table_constraints tc
        JOIN information_schema.constraint_column_usage AS ccu USING (constraint_schema, constraint_name)
        WHERE tc.constraint_type = 'FOREIGN KEY'
        AND ccu.table_name = 'channels'
      `);

      // Drop all foreign keys that reference channels
      console.log('Dropping foreign key constraints referencing channels...');
      for (const fk of foreignKeys.rows) {
        console.log(`  Dropping constraint ${fk.constraint_name} from ${fk.table_name}...`);
        await knex.raw(`ALTER TABLE ${fk.table_name} DROP CONSTRAINT IF EXISTS ${fk.constraint_name}`);
      }

      // Now we can undistribute the channels table
      await knex.raw(`SELECT undistribute_table('channels')`);
      console.log('  ✓ Channels table undistributed');
    } else {
      // If not distributed, still need to drop the foreign keys
      console.log('Channels table not distributed, dropping foreign key constraints...');

      const categoriesFK = await knex.raw(`
        SELECT conname
        FROM pg_constraint
        WHERE conrelid = 'categories'::regclass
        AND confrelid = 'channels'::regclass
        AND contype = 'f'
      `);

      for (const constraint of categoriesFK.rows) {
        await knex.raw(`ALTER TABLE categories DROP CONSTRAINT IF EXISTS ${constraint.conname}`);
        console.log(`  ✓ Dropped constraint ${constraint.conname} from categories`);
      }

      const ticketsFK = await knex.raw(`
        SELECT conname
        FROM pg_constraint
        WHERE conrelid = 'tickets'::regclass
        AND confrelid = 'channels'::regclass
        AND contype = 'f'
      `);

      for (const constraint of ticketsFK.rows) {
        await knex.raw(`ALTER TABLE tickets DROP CONSTRAINT IF EXISTS ${constraint.conname}`);
        console.log(`  ✓ Dropped constraint ${constraint.conname} from tickets`);
      }

      const hasTagsTable = await knex.schema.hasTable('tags');
      if (hasTagsTable) {
        const hasChannelId = await knex.schema.hasColumn('tags', 'channel_id');
        if (hasChannelId) {
          const tagsFK = await knex.raw(`
            SELECT conname
            FROM pg_constraint
            WHERE conrelid = 'tags'::regclass
            AND confrelid = 'channels'::regclass
            AND contype = 'f'
          `);

          for (const constraint of tagsFK.rows) {
            await knex.raw(`ALTER TABLE tags DROP CONSTRAINT IF EXISTS ${constraint.conname}`);
            console.log(`  ✓ Dropped constraint ${constraint.conname} from tags`);
          }
        }
      }

      const hasTagDefinitions = await knex.schema.hasTable('tag_definitions');
      if (hasTagDefinitions) {
        const hasChannelId = await knex.schema.hasColumn('tag_definitions', 'channel_id');
        if (hasChannelId) {
          const tagDefsFK = await knex.raw(`
            SELECT conname
            FROM pg_constraint
            WHERE conrelid = 'tag_definitions'::regclass
            AND confrelid = 'channels'::regclass
            AND contype = 'f'
          `);

          for (const constraint of tagDefsFK.rows) {
            await knex.raw(`ALTER TABLE tag_definitions DROP CONSTRAINT IF EXISTS ${constraint.conname}`);
            console.log(`  ✓ Dropped constraint ${constraint.conname} from tag_definitions`);
          }
        }
      }
    }

    // Also undistribute standard_channels if it's a reference table
    const standardChannelsExists = await knex.schema.hasTable('standard_channels');
    if (standardChannelsExists) {
      const standardDistributed = await knex.raw(`
        SELECT EXISTS (
          SELECT 1 FROM pg_dist_partition
          WHERE logicalrelid = 'standard_channels'::regclass
        ) as distributed
      `);

      if (standardDistributed.rows[0].distributed) {
        console.log('Undistributing standard_channels table before dropping...');
        await knex.raw(`SELECT undistribute_table('standard_channels')`);
        console.log('  ✓ Standard_channels table undistributed');
      }
    }
  } else {
    // No Citus, just drop the foreign keys normally
    console.log('Dropping foreign key constraints referencing channels...');

    const categoriesFK = await knex.raw(`
      SELECT conname
      FROM pg_constraint
      WHERE conrelid = 'categories'::regclass
      AND confrelid = 'channels'::regclass
      AND contype = 'f'
    `);

    for (const constraint of categoriesFK.rows) {
      await knex.raw(`ALTER TABLE categories DROP CONSTRAINT IF EXISTS ${constraint.conname}`);
      console.log(`  ✓ Dropped constraint ${constraint.conname} from categories`);
    }

    const ticketsFK = await knex.raw(`
      SELECT conname
      FROM pg_constraint
      WHERE conrelid = 'tickets'::regclass
      AND confrelid = 'channels'::regclass
      AND contype = 'f'
    `);

    for (const constraint of ticketsFK.rows) {
      await knex.raw(`ALTER TABLE tickets DROP CONSTRAINT IF EXISTS ${constraint.conname}`);
      console.log(`  ✓ Dropped constraint ${constraint.conname} from tickets`);
    }

    const hasTagsTable = await knex.schema.hasTable('tags');
    if (hasTagsTable) {
      const hasChannelId = await knex.schema.hasColumn('tags', 'channel_id');
      if (hasChannelId) {
        const tagsFK = await knex.raw(`
          SELECT conname
          FROM pg_constraint
          WHERE conrelid = 'tags'::regclass
          AND confrelid = 'channels'::regclass
          AND contype = 'f'
        `);

        for (const constraint of tagsFK.rows) {
          await knex.raw(`ALTER TABLE tags DROP CONSTRAINT IF EXISTS ${constraint.conname}`);
          console.log(`  ✓ Dropped constraint ${constraint.conname} from tags`);
        }
      }
    }

    const hasTagDefinitions = await knex.schema.hasTable('tag_definitions');
    if (hasTagDefinitions) {
      const hasChannelId = await knex.schema.hasColumn('tag_definitions', 'channel_id');
      if (hasChannelId) {
        const tagDefsFK = await knex.raw(`
          SELECT conname
          FROM pg_constraint
          WHERE conrelid = 'tag_definitions'::regclass
          AND confrelid = 'channels'::regclass
          AND contype = 'f'
        `);

        for (const constraint of tagDefsFK.rows) {
          await knex.raw(`ALTER TABLE tag_definitions DROP CONSTRAINT IF EXISTS ${constraint.conname}`);
          console.log(`  ✓ Dropped constraint ${constraint.conname} from tag_definitions`);
        }
      }
    }
  }

  // Step 2: Drop old channel_id columns
  console.log('Dropping old channel_id columns...');

  const hasCategoriesChannelId = await knex.schema.hasColumn('categories', 'channel_id');
  if (hasCategoriesChannelId) {
    await knex.schema.alterTable('categories', (table) => {
      table.dropColumn('channel_id');
    });
    console.log('  ✓ Dropped channel_id from categories');
  }

  const hasTicketsChannelId = await knex.schema.hasColumn('tickets', 'channel_id');
  if (hasTicketsChannelId) {
    await knex.schema.alterTable('tickets', (table) => {
      table.dropColumn('channel_id');
    });
    console.log('  ✓ Dropped channel_id from tickets');
  }

  const hasTagDefinitions = await knex.schema.hasTable('tag_definitions');
  if (hasTagDefinitions) {
    const hasChannelId = await knex.schema.hasColumn('tag_definitions', 'channel_id');
    if (hasChannelId) {
      await knex.schema.alterTable('tag_definitions', (table) => {
        table.dropColumn('channel_id');
      });
      console.log('  ✓ Dropped channel_id from tag_definitions');
    }
  }

  // Step 3: Drop old tables
  console.log('Dropping old tables...');

  if (channelsExists) {
    await knex.schema.dropTable('channels');
    console.log('  ✓ Dropped channels table');
  }

  const standardChannelsExists = await knex.schema.hasTable('standard_channels');
  if (standardChannelsExists) {
    await knex.schema.dropTable('standard_channels');
    console.log('  ✓ Dropped standard_channels table');
  }

  console.log('✓ Cleanup migration completed successfully');
  console.log('Old channels tables and channel_id columns have been removed.');
};

/**
 * Rollback - recreate the old channels tables from boards
 * This allows rolling back if issues are discovered
 * @param { import("knex").Knex } knex
 * @returns { Promise<void> }
 */
exports.down = async function(knex) {
  console.log('Rolling back cleanup migration - recreating channels tables...');

  // Step 1: Recreate channels table
  console.log('Recreating channels table...');
  await knex.schema.createTable('channels', (table) => {
    table.uuid('tenant').notNullable();
    table.uuid('channel_id').defaultTo(knex.raw('gen_random_uuid()')).notNullable();
    table.text('channel_name').notNullable();
    table.boolean('display_contact_name_id').defaultTo(true);
    table.boolean('display_priority').defaultTo(true);
    table.boolean('display_severity').defaultTo(true);
    table.boolean('display_urgency').defaultTo(true);
    table.boolean('display_impact').defaultTo(true);
    table.boolean('display_category').defaultTo(true);
    table.boolean('display_subcategory').defaultTo(true);
    table.boolean('display_assigned_to').defaultTo(true);
    table.boolean('display_status').defaultTo(true);
    table.boolean('display_due_date').defaultTo(true);
    table.boolean('is_default').defaultTo(false);
    table.boolean('display_itil_impact').defaultTo(false);
    table.boolean('display_itil_urgency').defaultTo(false);
    table.text('category_type').defaultTo('custom');
    table.text('priority_type').defaultTo('custom');
    table.integer('display_order').notNullable().defaultTo(0);
    table.text('description');
    table.primary(['tenant', 'channel_id']);
    table.foreign('tenant').references('tenants.tenant');
  });

  await knex.raw(`
    ALTER TABLE channels
    ADD CONSTRAINT channels_category_type_check
    CHECK (category_type IN ('custom', 'itil'))
  `);

  await knex.raw(`
    ALTER TABLE channels
    ADD CONSTRAINT channels_priority_type_check
    CHECK (priority_type IN ('custom', 'itil'))
  `);

  await knex.raw('CREATE INDEX idx_channels_tenant_category_type ON channels(tenant, category_type)');
  await knex.raw('CREATE INDEX idx_channels_tenant_priority_type ON channels(tenant, priority_type)');

  // Step 2: Copy data back from boards to channels
  console.log('Copying data from boards back to channels...');
  await knex.raw(`
    INSERT INTO channels (
      tenant, channel_id, channel_name, display_contact_name_id, display_priority,
      display_severity, display_urgency, display_impact, display_category,
      display_subcategory, display_assigned_to, display_status, display_due_date,
      is_default, display_itil_impact, display_itil_urgency, category_type,
      priority_type, display_order, description
    )
    SELECT
      tenant, board_id, board_name, display_contact_name_id, display_priority,
      display_severity, display_urgency, display_impact, display_category,
      display_subcategory, display_assigned_to, display_status, display_due_date,
      is_default, display_itil_impact, display_itil_urgency, category_type,
      priority_type, display_order, description
    FROM boards
  `);

  // Step 3: Recreate standard_channels table
  console.log('Recreating standard_channels table...');
  await knex.schema.createTable('standard_channels', (table) => {
    table.uuid('id').primary().defaultTo(knex.raw('gen_random_uuid()'));
    table.text('channel_name').notNullable().unique();
    table.text('description');
    table.integer('display_order').notNullable().defaultTo(0);
    table.boolean('is_inactive').defaultTo(false);
    table.boolean('is_default').defaultTo(false);
    table.text('category_type').defaultTo('custom');
    table.text('priority_type').defaultTo('custom');
    table.timestamp('created_at').notNullable().defaultTo(knex.fn.now());
    table.timestamp('updated_at').notNullable().defaultTo(knex.fn.now());
  });

  await knex.raw(`
    ALTER TABLE standard_channels
    ADD CONSTRAINT standard_channels_category_type_check
    CHECK (category_type IN ('custom', 'itil'))
  `);

  await knex.raw(`
    ALTER TABLE standard_channels
    ADD CONSTRAINT standard_channels_priority_type_check
    CHECK (priority_type IN ('custom', 'itil'))
  `);

  await knex.raw('CREATE INDEX idx_standard_channels_category_type ON standard_channels(category_type)');
  await knex.raw('CREATE INDEX idx_standard_channels_priority_type ON standard_channels(priority_type)');

  // Step 4: Copy data back from standard_boards
  console.log('Copying data from standard_boards back to standard_channels...');
  await knex.raw(`
    INSERT INTO standard_channels (
      id, channel_name, description, display_order, is_inactive, is_default,
      category_type, priority_type, created_at, updated_at
    )
    SELECT
      id, board_name, description, display_order, is_inactive, is_default,
      category_type, priority_type, created_at, updated_at
    FROM standard_boards
  `);

  // Step 5: Restore channel_id columns in related tables
  console.log('Restoring channel_id in categories table...');
  await knex.schema.alterTable('categories', (table) => {
    table.uuid('channel_id');
  });

  await knex.raw(`
    UPDATE categories
    SET channel_id = board_id
  `);

  await knex.schema.alterTable('categories', (table) => {
    table.foreign(['tenant', 'channel_id']).references(['tenant', 'channel_id']).inTable('channels');
  });

  console.log('Restoring channel_id in tickets table...');
  await knex.schema.alterTable('tickets', (table) => {
    table.uuid('channel_id');
  });

  await knex.raw(`
    UPDATE tickets
    SET channel_id = board_id
  `);

  await knex.schema.alterTable('tickets', (table) => {
    table.foreign(['tenant', 'channel_id']).references(['tenant', 'channel_id']).inTable('channels');
  });

  const hasTagsTable = await knex.schema.hasTable('tags');
  if (hasTagsTable) {
    const hasBoardId = await knex.schema.hasColumn('tags', 'board_id');
    if (hasBoardId) {
      console.log('Restoring channel_id in tags table...');
      await knex.schema.alterTable('tags', (table) => {
        table.uuid('channel_id');
      });

      await knex.raw(`
        UPDATE tags
        SET channel_id = board_id
      `);

      await knex.schema.alterTable('tags', (table) => {
        table.foreign(['tenant', 'channel_id']).references(['tenant', 'channel_id']).inTable('channels');
      });
    }
  }

  const hasTagDefinitions = await knex.schema.hasTable('tag_definitions');
  if (hasTagDefinitions) {
    const hasBoardId = await knex.schema.hasColumn('tag_definitions', 'board_id');
    if (hasBoardId) {
      console.log('Restoring channel_id in tag_definitions table...');
      await knex.schema.alterTable('tag_definitions', (table) => {
        table.uuid('channel_id');
      });

      await knex.raw(`
        UPDATE tag_definitions
        SET channel_id = board_id
        WHERE board_id IS NOT NULL
      `);

      await knex.schema.alterTable('tag_definitions', (table) => {
        table.foreign(['tenant', 'channel_id']).references(['tenant', 'channel_id']).inTable('channels');
      });
    }
  }

  console.log('✓ Rollback completed successfully');
  console.log('Old channels tables and channel_id columns have been restored.');
};