import { Hono } from "hono"
import { z } from "zod"
import { generateText } from "ai"
import { createOpenAI } from "@ai-sdk/openai"
import { createOpenAICompatible } from "@ai-sdk/openai-compatible"
import * as Log from "@saeeol/core/util/log"
import { lazy } from "@/util/lazy"

const log = Log.create({ service: "game-event" })

// ── Request schemas ──────────────────────────────────────────────

const GameContextSchema = z.object({
  character: z.object({ id: z.string(), name: z.string() }).optional(),
  stats: z.record(z.number()).optional(),
  flags: z.record(z.any()).optional(),
  items: z.record(z.number()).optional(),
  route: z.string().optional(),
  act: z.string().optional(),
  choice_history: z.array(z.string()).optional(),
  bosses_defeated: z.array(z.string()).optional(),
})

const EventSchema = z.object({
  id: z.string().optional(),
  lines: z.array(z.object({ speaker: z.string(), text: z.string() }).passthrough()).optional(),
  choices: z.array(z.any()).optional(),
  nextSceneId: z.string().optional(),
}).passthrough()

const BaseRequestSchema = z.object({
  action: z.enum(["augment", "generate", "reaction"]),
  context: GameContextSchema.optional(),
})

const AugmentRequestSchema = BaseRequestSchema.extend({
  action: z.literal("augment"),
  event: EventSchema,
})

const GenerateRequestSchema = BaseRequestSchema.extend({
  action: z.literal("generate"),
  event_id: z.string(),
  prompt_hint: z.record(z.any()).optional(),
})

const ReactionRequestSchema = BaseRequestSchema.extend({
  action: z.literal("reaction"),
  choice_text: z.string(),
  event: EventSchema,
})

const RequestSchema = z.discriminatedUnion("action", [
  AugmentRequestSchema,
  GenerateRequestSchema,
  ReactionRequestSchema,
])

// ── Prompt builders ──────────────────────────────────────────────

function buildSystemPrompt(hint: Record<string, any> = {}): string {
  const tone = hint.tone ?? "몰입감 있고 생생한 묘사"
  const themes = (hint.themes ?? []).join(", ")
  const escalation = hint.escalation ?? ""

  return `너는 에로틱 판타지 던전 크롤러 게임의 AI 내레이터다.

## 역할
- 플레이어의 행동과 상황에 맞는 생생한 묘사를 생성한다
- 캐릭터의 성격과 현재 상태(HP, 음란도, 개발도, 소지 아이템, 플래그)를 반영한다
- 항상 한국어로 응답한다

## 분위기
- 톤: ${tone}
- 테마: ${themes}
${escalation ? `- 에스컬레이션: ${escalation}` : ""}

## 응답 형식
항상 다음 JSON 스키마로 응답한다. 다른 텍스트는 절대 포함하지 않는다:
{
  "lines": [
    { "speaker": "나레이션", "text": "묘사 텍스트", "ai": true }
  ]
}

## 규칙
- lines는 1~3개
- 각 line의 text는 1~3문장
- 캐릭터 이름은 context에서 가져와서 자연스럽게 사용
- 현재 루트와 act의 분위기를 반영
- 플래그가 있으면 이전 경험을 참조
- 절대 JSON 이외의 텍스트를 출력하지 않는다`
}

function buildAugmentPrompt(event: any, context: any): string {
  const charName = context?.character?.name ?? "모험자"
  const charId = context?.character?.id ?? ""
  const route = context?.route ?? ""
  const flags = context?.flags ?? {}
  const stats = context?.stats ?? {}
  const originalLines = (event?.lines ?? []).map((l: any) => `${l.speaker}: ${l.text}`).join("\n")

  return `이 이벤트에 분위기를 더하는 AI 묘사를 생성하라.

## 현재 상태
- 캐릭터: ${charName} (${charId})
- 루트: ${route}
- HP: ${stats.hp ?? "?"}, 음란도: ${stats.lewdness ?? "?"}, 개발도: ${stats.corruption ?? "?"}
- 플래그: ${JSON.stringify(flags)}

## 원본 이벤트 대사
${originalLines}

## 요청
원본 대사 앞에 들어갈 1~2줄의 분위기 묘사를 생성하라. 캐릭터의 현재 상태와 플래그를 반영하라.`
}

