import fs from "fs/promises"
import path from "path"
import { cmd } from "./cmd"
import * as prompts from "@clack/prompts"
import { UI } from "../ui"
import { Global } from "@saeeol/core/global"
import { Instance } from "../../project/instance"
import { AppRuntime } from "../../effect/app-runtime"
import { Config } from "@/config/config"
import { Effect } from "effect"
import { put } from "./providers-auth"
import { ModelsDev } from "@/provider/models"
import { map, pipe, sortBy, values } from "remeda"

const { TEXT_HIGHLIGHT: H, TEXT_NORMAL: N, TEXT_DIM: D, TEXT_SUCCESS: S, TEXT_NORMAL_BOLD: B } = UI.Style

const getModels = () => AppRuntime.runPromise(ModelsDev.Service.use((s) => s.get()))
const refreshModels = () => AppRuntime.runPromise(ModelsDev.Service.use((s) => s.refresh(true)))

type ProviderInfo = { id: string; name: string; env: string[]; models: Record<string, unknown> }

export const InitCommand = cmd({
  command: "init",
  describe: "최초 설정 — provider, API key, 모델 선택",
  builder: (y) =>
    y
      .option("provider", {
        type: "string",
        alias: "p",
        describe: "provider id (예: anthropic, openai, google)",
      })
      .option("api-key", {
        type: "string",
        alias: "k",
        describe: "API key",
      })
      .option("model", {
        type: "string",
        alias: "m",
        describe: "기본 모델 (예: anthropic/claude-sonnet-4)",
      })
      .option("base-url", {
        type: "string",
        describe: "커스텀 API base URL (OpenAI 호환 엔드포인트용)",
      }),
  handler: async (args) => {
    await Instance.provide({
      directory: process.cwd(),
      async fn() {
        UI.empty()
        UI.println(logo())
        UI.empty()
        prompts.intro(`${B}SAEEOL 초기 설정${N}`)
        UI.empty()

        const config = await AppRuntime.runPromise(Config.Service.use((cfg) => cfg.get()))

        // 1. Provider 목록 동적 로드
        const s = prompts.spinner()
        s.start("provider 목록 로드 중...")
        await refreshModels().catch(() => {})
        const database = await getModels()
        s.stop(`${Object.keys(database).length}개 provider 로드 완료`)

        // 2. Provider 선택
        const disabled = new Set(config.disabled_providers ?? [])
        const enabled = config.enabled_providers ? new Set(config.enabled_providers) : undefined
        const providers = Object.entries(database)
          .filter(([key]) => (enabled ? enabled.has(key) : true) && !disabled.has(key))
          .map(([id, info]) => ({ id, name: info.name || id, env: info.env, models: info.models || {} } as ProviderInfo))

        const priority: Record<string, number> = {
          anthropic: 0, openai: 1, google: 2, "github-copilot": 3, openrouter: 4, groq: 5,
          "amazon-bedrock": 6, azure: 7, mistral: 8, deepinfra: 9, xai: 10, cerebras: 11,
        }

        let providerId: string
        if (args.provider) {
          providerId = args.provider
          const found = providers.find((p) => p.id === providerId)
          if (!found) {
            // 커스텀 provider — base-url 필요
            if (!args["base-url"]) {
              prompts.log.warn(`"${providerId}"은(는) 알려진 provider가 아닙니다. --base-url이 필요합니다.`)
              return
            }
          }
        } else {
          const sorted = pipe(
            providers,
            sortBy((x) => priority[x.id] ?? 99, (x) => x.name),
          )
          const selected = await prompts.select({
            message: "사용할 AI Provider를 선택하세요",
            maxItems: 12,
            options: [
              ...sorted.map((p) => ({ label: p.name, value: p.id })),
              { label: "직접 입력 (OpenAI 호환)", value: "__custom__" },
            ],
          })
          if (prompts.isCancel(selected)) throw new UI.CancelledError()
          providerId = selected as string
        }

        // 3. 커스텀 엔드포인트
        if (providerId === "__custom__") {
          await handleCustom(args, database)
          return
        }

        // 4. API Key 입력
        const providerInfo = providers.find((p) => p.id === providerId)
        const envVar = providerInfo?.env?.[0]
        const existingKey = envVar ? process.env[envVar] : undefined

        if (existingKey) {
          prompts.log.info(`${D}${envVar} 환경변수가 이미 설정되어 있습니다.${N}`)
        } else if (providerId === "github-copilot") {
          prompts.log.info("GitHub Copilot은 OAuth 로그인이 필요합니다.")
          prompts.log.info(`  ${H}saeeol auth login -p github-copilot${N} 으로 로그인하세요.`)
        } else {
          const hint = providerHint(providerId)
          if (hint) prompts.log.info(`${D}API key 발급: ${hint}${N}`)

          const key = args["api-key"] ?? (await prompts.password({
            message: `${providerInfo?.name ?? providerId} API Key`,
            validate: (x) => (x && x.length > 0 ? undefined : "필수 입력입니다"),
          }))
          if (prompts.isCancel(key)) throw new UI.CancelledError()
          await put(providerId, { type: "api", key })
          prompts.log.success("API key 저장 완료")
        }

        // 5. 모델 목록 동적 조회
        const model = args.model ?? (await pickModel(providerId, database))
        if (!model) return

        // 6. 설정 저장
        await saveConfigFile({ $schema: "https://app.saeeol.ai/config.json", model })

        UI.empty()
        prompts.outro(`${S}설정 완료!${N}`)
        UI.empty()
        UI.println(`  ${D}시작하기:${N}`)
        UI.println(`    ${H}saeeol${N}          ${D}# 대화형 세션 시작${N}`)
        UI.println(`    ${H}saeeol run${N}      ${D}# 프롬프트 바로 실행${N}`)
        UI.empty()
      },
    })
  },
})

