import { NextRequest, NextResponse } from 'next/server'
import { invitationRepo } from '@/lib/database/repositories/invitation.repo'
import { pollLoginQrStatus } from '@/lib/im/adapters/weixin/weixin-api'
import { provisionUser } from '@/lib/invite/provisioner'
import { getBridgeManager } from '@/lib/im/core/bridge-manager'
import type { WeixinCredentials } from '@/lib/im/adapters/weixin/weixin-types'

/**
 * GET /api/invite/[token]/poll
 * Poll the WeChat iLink QR scan status.
 * Public endpoint — the invite token is the auth.
 *
 * States:
 *   wait       → QR shown, waiting for scan (keep polling)
 *   scanned    → scanned by user, waiting for confirmation (keep polling)
 *   confirmed  → user confirmed on phone → auto-provision → return completed
 *   expired    → QR expired
 */
export async function GET(
  req: NextRequest,
  { params }: { params: Promise<{ token: string }> },
) {
  const { token } = await params

  // Validate invitation
  const invite = invitationRepo.findByInviteToken(token)

  if (!invite) {
    return NextResponse.json({ error: 'Invitation not found' }, { status: 404 })
  }

  // ETag based on updated_at + qrcode_key: cheap to compute, identical
  // between polls that don't change anything, so clients can re-poll every
  // 2s without burning a DB write on the no-op path.
  const etag = `W/"${invite.updatedAt}-${invite.qrcodeKey}"`
  if (req.headers.get('if-none-match') === etag) {
    return new NextResponse(null, { status: 304, headers: { ETag: etag } })
  }

  const withEtag = (body: object, init?: ResponseInit) =>
    NextResponse.json(body, { ...(init ?? {}), headers: { ...(init?.headers ?? {}), ETag: etag } })

  // Only poll if QR was generated
  if (invite.status !== 'qr_generated' && invite.status !== 'bot_scanned') {
    // If already completed, return completed
    if (invite.status === 'completed') {
      return withEtag({ status: 'completed', message: 'Already provisioned' })
    }
    return withEtag({
      status: invite.status,
      message: `Current status: ${invite.status}`
    })
  }

  if (!invite.qrcodeKey) {
    return NextResponse.json({ error: 'No QR code found. Generate QR first.' }, { status: 400 })
  }

  try {
    // Poll iLink for QR status
    const pollResponse = await pollLoginQrStatus(invite.qrcodeKey)

    if (pollResponse.errcode && pollResponse.errcode !== 0) {
      return NextResponse.json({ error: pollResponse.errmsg || 'Poll failed' }, { status: 502 })
    }

    const status = normalizeQrStatus(pollResponse.status)

    switch (status) {
      case 'wait':
        return NextResponse.json({ status: 'wait', message: '等待扫码' })

      case 'scanned':
        // Advance qr_generated → bot_scanned atomically. The status guard
        // lives in the WHERE clause — NOT the in-memory snapshot — so a late
        // poll whose pollLoginQrStatus round-trip outlasted another poll's
        // full confirm→provision run can never regress a row that has already
        // moved on to bot_connected/completed back to bot_scanned.
        invitationRepo.casToBotScanned(invite.id)
        return NextResponse.json({ status: 'scanned', message: '已扫码，请在手机上确认' })

      case 'confirmed': {
        // Bot authorized! Get credentials
        const botToken = pollResponse.bot_token
        const ilinkBotId = pollResponse.ilink_bot_id
        const baseurl = pollResponse.baseurl || 'https://ilinkai.weixin.qq.com'
        const ilinkUserId = pollResponse.ilink_user_id || ''

        if (!botToken || !ilinkBotId) {
          return NextResponse.json({
            error: 'Login confirmed but no bot credentials received'
          }, { status: 502 })
        }

        // ── Concurrency guard (bug fix) ───────────────────────────────
        // This endpoint is polled every ~2s from the invite page, and iLink
        // keeps returning 'confirmed' after the user taps Confirm. Provision
        // (mkdir + 3 inserts + startAdapter) outlasts a single poll cycle, so
        // without a guard two overlapping polls each provision — creating
        // duplicate user dirs / channels / bindings.
        //
        // CAS: atomically flip qr_generated|bot_scanned → bot_connected and
        // stamp the credentials. SQLite serializes writers, so exactly one
        // poll gets changes === 1 and proceeds to provision; every
        // overlapping poll sees changes === 0 and short-circuits.
        const cas = invitationRepo.casToBotConnected(invite.id, {
          botToken,
          ilinkBotId,
          baseurl,
        })

        if (cas.changes === 0) {
          // Another concurrent poll already provisioned this invite (the row
          // has advanced past bot_scanned). Don't provision again.
          return NextResponse.json({
            status: 'completed',
            message: '🎉 配置完成！现在你可以通过微信使用智能体了',
          })
        }

        // Auto-provision: create workspace + register channel
        const credentials = {
          botToken,
          ilinkBotId,
          baseUrl: baseurl,
          cdnBaseUrl: 'https://novac2c.cdn.weixin.qq.com/c2c',
          ilinkUserId,
        } as WeixinCredentials & { ilinkUserId: string }

        const provisionResult = provisionUser(
          invite.projectRoot,
          invite.inviteePhone,
          credentials,
          invite.inviterUserId,
        )

        // Finalize invitation. The `status = 'bot_connected'` guard keeps the
        // state machine monotonic: only the poll that won the CAS above can
        // reach here, but this protects against any future writer flipping
        // the row between the two UPDATEs.
        invitationRepo.casToCompleted(invite.id, {
          createdUserId: provisionResult.userId,
          createdUserDir: provisionResult.userDir,
          createdChannelId: provisionResult.channelId,
        })

        // Start the adapter in this process so the host can begin receiving
        // messages for this new bot. Failure is non-fatal — host restart will
        // re-trigger autoReconnect for any enabled channels.
        try {
          await getBridgeManager().startAdapter(provisionResult.channelId)
          console.log(`[invite] adapter started for channel ${provisionResult.channelId}`)
        } catch (err) {
          console.error(`[invite] startAdapter failed for ${provisionResult.channelId}:`, err)
        }

        return NextResponse.json({
          status: 'completed',
          message: '🎉 配置完成！现在你可以通过微信使用智能体了',
        })
      }

      case 'expired':
        return NextResponse.json({ status: 'expired', message: '二维码已过期，请重新获取' })

      default:
        return NextResponse.json({ status: 'unknown', message: `Unknown status: ${pollResponse.status}` })
    }
  } catch (err) {
    const message = err instanceof Error ? err.message : 'Unknown error'
    console.error('[invite] poll error:', message)
    return NextResponse.json({ error: message }, { status: 502 })
  }
}

function normalizeQrStatus(status?: string | null): string {
  if (status === 'wait') return 'wait'
  if (status === 'scaned' || status === 'scanned') return 'scanned'
  if (status === 'confirmed') return 'confirmed'
  if (status === 'expired') return 'expired'
  return 'unknown'
}
