import type { App } from '@types'
import { generateTemplates, type TemplateType } from '%templates'
import color from 'kleur'
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'fs'
import { basename, join } from 'path'
import { buildAppUrl } from '@urls'
import { del, get, getManifest, gitUrl, HOST, post } from '../http'
import { confirm, prompt } from '../prompts'
import { resolveAppName } from '../name'

export const STATE_ICONS: Record<string, string> = {
  error: color.red('●'),
  running: color.green('●'),
  starting: color.yellow('◎'),
  stopped: color.gray('◯'),
  invalid: color.red('◌'),
}

const sleep = (ms: number) => new Promise(resolve => setTimeout(resolve, ms))

async function waitForState(name: string, target: string, timeout: number): Promise<string | undefined> {
  const start = Date.now()
  while (Date.now() - start < timeout) {
    await sleep(500)
    const app: App | undefined = await get(`/api/apps/${name}`)
    if (!app) return undefined
    if (app.state === target) return target
    // Terminal failure states — stop polling
    if (target === 'running' && (app.state === 'stopped' || app.state === 'invalid' || app.state === 'error')) return app.state
    if (target === 'stopped' && (app.state === 'invalid' || app.state === 'error')) return app.state
  }
  // Timed out — return last known state
  const app: App | undefined = await get(`/api/apps/${name}`)
  return app?.state
}

export async function infoApp(arg?: string) {
  const name = resolveAppName(arg)
  if (!name) return

  const app: App | undefined = await get(`/api/apps/${name}`)
  if (!app) {
    console.error(`App not found: ${name}`)
    return
  }

  const icon = STATE_ICONS[app.state] ?? '◯'
  console.log(`${icon} ${color.bold(app.name)} ${app.tool ? '[tool]' : ''}`)
  console.log(`  State:   ${app.state}`)
  if (app.state === 'running') {
    console.log(`  URL:     ${buildAppUrl(app.name, HOST)}`)
  }
  if (app.port) {
    console.log(`  Port:    ${app.port}`)
  }
  if (app.tunnelUrl) {
    console.log(`  Tunnel:  ${app.tunnelUrl}`)
  }
  if (app.pid) {
    console.log(`  PID:     ${app.pid}`)
  }
  if (app.started) {
    const uptime = Date.now() - app.started
    const seconds = Math.floor(uptime / 1000) % 60
    const minutes = Math.floor(uptime / 60000) % 60
    const hours = Math.floor(uptime / 3600000)
    const parts = []
    if (hours) parts.push(`${hours}h`)
    if (minutes) parts.push(`${minutes}m`)
    parts.push(`${seconds}s`)
    console.log(`  Uptime:  ${parts.join(' ')}`)
  }
  if (app.error) console.log(`  Error:   ${color.red(app.error)}`)
}

interface ListAppsOptions {
  apps?: boolean
  tools?: boolean
}

export async function listApps(options: ListAppsOptions) {
  const allApps: App[] | undefined = await get('/api/apps')
  if (!allApps) return

  if (options.apps || options.tools) {
    const filtered = allApps.filter((app) => {
      if (options.tools) return app.tool
      return !app.tool
    })

    for (const app of filtered) {
      console.log(`${STATE_ICONS[app.state] ?? '◯'} ${app.name}`)
    }
  } else {
    const apps = allApps.filter((app) => !app.tool)
    const tools = allApps.filter((app) => app.tool)

    if (tools.length === 0) {
      // No tools, just list apps without header/indent
      for (const app of apps) {
        console.log(`${STATE_ICONS[app.state] ?? '◯'} ${app.name}`)
      }
    } else {
      if (apps.length > 0) {
        console.log('apps:')
        for (const app of apps) {
          console.log(`  ${STATE_ICONS[app.state] ?? '◯'} ${app.name}`)
        }
        console.log()
      }
      console.log('tools:')
      for (const tool of tools) {
        console.log(`  ${STATE_ICONS[tool.state] ?? '◯'} ${tool.name}`)
      }
    }
  }
}