async function pickModel(
  providerId: string,
  database: Record<string, any>,
): Promise<string | undefined> {
  const provider = database[providerId]
  if (!provider?.models) {
    prompts.log.warn("해당 provider의 모델 목록을 가져올 수 없습니다.")
    return `${providerId}/default`
  }

  const models = Object.entries(provider.models as Record<string, any>)
    .filter(([, m]) => m.status !== "deprecated" && m.status !== "alpha")
    .map(([id, m]) => ({
      id: `${providerId}/${id}`,
      name: m.name || id,
      context: m.limit?.context,
    }))

  if (models.length === 0) {
    prompts.log.warn("사용 가능한 모델이 없습니다.")
    return `${providerId}/default`
  }

  // 컨텍스트 길이 기준 내림차순 정렬 (큰 게 보통 더 강력)
  models.sort((a, b) => (b.context ?? 0) - (a.context ?? 0))

  const selected = await prompts.select({
    message: "기본 모델을 선택하세요",
    maxItems: 10,
    options: models.map((m) => ({
      label: m.name,
      value: m.id,
      hint: m.context ? `${(m.context / 1000).toFixed(0)}K ctx` : undefined,
    })),
  })
  if (prompts.isCancel(selected)) throw new UI.CancelledError()
  return selected as string
}

