import path from 'node:path'
import fs from 'node:fs/promises'
import { fileURLToPath } from 'node:url'
import pg from 'pg'
import { afterAll, beforeAll, describe, expect, it } from 'vitest'
import { runMigrations } from './migrations.js'

const { Client } = pg
const baseDatabaseUrl = process.env.TEST_DATABASE_URL
const suite = baseDatabaseUrl ? describe : describe.skip
const databaseName = `osint_test_${process.pid}_${Date.now()}`
let adminClient: InstanceType<typeof Client>
let testDatabaseUrl = ''

suite('PostgreSQL migrations', () => {
  beforeAll(async () => {
    const adminUrl = new URL(baseDatabaseUrl!)
    adminUrl.pathname = '/postgres'
    adminClient = new Client({ connectionString: adminUrl.toString() })
    await adminClient.connect()
    await adminClient.query(`CREATE DATABASE "${databaseName}"`)
    const testUrl = new URL(baseDatabaseUrl!)
    testUrl.pathname = `/${databaseName}`
    testDatabaseUrl = testUrl.toString()
  })

  afterAll(async () => {
    if (!adminClient) return
    await adminClient.query(`DROP DATABASE IF EXISTS "${databaseName}"`)
    await adminClient.end()
  })

  it('applies every migration transactionally and is idempotent', async () => {
    const migrationsDir = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..', 'migrations')
    const migrationCount = (await fs.readdir(migrationsDir)).filter(name => /^\d+.*\.sql$/.test(name)).length
    const firstRun: string[] = []
    await runMigrations(testDatabaseUrl, migrationsDir, message => firstRun.push(message))
    expect(firstRun.filter(message => message.startsWith('apply '))).toHaveLength(migrationCount)

    const client = new Client({ connectionString: testDatabaseUrl })
    await client.connect()
    const tables = await client.query<{ table_name: string }>(`SELECT table_name FROM information_schema.tables WHERE table_schema = 'osint'`)
    const tableNames = tables.rows.map(row => row.table_name)
    expect(tableNames).toEqual(expect.arrayContaining([
      'boards', 'levels', 'level_templates', 'level_template_versions', 'exhibits', 'folder_exhibits',
      'document_exhibits', 'folder_memberships', 'exhibit_connections', 'metadata_fields', 'assets', 'schema_migrations',
      'party_exhibits', 'person_parties', 'organization_parties', 'brief_concepts', 'level_briefs',
      'board_views', 'timeline_views',
      'mysteries', 'npcs', 'npc_poses', 'playthroughs',
      'story_nodes', 'story_node_terminals', 'utterances',
      'level_flags', 'document_flag_requirements', 'level_seen_documents', 'achievements',
      'asset_text_extractions', 'evidence_match_rules', 'evidence_match_anchors',
      'evidence_match_evaluations', 'evidence_match_anchor_evaluations',
      'level_goals', 'level_goal_flag_requirements',
      'evidence_semantic_rules', 'evidence_semantic_evaluations',
      'claim_exhibits', 'exhibit_citations', 'case_reports', 'case_report_submissions', 'case_report_submission_issues',
      'document_capture_kinds',
    ]))
    expect(tableNames).not.toEqual(expect.arrayContaining(['cases', 'widgets', 'widget_relations', 'cutscenes', 'dialogue_steps', 'mystery_chapters', 'seen_dialogue']))
    const ledger = await client.query<{ count: string }>('SELECT COUNT(*)::text AS count FROM osint.schema_migrations')
    expect(ledger.rows[0].count).toBe(String(migrationCount))
    const connectionColumns = await client.query<{ column_name: string }>(`SELECT column_name FROM information_schema.columns WHERE table_schema='osint' AND table_name='exhibit_connections'`)
    expect(connectionColumns.rows.map(row => row.column_name)).toEqual(expect.arrayContaining(['label', 'tightness', 'tag_style', 'tag_position_percent', 'tag_lateral_offset']))
    const eventOccurrence = await client.query<{ is_nullable: string }>(`SELECT is_nullable FROM information_schema.columns WHERE table_schema='osint' AND table_name='event_exhibits' AND column_name='occurred_at'`)
    expect(eventOccurrence.rows[0].is_nullable).toBe('YES')
    await client.end()

    const secondRun: string[] = []
    await runMigrations(testDatabaseUrl, migrationsDir, message => secondRun.push(message))
    expect(secondRun.filter(message => message.startsWith('skip  '))).toHaveLength(migrationCount)
    expect(secondRun.some(message => message.startsWith('apply '))).toBe(false)
  })
})
