#!/usr/bin/env bun
import { createServer } from 'node:net'
import { execSync, spawn } from 'node:child_process'
import { readdirSync, existsSync, readFileSync } from 'node:fs'
import { join } from 'node:path'
import { createInterface } from 'node:readline'
import { platform } from 'node:os'

// --- Config ---
const VITE_PORT = Number(process.env.PORT ?? 3000)
const VERCEL_PORT = 3000
const MOCK_PORT = 3001
const IS_WIN = platform() === 'win32'

// --- Helpers ---
function ask(question: string): Promise<string> {
  const rl = createInterface({ input: process.stdin, output: process.stdout })
  return new Promise(resolve => {
    rl.question(question, answer => {
      rl.close()
      resolve(answer.trim().toLowerCase())
    })
  })
}

function isPortInUse(port: number): Promise<boolean> {
  return new Promise(resolve => {
    const server = createServer()
    server.once('error', () => resolve(true))
    server.once('listening', () => {
      server.close(() => resolve(false))
    })
    server.listen(port, '127.0.0.1')
  })
}

function getProcessOnPort(port: number): string | null {
  try {
    if (IS_WIN) {
      const out = execSync(`netstat -ano | findstr :${port} | findstr LISTENING`, {
        encoding: 'utf-8',
      }).trim()
      if (!out) return null
      const pid = out.split(/\s+/).pop()
      if (!pid) return null
      const info = execSync(`tasklist /FI "PID eq ${pid}" /FO CSV /NH`, {
        encoding: 'utf-8',
      }).trim()
      const name = info.split(',')[0]?.replace(/"/g, '') ?? 'unknown'
      return `${name} (PID ${pid})`
    }
    const out = execSync(`lsof -ti:${port}`, { encoding: 'utf-8' }).trim()
    if (!out) return null
    const pid = out.split('\n')[0]
    const name = execSync(`ps -p ${pid} -o comm= 2>/dev/null`, { encoding: 'utf-8' }).trim()
    return `${name} (PID ${pid})`
  } catch {
    return null
  }
}

function killPort(port: number): boolean {
  try {
    if (IS_WIN) {
      const out = execSync(`netstat -ano | findstr :${port} | findstr LISTENING`, {
        encoding: 'utf-8',
      }).trim()
      const pid = out.split(/\s+/).pop()
      if (pid) execSync(`taskkill /PID ${pid} /F`, { stdio: 'pipe' })
      return true
    }
    const pids = execSync(`lsof -ti:${port}`, { encoding: 'utf-8' }).trim()
    for (const pid of pids.split('\n').filter(Boolean)) {
      process.kill(Number(pid), 'SIGKILL')
    }
    return true
  } catch {
    return false
  }
}

function hasApiRoutes(): boolean {
  const apiDir = join(process.cwd(), 'api')
  if (!existsSync(apiDir)) return false
  try {
    const files = readdirSync(apiDir, { recursive: true }) as string[]
    return files.some(f => f.endsWith('.ts') || f.endsWith('.js'))
  } catch {
    return false
  }
}

function hasJsonServer(): boolean {
  try {
    const pkg = JSON.parse(readFileSync(join(process.cwd(), 'package.json'), 'utf-8'))
    return !!pkg.devDependencies?.['json-server']
  } catch {
    return false
  }
}

async function resolvePort(port: number, label: string): Promise<number> {
  if (!(await isPortInUse(port))) return port

  const proc = getProcessOnPort(port)
  const who = proc ? ` (${proc})` : ''
  console.log(`\n⚠  Port ${port}${who} is in use (${label})`)

  const answer = await ask('   [k]ill  [c]hange port  [a]bort → ')

  if (answer === 'k') {
    killPort(port)
    console.log(`   Killed process on port ${port}`)
    return port
  }

  if (answer === 'c') {
    const newPort = await ask('   New port: ')
    const parsed = Number(newPort)
    if (Number.isNaN(parsed) || parsed < 1 || parsed > 65535) {
      console.error('   Invalid port. Aborting.')
      process.exit(1)
    }
    return resolvePort(parsed, label)
  }

  console.log('   Aborted.')
  process.exit(0)
}

// --- Main ---
async function main() {
  const useVercel = hasApiRoutes()

  if (useVercel) {
    const port = await resolvePort(VERCEL_PORT, 'vercel dev')
    console.log(`\n▶  Starting vercel dev on port ${port}...\n`)
    const child = spawn('npx', ['vercel', 'dev', '--listen', String(port)], {
      stdio: 'inherit',
      env: { ...process.env },
    })
    child.on('exit', code => process.exit(code ?? 0))
  } else if (hasJsonServer()) {
    const vitePort = await resolvePort(VITE_PORT, 'vite')
    const mockPort = await resolvePort(MOCK_PORT, 'json-server')

    console.log(`\n▶  Starting vite on port ${vitePort} + json-server on port ${mockPort}...\n`)
    const child = spawn(
      'npx',
      ['concurrently', `"vite --port ${vitePort}"`, `"json-server mock/db.json --port ${mockPort}"`],
      { stdio: 'inherit', env: { ...process.env }, shell: true },
    )
    child.on('exit', code => process.exit(code ?? 0))
  } else {
    const vitePort = await resolvePort(VITE_PORT, 'vite')
    console.log(`\n▶  Starting vite on port ${vitePort}...\n`)
    const child = spawn('npx', ['vite', '--port', String(vitePort)], {
      stdio: 'inherit',
      env: { ...process.env },
    })
    child.on('exit', code => process.exit(code ?? 0))
  }
}

main()