async function handleCustom(args: Record<string, unknown>, database: Record<string, any>) {
  const baseUrl = (args["base-url"] as string) ?? (await prompts.text({
    message: "API Base URL",
    placeholder: "https://api.example.com/v1",
    validate: (x) => (x && x.startsWith("http") ? undefined : "http(s)://로 시작해야 합니다"),
  }))
  if (prompts.isCancel(baseUrl)) throw new UI.CancelledError()

  const key = (args["api-key"] as string) ?? (await prompts.password({
    message: "API Key",
    validate: (x) => (x && x.length > 0 ? undefined : "필수 입력입니다"),
  }))
  if (prompts.isCancel(key)) throw new UI.CancelledError()

  // /v1/models 호출로 동적 모델 조회
  let modelId: string | undefined
  try {
    const resp = await fetch(`${baseUrl.replace(/\/+$/, "")}/models`, {
      headers: { Authorization: `Bearer ${key}` },
      signal: AbortSignal.timeout(10000),
    })
    if (resp.ok) {
      const body = (await resp.json()) as { data?: Array<{ id: string }> }
      const remoteModels = body.data ?? []
      if (remoteModels.length > 0) {
        const selected = await prompts.select({
          message: `${remoteModels.length}개 모델 발견. 기본 모델 선택`,
          maxItems: 10,
          options: remoteModels.slice(0, 50).map((m) => ({ label: m.id, value: m.id })),
        })
        if (!prompts.isCancel(selected)) modelId = selected as string
      }
    }
  } catch {
    // /models 엔드포인트 없으면 수동 입력
  }

  if (!modelId) {
    modelId = (args.model as string) ?? (await prompts.text({
      message: "모델 ID",
      placeholder: "gpt-4o",
      validate: (x) => (x && x.length > 0 ? undefined : "필수 입력입니다"),
    })) as string
    if (prompts.isCancel(modelId)) throw new UI.CancelledError()
  }

  const pid = `custom-${Date.now().toString(36)}`
  await put(pid, { type: "api", key })

  await saveConfigFile({
    $schema: "https://app.saeeol.ai/config.json",
    model: `${pid}/${modelId}`,
    provider: {
      [pid]: {
        name: "Custom",
        npm: "@ai-sdk/openai-compatible",
        api: baseUrl,
      },
    },
  })

  UI.empty()
  prompts.outro(`${S}설정 완료!${N}`)
}

function providerHint(id: string): string | undefined {
  const hints: Record<string, string> = {
    anthropic: "https://console.anthropic.com/settings/keys",
    openai: "https://platform.openai.com/api-keys",
    google: "https://aistudio.google.com/apikey",
    openrouter: "https://openrouter.ai/keys",
    groq: "https://console.groq.com/keys",
    "amazon-bedrock": "AWS 자격 증명 필요",
    azure: "Azure OpenAI 리소스 필요",
    mistral: "https://console.mistral.ai/api-keys",
    deepinfra: "https://deepinfra.com/dash/api_keys",
    xai: "https://console.x.ai",
    cerebras: "https://cloud.cerebras.ai",
  }
  return hints[id]
}

function logo(): string {
  const lines = [
    "  ██████╗  █████╗ ███████╗███████╗██╗  ██╗",
    "  ██╔══██╗██╔══██╗██╔════╝██╔════╝██║ ██╔╝",
    "  ██████╔╝███████║███████╗███████╗█████╔╝ ",
    "  ██╔═══╝ ██╔══██║╚════██║╚════██║██╔═██╗ ",
    "  ██║     ██║  ██║███████║███████║██║  ██╗",
    "  ╚═╝     ╚═╝  ╚═╝╚══════╝╚══════╝╚═╝  ╚═╝",
  ]
  return lines.map((l) => `${H}${l}${N}`).join("\n")
}

async function saveConfigFile(config: Record<string, unknown>) {
  const configPath = path.join(Global.Path.config, "saeeol.json")
  const existing = await fs.readFile(configPath, "utf-8").catch(() => null)

  if (existing) {
    try {
      const parsed = JSON.parse(existing)
      const merged = { ...parsed, ...config }
      if (parsed.provider && config.provider) {
        merged.provider = { ...parsed.provider, ...(config.provider as Record<string, unknown>) }
      }
      await fs.writeFile(configPath, JSON.stringify(merged, null, 2) + "\n", "utf-8")
    } catch {
      await fs.writeFile(configPath, JSON.stringify(config, null, 2) + "\n", "utf-8")
    }
  } else {
    await fs.mkdir(Global.Path.config, { recursive: true })
    await fs.writeFile(configPath, JSON.stringify(config, null, 2) + "\n", "utf-8")
  }

  prompts.log.success(`설정 파일: ${D}${configPath}${N}`)
}
