import { spawn, type Subprocess } from 'bun'
import postgres from 'postgres'
import { printHeader, printSummary, type TestResult } from './lib/reporter.js'
import { getSuites, runSuite } from './lib/context.js'
import { createClient } from './lib/client.js'

// --- Config ---
// Use DATABASE_URL from env if available, otherwise construct from individual vars
const DB_HOST = process.env.DB_HOST ?? 'localhost'
const DB_PORT = Number(process.env.DB_PORT ?? '5432')
const DB_USER = process.env.DB_USER ?? 'lunarfront'
const DB_PASS = process.env.DB_PASS ?? 'lunarfront'
const TEST_DB = 'lunarfront_api_test'
const DB_URL = process.env.DATABASE_URL ?? `postgresql://${DB_USER}:${DB_PASS}@${DB_HOST}:${DB_PORT}/${TEST_DB}`
const USE_EXTERNAL_DB = !!process.env.DATABASE_URL
const TEST_PORT = 8001
const BASE_URL = `http://localhost:${TEST_PORT}`
const COMPANY_ID = '10000000-1000-4000-8000-000000000001'
const LOCATION_ID = '10000000-1000-4000-8000-000000000002'

// --- Parse CLI args ---
const args = process.argv.slice(2)
let filterSuite: string | undefined
let filterTags: string[] | undefined

for (let i = 0; i < args.length; i++) {
  if (args[i] === '--suite' && args[i + 1]) filterSuite = args[++i]
  if (args[i] === '--tag' && args[i + 1]) filterTags = args[++i].split(',')
}

