import { NextRequest, NextResponse } from 'next/server'
import { spawn, ChildProcess } from 'child_process'
import { imChannelRepo } from '@/lib/database/repositories/im-channel.repo'
import { getForgeDataDir } from '@/lib/forge-data'
import { generateQrCodeDataUrl } from '@/lib/qrcode'
import path from 'path'
import fs from 'fs'

// ---------------------------------------------------------------------------
// WeCom CLI initialization via wecom-cli init --noninteractive
//
// Spawns wecom-cli as a child process, captures the QR URL,
// and polls for completion (bot.enc generated).
//
// POST /api/im-channels/:id/wecom-cli
//   { action: 'start' }  → spawn wecom-cli init, return QR URL
//   { action: 'poll' }    → check if init completed
//   { action: 'cancel' }  → kill the background process
// ---------------------------------------------------------------------------

interface InitSession {
  status: 'pending' | 'completed' | 'failed' | 'cancelled'
  configDir: string
  botId: string
  qrUrl?: string
  qrDataUrl?: string
  error?: string
  process?: ChildProcess
  timeout?: ReturnType<typeof setTimeout>
}

// Auto-cleanup sessions after 5 minutes to prevent memory leaks
const SESSION_TIMEOUT_MS = 5 * 60 * 1000

const initSessions = new Map<string, InitSession>()

function getConfigDir(botId: string): string {
  return path.join(getForgeDataDir(), 'wecom-cli', `bot_${botId}`)
}

function cleanupSession(id: string): void {
  const session = initSessions.get(id)
  if (!session) return

  // Clear auto-cleanup timeout
  if (session.timeout) {
    clearTimeout(session.timeout)
    session.timeout = undefined
  }

  // Kill child process if still running
  if (session.process && !session.process.killed) {
    try {
      session.process.kill('SIGTERM')
      // Give it a moment, then SIGKILL
      const proc = session.process
      setTimeout(() => {
        if (proc && !proc.killed) {
          try { proc.kill('SIGKILL') } catch { /* ignore */ }
        }
      }, 3000)
    } catch { /* ignore */ }
  }

  initSessions.delete(id)
}

