import 'dotenv/config'
import { Pool } from 'pg'
import { readdir } from 'fs/promises'
import { join, dirname } from 'path'
import { fileURLToPath } from 'url'
import { getDb } from './index.js'
import { logger } from '../lib/logger.js'

const __filename = fileURLToPath(import.meta.url)
const __dirname = dirname(__filename)

export interface Migration {
  up: (db: Pool) => Promise<void>
  down: (db: Pool) => Promise<void>
}

const MIGRATIONS_TABLE = 'schema_migrations'

async function ensureMigrationsTable(db: Pool): Promise<void> {
  await db.query(`
    CREATE TABLE IF NOT EXISTS ${MIGRATIONS_TABLE} (
      version VARCHAR(255) PRIMARY KEY,
      name VARCHAR(255) NOT NULL,
      executed_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
    )
  `)
}

async function getExecutedMigrations(db: Pool): Promise<string[]> {
  await ensureMigrationsTable(db)
  const result = await db.query(`SELECT version FROM ${MIGRATIONS_TABLE} ORDER BY version`)
  return result.rows.map((row) => row.version)
}

async function recordMigration(db: Pool, version: string, name: string): Promise<void> {
  await db.query(
    `INSERT INTO ${MIGRATIONS_TABLE} (version, name) VALUES ($1, $2) ON CONFLICT (version) DO NOTHING`,
    [version, name]
  )
}

async function removeMigration(db: Pool, version: string): Promise<void> {
  await db.query(`DELETE FROM ${MIGRATIONS_TABLE} WHERE version = $1`, [version])
}

async function loadMigration(version: string): Promise<Migration> {
  const migrationPath = join(__dirname, 'migrations', `${version}.ts`)
  try {
    const migration = await import(migrationPath)
    return {
      up: migration.up,
      down: migration.down,
    }
  } catch (error) {
    throw new Error(`Failed to load migration ${version}: ${error}`)
  }
}

async function getAllMigrations(): Promise<string[]> {
  const migrationsDir = join(__dirname, 'migrations')
  const files = await readdir(migrationsDir)
  return files
    .filter((file) => file.endsWith('.ts') && file !== 'index.ts')
    .map((file) => file.replace('.ts', ''))
    .sort()
}

async function migrateUp(db: Pool): Promise<void> {
  await ensureMigrationsTable(db)
  const executed = await getExecutedMigrations(db)
  const allMigrations = await getAllMigrations()
  const pending = allMigrations.filter((m) => !executed.includes(m))

  logger.info(`Found ${pending.length} pending migrations`)

  for (const version of pending) {
    logger.info(`Running migration ${version}...`)
    const migration = await loadMigration(version)
    await migration.up(db)
    const name = version.replace(/^\d+_/, '').replace(/_/g, ' ')
    await recordMigration(db, version, name)
    logger.info(`✓ Migration ${version} completed`)
  }

  if (pending.length === 0) {
    logger.info('No pending migrations')
  }
}

async function migrateDown(db: Pool, targetVersion?: string): Promise<void> {
  await ensureMigrationsTable(db)
  const executed = await getExecutedMigrations(db)

  if (executed.length === 0) {
    logger.info('No migrations to roll back')
    return
  }

  const toRollback = targetVersion
    ? executed.slice(executed.indexOf(targetVersion) + 1).reverse()
    : [executed[executed.length - 1]]

  for (const version of toRollback) {
    logger.info(`Rolling back migration ${version}...`)
    const migration = await loadMigration(version)
    await migration.down(db)
    await removeMigration(db, version)
    logger.info(`✓ Migration ${version} rolled back`)
  }
}

async function showStatus(db: Pool): Promise<void> {
  await ensureMigrationsTable(db)
  const executed = await getExecutedMigrations(db)
  const allMigrations = await getAllMigrations()

  logger.info('\nMigration Status:')
  logger.info('================\n')

  for (const migration of allMigrations) {
    const status = executed.includes(migration) ? '✓' : '✗'
    const name = migration.replace(/^\d+_/, '').replace(/_/g, ' ')
    logger.info(`${status} ${migration} - ${name}`)
  }

  logger.info(`\nTotal: ${executed.length}/${allMigrations.length} executed\n`)
}

async function main(): Promise<void> {
  const command = process.argv[2]
  const db = getDb()

  try {
    switch (command) {
      case 'up':
        await migrateUp(db)
        break
      case 'down':
        const targetVersion = process.argv[3]
        await migrateDown(db, targetVersion)
        break
      case 'status':
        await showStatus(db)
        break
      default:
        logger.info('Usage: npm run db:migrate [up|down|status]')
        logger.info('  up     - Run all pending migrations')
        logger.info('  down   - Roll back the last migration')
        logger.info('  status - Show migration status')
        process.exit(1)
    }
  } catch (error) {
    logger.error('Migration error', { error })
    process.exit(1)
  } finally {
    await db.end()
  }
}

if (import.meta.url === `file://${process.argv[1]}`) {
  main()
}

