import { NextRequest, NextResponse } from 'next/server'
import { imChannelRepo } from '@/lib/database/repositories/im-channel.repo'
import { generateQrCodeDataUrl } from '@/lib/qrcode'

// ---------------------------------------------------------------------------
// Feishu (Lark) One-Click App Registration API
//
// Uses the SDK's built-in registerApp() (OAuth 2.0 Device Authorization Grant,
// RFC 8628), exactly like DingTalk's Device Flow. No Playwright needed.
//
// POST /api/im-channels/:id/feishu-auth
//   { action: 'start', appName?: string }  →  returns QR data URL
//   { action: 'poll' }                      →  returns current status
//   { action: 'cancel' }                    →  aborts the running registerApp
// ---------------------------------------------------------------------------

interface AuthSession {
  status: 'pending' | 'scanned' | 'confirmed' | 'denied' | 'expired' | 'error'
  appId?: string
  appSecret?: string
  qrDataUrl?: string
  expireIn?: number
  error?: string
  controller?: AbortController
}

// Active auth sessions keyed by channel ID
const authSessions = new Map<string, AuthSession>()

function getSession(id: string): AuthSession | undefined {
  return authSessions.get(id)
}

function setSession(id: string, session: AuthSession): void {
  authSessions.set(id, session)
}

function deleteSession(id: string): void {
  const session = authSessions.get(id)
  if (session?.controller) {
    try { session.controller.abort() } catch { /* ignore */ }
  }
  authSessions.delete(id)
}

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

  let body: { action?: string; appName?: 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 feishu/lark
  const channel = imChannelRepo.findById(id)
  if (!channel) return NextResponse.json({ error: 'Channel not found' }, { status: 404 })
  if (channel.type !== 'feishu') {
    return NextResponse.json({ error: 'Channel is not a Feishu channel' }, { status: 400 })
  }

  try {
    if (action === 'start') {
      // Clean up any previous session
      deleteSession(id)

      // Dynamically import the SDK to avoid loading it on every request
      // eslint-disable-next-line @typescript-eslint/no-require-imports
      const lark = await import('@larksuiteoapi/node-sdk') as typeof import('@larksuiteoapi/node-sdk')

      const controller = new AbortController()
      const session: AuthSession = { status: 'pending' }
      setSession(id, session)

      // Start registerApp in background (don't await — it blocks for 10 min)
      lark.registerApp({
        appPreset: {
          name: body.appName || 'Forge Bot',
        },
        signal: controller.signal,
        onQRCodeReady(info) {
          session.qrDataUrl = generateQrCodeDataUrl(info.url)
          session.expireIn = info.expireIn
        },
        onStatusChange(info) {
          if (info.status === 'polling' || info.status === 'slow_down') {
            session.status = 'pending'
          }
        },
      }).then((result) => {
        session.status = 'confirmed'
        session.appId = result.client_id
        session.appSecret = result.client_secret
      }).catch((err) => {
        const code: string = err?.code ?? ''
        if (code === 'access_denied') {
          session.status = 'denied'
          session.error = err?.description || 'User denied the authorization'
        } else if (code === 'expired_token') {
          session.status = 'expired'
          session.error = 'QR code expired or polling timed out'
        } else if (code === 'abort') {
          session.status = 'expired'
          session.error = 'Cancelled'
        } else {
          session.status = 'error'
          session.error = err?.description || err?.message || 'Unknown error'
        }
      })

      // Wait for QR code to become ready (poll every 500ms, max 30s)
      for (let i = 0; i < 60; i++) {
        if (session.qrDataUrl) break
        await new Promise(resolve => setTimeout(resolve, 500))
      }

      if (!session.qrDataUrl) {
        session.status = 'error'
        session.error = 'Failed to generate QR code'
        return NextResponse.json({ error: 'Failed to generate QR code' }, { status: 500 })
      }

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

      if (session.status === 'confirmed') {
        if (!session.appId || !session.appSecret) {
          return NextResponse.json({ error: 'Auth confirmed but credentials missing' }, { status: 500 })
        }
        return NextResponse.json({
          status: 'confirmed',
          appId: session.appId,
          appSecret: session.appSecret,
        })
      }

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