// --- DB setup ---
async function setupDatabase() {
  if (!USE_EXTERNAL_DB) {
    // Local: create test DB if needed
    const adminSql = postgres(`postgresql://${DB_USER}:${DB_PASS}@${DB_HOST}:${DB_PORT}/postgres`)
    const [exists] = await adminSql`SELECT 1 FROM pg_database WHERE datname = ${TEST_DB}`
    if (!exists) {
      await adminSql.unsafe(`CREATE DATABASE ${TEST_DB}`)
      console.log(`  Created database ${TEST_DB}`)
    }
    await adminSql.end()
  }

  // Run migrations
  const { execSync } = await import('child_process')
  execSync(`bunx drizzle-kit migrate`, {
    cwd: new URL('..', import.meta.url).pathname,
    env: {
      ...process.env,
      DATABASE_URL: DB_URL,
    },
    stdio: 'pipe',
  })

  // Truncate all tables
  const testSql = postgres(DB_URL)
  await testSql.unsafe(`
    DO $$ DECLARE r RECORD;
    BEGIN
      SET client_min_messages TO WARNING;
      FOR r IN (SELECT tablename FROM pg_tables WHERE schemaname = 'public') LOOP
        EXECUTE 'TRUNCATE TABLE ' || quote_ident(r.tablename) || ' CASCADE';
      END LOOP;
      RESET client_min_messages;
    END $$
  `)

  // Seed company + location (company table stays as store settings)
  await testSql`INSERT INTO company (id, name, timezone) VALUES (${COMPANY_ID}, 'Test Store', 'America/Chicago')`
  await testSql`INSERT INTO location (id, name, tax_rate, service_tax_rate) VALUES (${LOCATION_ID}, 'Test Location', '0.0825', '0.0500')`
  await testSql`INSERT INTO location (id, name, tax_rate, service_tax_rate, cash_rounding) VALUES ('10000000-1000-4000-8000-000000000003', 'Rounding Location', '0.0825', '0.0500', true)`

  // Seed lookup tables
  const { SYSTEM_UNIT_STATUSES, SYSTEM_ITEM_CONDITIONS } = await import('../src/db/schema/lookups.js')
  for (const s of SYSTEM_UNIT_STATUSES) {
    await testSql`INSERT INTO inventory_unit_status (name, slug, description, is_system, sort_order) VALUES (${s.name}, ${s.slug}, ${s.description}, true, ${s.sortOrder})`
  }
  for (const c of SYSTEM_ITEM_CONDITIONS) {
    await testSql`INSERT INTO item_condition (name, slug, description, is_system, sort_order) VALUES (${c.name}, ${c.slug}, ${c.description}, true, ${c.sortOrder})`
  }

  // Seed RBAC permissions and default roles
  const { SYSTEM_PERMISSIONS, DEFAULT_ROLES } = await import('../src/db/seeds/rbac.js')
  for (const p of SYSTEM_PERMISSIONS) {
    await testSql`INSERT INTO permission (slug, domain, action, description) VALUES (${p.slug}, ${p.domain}, ${p.action}, ${p.description}) ON CONFLICT (slug) DO NOTHING`
  }

  const permRows = await testSql`SELECT id, slug FROM permission`
  const permMap = new Map(permRows.map((r: any) => [r.slug, r.id]))

  for (const roleDef of DEFAULT_ROLES) {
    const [role] = await testSql`INSERT INTO role (name, slug, description, is_system) VALUES (${roleDef.name}, ${roleDef.slug}, ${roleDef.description}, true) RETURNING id`
    for (const permSlug of roleDef.permissions) {
      const permId = permMap.get(permSlug)
      if (permId) {
        await testSql`INSERT INTO role_permission (role_id, permission_id) VALUES (${role.id}, ${permId})`
      }
    }
  }

  // Seed default modules
  const DEFAULT_MODULES = [
    { slug: 'inventory', name: 'Inventory', description: 'Product catalog, stock tracking, and unit management', enabled: true },
    { slug: 'pos', name: 'Point of Sale', description: 'Sales transactions, cash drawer, and receipts', enabled: true },
    { slug: 'repairs', name: 'Repairs', description: 'Repair ticket management, batches, and service templates', enabled: true },
    { slug: 'rentals', name: 'Rentals', description: 'Rental agreements and billing', enabled: false },
    { slug: 'lessons', name: 'Lessons', description: 'Scheduling, staff management, and billing', enabled: true },
    { slug: 'files', name: 'Files', description: 'Shared file storage with folder organization', enabled: true },
    { slug: 'vault', name: 'Vault', description: 'Encrypted password and secret manager', enabled: true },
    { slug: 'email', name: 'Email', description: 'Email campaigns, templates, and sending', enabled: false },
    { slug: 'reports', name: 'Reports', description: 'Business reports and data export', enabled: true },
  ]
  for (const m of DEFAULT_MODULES) {
    await testSql`INSERT INTO module_config (slug, name, description, licensed, enabled) VALUES (${m.slug}, ${m.name}, ${m.description}, true, ${m.enabled}) ON CONFLICT (slug) DO NOTHING`
  }

  // Seed test email provider so email endpoints work without real provider
  await testSql`INSERT INTO app_settings (key, value, is_encrypted) VALUES ('email.provider', 'test', false) ON CONFLICT (key) DO NOTHING`
  await testSql`INSERT INTO app_settings (key, value, is_encrypted) VALUES ('email.from_address', 'Test Store <noreply@test.com>', false) ON CONFLICT (key) DO NOTHING`

  await testSql.end()
  console.log('  Database ready')
}

// --- Start backend ---
async function killPort(port: number) {
  try {
    const { execSync } = await import('child_process')
    // Try lsof first, fall back to fuser
    try {
      execSync(`lsof -ti:${port} | xargs kill -9 2>/dev/null || true`, { stdio: 'pipe' })
    } catch {
      execSync(`fuser -k ${port}/tcp 2>/dev/null || true`, { stdio: 'pipe' })
    }
    await new Promise((r) => setTimeout(r, 1000))
  } catch {}
}