function buildGeneratePrompt(eventId: string, context: any, hint: Record<string, any> = {}): string {
  const charName = context?.character?.name ?? "모험자"
  const route = context?.route ?? ""
  const act = context?.act ?? ""
  const stats = context?.stats ?? {}
  const flags = context?.flags ?? {}
  const items = context?.items ?? {}
  const charHints = hint.character_hints ?? {}
  const charId = context?.character?.id ?? ""
  const charHint = charHints[charId] ?? ""

  return `완전히 새로운 게임 이벤트를 생성하라.

## 이벤트 ID: ${eventId}
## Act: ${act}, Route: ${route}

## 캐릭터
- 이름: ${charName} (${charId})
- 성향: ${charHint}
- HP: ${stats.hp ?? "?"}, 음란도: ${stats.lewdness ?? "?"}, 개발도: ${stats.corruption ?? "?"}

## 소지품
${JSON.stringify(items)}

## 경험한 일들 (플래그)
${JSON.stringify(flags)}

## 요청
다음 JSON 스키마로 완전한 이벤트를 생성하라:
{
  "id": "${eventId}",
  "act": "${act}",
  "lines": [
    { "speaker": "나레이션", "text": "...", "ai": true }
  ],
  "choices": [
    { "text": "선택지 텍스트", "nextSceneId": "다음_이벤트_ID", "conditions": [], "effects": [] }
  ],
  "nextSceneId": "기본_다음_이벤트_ID"
}

2~4줄의 대사와 2~3개의 선택지를 만들어라.`
}

function buildReactionPrompt(choiceText: string, event: any, context: any): string {
  const charName = context?.character?.name ?? "모험자"
  const stats = context?.stats ?? {}

  return `플레이어가 선택을 한 직후의 반응 묘사를 생성하라.

## 선택한 것: "${choiceText}"
## 캐릭터: ${charName}
- HP: ${stats.hp ?? "?"}, 음란도: ${stats.lewdness ?? "?"}

## 요청
선택 직후의 짧은 반응 묘사 1~2줄을 생성하라. 캐릭터의 감정과 상황 변화를 반영하라.

JSON 형식:
{
  "lines": [
    { "speaker": "나레이션", "text": "...", "ai": true }
  ]
}`
}

// ── LLM call ─────────────────────────────────────────────────────

function createProvider() {
  // Use OPENAI_API_KEY or OPENAI_BASE_URL from env
  const apiKey = process.env.OPENAI_API_KEY ?? process.env.ANTHROPIC_API_KEY ?? ""
  const baseURL = process.env.OPENAI_BASE_URL

  if (baseURL) {
    return createOpenAICompatible({ name: "custom", baseURL, apiKey })
  }

  return createOpenAI({ apiKey })
}

async function callLLM(systemPrompt: string, userPrompt: string): Promise<any> {
  const provider = createProvider()
  const modelId = process.env.GAME_LLM_MODEL ?? "gpt-4o-mini"
  const model = provider(modelId)

  const result = await generateText({
    model,
    system: systemPrompt,
    prompt: userPrompt,
    maxTokens: 1024,
    temperature: 0.8,
  })

  // Extract JSON from response (handle markdown code blocks)
  let text = result.text.trim()
  const jsonMatch = text.match(/```(?:json)?\s*([\s\S]*?)```/)
  if (jsonMatch) {
    text = jsonMatch[1].trim()
  }

  return JSON.parse(text)
}

// ── Routes ───────────────────────────────────────────────────────

export const GameEventRoutes = lazy(() =>
  new Hono()
    .post(
      "/event",
      async (c) => {
        const body = await c.req.json()
        const parsed = RequestSchema.safeParse(body)

        if (!parsed.success) {
          return c.json({ error: "Invalid request", details: parsed.error.flatten() }, 400)
        }

        const req = parsed.data
        const context = req.context ?? {}

        try {
          switch (req.action) {
            case "augment": {
              const hint: Record<string, any> = {}
              const systemPrompt = buildSystemPrompt(hint)
              const userPrompt = buildAugmentPrompt(req.event, context)

              const result = await callLLM(systemPrompt, userPrompt)

              // Merge AI lines into original event
              const aiLines: any[] = result.lines ?? []
              const originalLines: any[] = req.event?.lines ?? []
              const merged = {
                ...req.event,
                lines: [...aiLines, ...originalLines],
              }

              log.info("augment event", { id: req.event?.id, aiLines: aiLines.length })
              return c.json(merged)
            }

            case "generate": {
              const hint = req.prompt_hint ?? {}
              const systemPrompt = buildSystemPrompt(hint)
              const userPrompt = buildGeneratePrompt(req.event_id, context, hint)

              const result = await callLLM(systemPrompt, userPrompt)
              log.info("generate event", { id: req.event_id })
              return c.json(result)
            }

            case "reaction": {
              const hint: Record<string, any> = {}
              const systemPrompt = buildSystemPrompt(hint)
              const userPrompt = buildReactionPrompt(req.choice_text, req.event, context)

              const result = await callLLM(systemPrompt, userPrompt)
              log.info("reaction", { choice: req.choice_text })
              return c.json(result)
            }
          }
        } catch (err: any) {
          log.error("game event failed", { error: err.message })
          return c.json({ error: err.message }, 500)
        }
      },
    ),
)