interface NewAppOptions {
  ssr?: boolean
  bare?: boolean
  spa?: boolean
}

export async function newApp(name: string | undefined, options: NewAppOptions) {
  const appPath = name ? join(process.cwd(), name) : process.cwd()
  const appName = name ?? basename(process.cwd())

  // Determine template type from flags
  let template: TemplateType = 'ssr'
  if (options.bare) template = 'bare'
  else if (options.spa) template = 'spa'

  const pkgPath = join(appPath, 'package.json')

  // If package.json exists, ensure it has scripts.toes and bail
  if (existsSync(pkgPath)) {
    const pkg = JSON.parse(readFileSync(pkgPath, 'utf-8'))
    if (!pkg.scripts?.toes) {
      pkg.scripts = pkg.scripts ?? {}
      pkg.scripts.toes = 'bun start'
      writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + '\n')
      console.log(color.green('✓ Added scripts.toes to package.json'))
    }
    return
  }

  if (name && existsSync(appPath)) {
    console.error(`Directory already exists: ${name}`)
    return
  }

  const filesToCheck = ['index.tsx', 'tsconfig.json']
  const existing = filesToCheck.filter((f) => existsSync(join(appPath, f)))
  if (existing.length > 0) {
    console.error(`Files already exist: ${existing.join(', ')}`)
    return
  }

  const templateLabel = template === 'ssr' ? '' : ` (${template})`
  const ok = await confirm(`Create ${color.bold(appName)}${templateLabel} in ${appPath}?`)
  if (!ok) return

  const templates = generateTemplates(appName, template)

  // Create directories for all template files
  for (const filename of Object.keys(templates)) {
    const dir = join(appPath, filename, '..')
    mkdirSync(dir, { recursive: true })
  }

  for (const [filename, content] of Object.entries(templates)) {
    writeFileSync(join(appPath, filename), content)
  }

  // Initialize git repo and push to server (git push creates the app via the git tool)
  const run = (cmd: string[]) => Bun.spawn(cmd, { cwd: appPath, stdout: 'ignore', stderr: 'ignore' }).exited

  await run(['git', 'init'])
  await run(['git', 'add', '.'])
  await run(['git', 'commit', '-m', 'init'])
  await run(['git', 'remote', 'add', 'toes', gitUrl(appName)])
  await run(['git', 'push', 'toes', 'main'])

  console.log(color.green(`✓ Created ${appName}`))
  if (name) {
    console.log(`\n  cd ${name}`)
  }
}

export async function getApp(name: string, directory?: string) {
  const target = directory ?? name

  if (existsSync(target)) {
    console.error(`Directory already exists: ${target}`)
    return
  }

  const url = gitUrl(name)
  const args = ['git', 'clone', url]
  if (directory) args.push(directory)
  const proc = Bun.spawn(args, { stdout: 'inherit', stderr: 'inherit' })
  const exitCode = await proc.exited

  if (exitCode !== 0) {
    console.error(color.red(`Failed to clone ${name}`))
    return
  }

  console.log(color.green(`✓ Cloned ${name}`))
  console.log(`\n  cd ${target}\n  bun install`)
}

export async function openApp(arg?: string) {
  const name = resolveAppName(arg)
  if (!name) return

  const app: App | undefined = await get(`/api/apps/${name}`)
  if (!app) {
    console.error(`App not found: ${name}`)
    return
  }
  if (app.state !== 'running') {
    console.error(`App is not running: ${name}`)
    return
  }
  const url = buildAppUrl(app.name, HOST)
  console.log(`Opening ${url}`)
  Bun.spawn(['open', url])
}