async function startBackend(): Promise<Subprocess> {
  await killPort(TEST_PORT)
  const proc = spawn({
    cmd: ['bun', 'run', 'src/main.ts'],
    cwd: new URL('..', import.meta.url).pathname,
    env: {
      ...process.env,
      DATABASE_URL: DB_URL,
      REDIS_URL: process.env.REDIS_URL ?? 'redis://localhost:6379',
      JWT_SECRET: 'test-secret-for-api-tests',
      PORT: String(TEST_PORT),
      HOST: '0.0.0.0',
      NODE_ENV: 'development',
      LOG_LEVEL: 'error',
      STORAGE_LOCAL_PATH: '/tmp/lunarfront-test-files',
    },
    stdout: 'pipe',
    stderr: 'pipe',
  })

  // Wait for health check
  for (let i = 0; i < 30; i++) {
    try {
      const res = await fetch(`${BASE_URL}/v1/health`)
      if (res.ok) {
        console.log(`  Backend running on port ${TEST_PORT}`)
        return proc
      }
    } catch {}
    await new Promise((r) => setTimeout(r, 500))
  }

  // Check if process died
  const exitCode = proc.exitCode
  if (exitCode !== null) {
    console.error(`  Backend exited with code ${exitCode}`)
  }
  proc.kill()
  throw new Error('Backend failed to start within 15 seconds')
}

// --- Register test user ---
async function registerTestUser(): Promise<string> {
  const testPassword = 'testpassword1234'

  const headers = { 'Content-Type': 'application/json' }
  const registerRes = await fetch(`${BASE_URL}/v1/auth/register`, {
    method: 'POST',
    headers,
    body: JSON.stringify({
      email: 'test@lunarfront.dev',
      password: testPassword,
      firstName: 'Test',
      lastName: 'Runner',
      role: 'admin',
    }),
  })
  const registerData = await registerRes.json() as { token?: string; user?: { id: string } }

  // Assign admin role to the user via direct SQL
  if (registerRes.status === 201 && registerData.user) {
    const assignSql = postgres(DB_URL)
    const [adminRole] = await assignSql`SELECT id FROM role WHERE slug = 'admin' LIMIT 1`
    if (adminRole) {
      await assignSql`INSERT INTO user_role_assignment (user_id, role_id) VALUES (${registerData.user.id}, ${adminRole.id}) ON CONFLICT DO NOTHING`
    }
    await assignSql.end()
  }

  // Login to get token with permissions loaded
  const loginRes = await fetch(`${BASE_URL}/v1/auth/login`, {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ email: 'test@lunarfront.dev', password: testPassword }),
  })
  const loginData = await loginRes.json() as { token?: string }
  if (loginRes.status !== 200 || !loginData.token) throw new Error(`Auth failed: ${JSON.stringify(loginData)}`)
  return loginData.token
}

// --- Import suites ---
async function loadSuites() {
  const { readdirSync } = await import('fs')
  const suitesDir = new URL('./suites/', import.meta.url).pathname
  const files = readdirSync(suitesDir).filter((f: string) => f.endsWith('.ts'))
  for (const file of files) {
    await import(`./suites/${file}`)
  }
}

// --- Main ---
async function main() {
  let backend: Subprocess | undefined
  try {
    console.log('\n  Setting up...')
    await setupDatabase()
    backend = await startBackend()

    const token = await registerTestUser()
    console.log('  Authenticated\n')

    await loadSuites()

    const suites = getSuites().filter((s) => !filterSuite || s.name.toLowerCase().includes(filterSuite.toLowerCase()))

    printHeader(BASE_URL)

    const allResults: TestResult[] = []
    const startTime = performance.now()

    for (const s of suites) {
      const results = await runSuite(s, BASE_URL, token, filterTags)
      allResults.push(...results)
    }

    const totalDuration = performance.now() - startTime
    printSummary(allResults, totalDuration)

    const failed = allResults.some((r) => !r.passed)
    process.exit(failed ? 1 : 0)
  } catch (err) {
    console.error('\n  Fatal error:', err)
    process.exit(1)
  } finally {
    if (backend) backend.kill()
  }
}

main()