export async function POST(req: NextRequest, { params }: { params: Promise<{ id: string }> }) {
  const { id } = await params

  let body: { action?: string }
  try { body = await req.json() } catch { return NextResponse.json({ error: 'Invalid JSON' }, { status: 400 }) }

  const action = body.action
  if (action !== 'start' && action !== 'poll' && action !== 'cancel') {
    return NextResponse.json({ error: 'action must be "start", "poll", or "cancel"' }, { status: 400 })
  }

  // Validate channel exists and is wecom
  const channel = imChannelRepo.findById(id)
  if (!channel) return NextResponse.json({ error: 'Channel not found' }, { status: 404 })
  if (channel.type !== 'wecom') {
    return NextResponse.json({ error: 'Channel is not a WeCom channel' }, { status: 400 })
  }

  // Parse current credentials
  let creds: Record<string, string> = {}
  try { creds = JSON.parse(channel.credentials) } catch { /* use empty */ }
  const botId = (creds.bot_id || '').trim()

  try {
    if (action === 'start') {
      if (!botId) {
        return NextResponse.json({ error: 'Please save bot_id and secret first before initializing CLI' }, { status: 400 })
      }

      // Validate botId format to prevent path traversal attacks
      if (!/^[a-zA-Z0-9_]+$/.test(botId)) {
        return NextResponse.json({ error: 'Invalid bot_id format: only alphanumeric characters and underscores are allowed' }, { status: 400 })
      }

      // Clean up any previous session
      cleanupSession(id)

      const configDir = getConfigDir(botId)
      fs.mkdirSync(configDir, { recursive: true })

      // Check if wecom-cli is available
      try {
        const whichRes = await new Promise<string>((resolve, reject) => {
          const proc = spawn('which', ['wecom-cli'], { stdio: ['ignore', 'pipe', 'pipe'] })
          let out = ''
          proc.stdout!.on('data', (d: Buffer) => { out += d.toString() })
          proc.on('close', (code) => {
            if (code === 0) resolve(out.trim())
            else reject(new Error('wecom-cli not found'))
          })
          proc.on('error', reject)
        })
        if (!whichRes) throw new Error('wecom-cli not found')
      } catch {
        return NextResponse.json({ error: 'wecom-cli is not installed. Run: npm install -g @wecom/cli' }, { status: 400 })
      }

      // Spawn wecom-cli init
      const child = spawn('wecom-cli', ['init', '--noninteractive'], {
        env: { ...process.env, WECOM_CLI_CONFIG_DIR: configDir },
        stdio: ['ignore', 'pipe', 'pipe'],
      })

      const session: InitSession = {
        status: 'pending',
        configDir,
        botId,
        process: child,
      }

      // Capture QR URL from stdout/stderr
      const outputHandler = (data: Buffer) => {
        const text = data.toString()
        const match = text.match(/https:\/\/work\.weixin\.qq\.com\/ai\/qc\/gen[^\s]+/)
        if (match && !session.qrUrl) {
          session.qrUrl = match[0]
          session.qrDataUrl = generateQrCodeDataUrl(match[0])
        }
      }

      child.stdout!.on('data', outputHandler)
      child.stderr!.on('data', outputHandler)

      child.on('close', (code) => {
        // Clear timeout since process has exited
        if (session.timeout) {
          clearTimeout(session.timeout)
          session.timeout = undefined
        }

        if (session.status === 'pending') {
          // Check if bot.enc was created
          const botEncPath = path.join(configDir, 'bot.enc')
          if (fs.existsSync(botEncPath)) {
            session.status = 'completed'
          } else if (code !== 0) {
            session.status = 'failed'
            session.error = `wecom-cli init exited with code ${code}`
          } else {
            session.status = 'failed'
            session.error = 'wecom-cli init completed but bot.enc not found'
          }
        }
      })

      child.on('error', (err) => {
        session.status = 'failed'
        session.error = err.message
      })

      initSessions.set(id, session)

      // Set auto-cleanup timeout to prevent memory leaks
      session.timeout = setTimeout(() => {
        console.log(`[WeCom CLI] Session ${id} timed out after ${SESSION_TIMEOUT_MS / 1000}s, cleaning up`)
        cleanupSession(id)
      }, SESSION_TIMEOUT_MS)

      // Wait up to 10s for QR URL to appear
      for (let i = 0; i < 100; i++) {
        if (session.qrUrl) break
        if (session.status === 'failed') break
        await new Promise(resolve => setTimeout(resolve, 100))
      }

      if (!session.qrUrl) {
        if (session.status === 'failed') {
          return NextResponse.json({ error: session.error || 'Failed to start wecom-cli init' }, { status: 500 })
        }
        return NextResponse.json({ error: 'Failed to capture QR URL from wecom-cli' }, { status: 500 })
      }

      return NextResponse.json({
        status: 'pending',
        qrUrl: session.qrUrl,
        qrDataUrl: session.qrDataUrl,
        configDir: session.configDir,
      })
    } else if (action === 'poll') {
      const session = initSessions.get(id)
      if (!session) {
        return NextResponse.json({ status: 'not_started' })
      }

      // Double-check: is bot.enc present?
      const botEncPath = path.join(session.configDir, 'bot.enc')
      if (fs.existsSync(botEncPath) && session.status !== 'completed') {
        session.status = 'completed'
      }

      if (session.status === 'completed') {
        // Clean up the process if still alive
        if (session.process && !session.process.killed) {
          try { session.process.kill('SIGTERM') } catch { /* ignore */ }
        }
        // Clear timeout since session is complete
        if (session.timeout) {
          clearTimeout(session.timeout)
          session.timeout = undefined
        }
        return NextResponse.json({
          status: 'completed',
          configDir: session.configDir,
        })
      }

      return NextResponse.json({
        status: session.status,
        error: session.error,
      })
    } else {
      // action === 'cancel'
      cleanupSession(id)
      return NextResponse.json({ status: 'cancelled' })
    }
  } catch (err) {
    const errorMsg = err instanceof Error ? err.message : 'Unknown error'
    return NextResponse.json({ error: errorMsg }, { status: 500 })
  }
}