export async function shareApp(arg?: string) {
  const name = resolveAppName(arg)
  if (!name) return
  const result = await post<{ ok: boolean, error?: string }>(`/api/apps/${name}/tunnel`)
  if (!result) return
  if (!result.ok) {
    console.error(color.red(result.error ?? 'Failed to share'))
    return
  }
  process.stdout.write(`${color.cyan('↗')} Sharing ${color.bold(name)}...`)
  // Poll until tunnelUrl appears
  const start = Date.now()
  while (Date.now() - start < 15000) {
    await sleep(500)
    const app: App | undefined = await get(`/api/apps/${name}`)
    if (app?.tunnelUrl) {
      console.log(` ${color.cyan(app.tunnelUrl)}`)
      return
    }
  }
  console.log(` ${color.yellow('enabled (URL pending)')}`)
}

export async function renameApp(arg: string | undefined, newName: string) {
  const name = resolveAppName(arg)
  if (!name) return

  const result = await getManifest(name)
  if (result === null) return
  if (!result.exists) {
    console.error(`App not found on server: ${name}`)
    return
  }

  const expected = `sudo rename ${name} ${newName}`
  console.log(`This will rename ${color.bold(name)} to ${color.bold(newName)}.`)
  const answer = await prompt(`Type "${expected}" to confirm: `)

  if (answer !== expected) {
    console.log('Aborted.')
    return
  }

  const response = await post<{ ok: boolean, error?: string, name?: string }>(`/api/apps/${name}/rename`, { name: newName })
  if (!response) return

  if (!response.ok) {
    console.error(color.red(`Error: ${response.error}`))
    return
  }

  console.log(color.green(`✓ Renamed ${name} to ${response.name}`))
}

export async function restartApp(arg?: string) {
  const name = resolveAppName(arg)
  if (!name) return
  const result = await post(`/api/apps/${name}/restart`)
  if (!result) return
  process.stdout.write(`${color.yellow('↻')} Restarting ${color.bold(name)}...`)
  const state = await waitForState(name, 'running', 15000)
  if (state === 'running') {
    console.log(` ${color.green('running')}`)
  } else {
    console.log(` ${color.red(state ?? 'unknown')}`)
  }
}

export async function rmApp(arg?: string) {
  const name = resolveAppName(arg)
  if (!name) return

  const result = await getManifest(name)
  if (result === null) return
  if (!result.exists) {
    console.error(`App not found on server: ${name}`)
    return
  }

  const expected = `sudo rm ${name}`
  console.log(`This will ${color.red('permanently delete')} ${color.bold(name)} from the server.`)
  const answer = await prompt(`Type "${expected}" to confirm: `)

  if (answer !== expected) {
    console.log('Aborted.')
    return
  }

  const success = await del(`/api/sync/apps/${name}`)
  if (success) {
    console.log(color.green(`✓ Removed ${name}`))
  }
}

export async function startApp(arg?: string) {
  const name = resolveAppName(arg)
  if (!name) return
  const result = await post(`/api/apps/${name}/start`)
  if (!result) return
  process.stdout.write(`${color.green('▶')} Starting ${color.bold(name)}...`)
  const state = await waitForState(name, 'running', 15000)
  if (state === 'running') {
    console.log(` ${color.green('running')}`)
  } else {
    console.log(` ${color.red(state ?? 'unknown')}`)
  }
}

export async function unshareApp(arg?: string) {
  const name = resolveAppName(arg)
  if (!name) return
  const result = await del(`/api/apps/${name}/tunnel`)
  if (!result) return
  console.log(`${color.gray('↗')} Unshared ${color.bold(name)}`)
}

export async function stopApp(arg?: string) {
  const name = resolveAppName(arg)
  if (!name) return
  const result = await post(`/api/apps/${name}/stop`)
  if (!result) return
  process.stdout.write(`${color.red('■')} Stopping ${color.bold(name)}...`)
  const state = await waitForState(name, 'stopped', 10000)
  if (state === 'stopped') {
    console.log(` ${color.gray('stopped')}`)
  } else {
    console.log(` ${color.yellow(state ?? 'unknown')}`)
  }
}

