import type { Hooks, PluginInput } from "@woozlit/plugin"
import { Log } from "../util"
import { createServer } from "http"
import { exec } from "child_process"

const log = Log.create({ service: "plugin.woozlit" })

const WOOZLIT_BASE_URL = process.env.WOOZLIT_BASE_URL || "https://www.woozlit.com"
const FIREBASE_API_KEY = "AIzaSyBbJtkBoJrKM2hjW2H3o9pgtcyiGge9yOo"

function decodeJwt(token: string): any {
  try {
    const parts = token.split(".")
    if (parts.length !== 3) return null
    return JSON.parse(Buffer.from(parts[1], "base64url").toString("utf-8"))
  } catch {
    return null
  }
}

function isCustomToken(token: string): boolean {
  try {
    const payload = decodeJwt(token)
    if (!payload) return false
    if (payload.iss && payload.iss.startsWith("https://securetoken.google.com/")) {
      return false
    }
    return true
  } catch {
    return false
  }
}

async function exchangeCustomTokenForIdToken(customToken: string) {
  const res = await fetch(`https://identitytoolkit.googleapis.com/v1/accounts:signInWithCustomToken?key=${FIREBASE_API_KEY}`, {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({ token: customToken, returnSecureToken: true }),
  })
  if (!res.ok) throw new Error("Firebase custom token exchange failed")
  const data = await res.json()
  return {
    idToken: data.idToken,
    refreshToken: data.refreshToken,
    expiresIn: parseInt(data.expiresIn, 10),
  }
}

async function refreshFirebaseIdToken(refreshToken: string) {
  const res = await fetch(`https://securetoken.googleapis.com/v1/token?key=${FIREBASE_API_KEY}`, {
    method: "POST",
    headers: { "Content-Type": "application/x-www-form-urlencoded" },
    body: new URLSearchParams({
      grant_type: "refresh_token",
      refresh_token: refreshToken,
    }),
  })
  if (!res.ok) throw new Error("Firebase token refresh failed")
  const data = await res.json()
  return {
    idToken: data.id_token,
    refreshToken: data.refresh_token,
    expiresIn: parseInt(data.expires_in, 10),
  }
}

function openBrowser(url: string) {
  const command =
    process.platform === "darwin"
      ? `open "${url}"`
      : process.platform === "win32"
        ? `start "" "${url}"`
        : `xdg-open "${url}"`
  exec(command, (error) => {
    if (error) {
      log.warn("could not open browser automatically", { error })
    }
  })
}

function createDesktopAuthResponse(title: string, message: string) {
  return `<!doctype html>
<html>
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>${title}</title>
  <style>
    body { margin: 0; min-height: 100vh; display: grid; place-items: center; background: #0a0a0a; color: #fff; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; }
    main { max-width: 420px; padding: 32px; text-align: center; }
    h1 { margin: 0 0 10px; font-size: 24px; font-weight: 600; }
    p { margin: 0; color: #a1a1aa; line-height: 1.5; }
  </style>
</head>
<body>
  <main>
    <h1>${title}</h1>
    <p>${message}</p>
  </main>
</body>
</html>`
}

export async function WoozlitPlugin(input: PluginInput): Promise<Hooks> {
  const sdk = input.client
  let cached: { idToken: string; expires: number } | null = null

  async function getValidToken(): Promise<string | null> {
    if (cached && cached.expires - Date.now() > 5 * 60 * 1000) {
      return cached.idToken
    }

    const res = await sdk.auth.get({ path: { id: "woozlit" } })
    if (res.error || !res.data || !res.data.auth) return null
    const authData = res.data.auth as any

    if (authData.type === "api") {
      return authData.key
    }

    if (authData.type === "oauth") {
      if (authData.expires && authData.expires - Date.now() > 5 * 60 * 1000) {
        cached = { idToken: authData.access, expires: authData.expires }
        return authData.access
      }

      if (!authData.refresh || authData.refresh === "dummy") return null

      try {
        log.info("Refreshing Woozlit auth token...")
        const tokenData = await refreshFirebaseIdToken(authData.refresh)
        const newAuth = {
          type: "oauth",
          access: tokenData.idToken,
          refresh: tokenData.refreshToken,
          expires: Date.now() + tokenData.expiresIn * 1000,
        }
        await sdk.auth.set({ path: { id: "woozlit" }, body: newAuth as any })
        cached = { idToken: tokenData.idToken, expires: newAuth.expires }
        return tokenData.idToken
      } catch (err) {
        log.error("Failed to refresh Woozlit token, clearing credentials", { error: err })
        try {
          await (sdk.auth as any)._client.delete({ url: "/auth/{id}", path: { id: "woozlit" } })
        } catch (e) {
          log.error("Failed to clear expired credentials from database", { error: e })
        }
        return null
      }
    }

    return null
  }

  async function wrappedFetch(url: any, init?: any): Promise<Response> {
    const token = await getValidToken()
    const headers = new Headers(init?.headers)
    if (token) {
      headers.set("Authorization", `Bearer ${token}`)
    }
    headers.set("X-Woozlit-Source", "woozlitcode-cli")
    return fetch(url, { ...init, headers })
  }

  return {
    config: async (cfg) => {
      cfg.provider ??= {}
      cfg.provider.woozlit ??= {
        name: "Woozlit",
        npm: "@ai-sdk/openai-compatible",
        api: `${WOOZLIT_BASE_URL}/api/woozlit/v1`,
        options: {
          apiKey: "dummy",
          fetch: wrappedFetch,
        },
        models: {

          "panko": {
            name: "Panko",
            attachment: true,
            reasoning: true,
            tool_call: true,
            temperature: false,
            modalities: { input: ["text", "image"], output: ["text"] },
            limit: { context: 128000, output: 8192 },
            cost: { input: 0, output: 0 },
          },
          "panko-pro": {
            name: "Panko Pro",
            attachment: true,
            reasoning: true,
            tool_call: true,
            temperature: false,
            modalities: { input: ["text", "image"], output: ["text"] },
            limit: { context: 128000, output: 16384 },
            cost: { input: 0, output: 0 },
          },
          "panko-mini": {
            name: "Panko Mini",
            attachment: true,
            reasoning: true,
            tool_call: true,
            temperature: false,
            modalities: { input: ["text", "image"], output: ["text"] },
            limit: { context: 64000, output: 8192 },
            cost: { input: 0, output: 0 },
          },
          "panko-xp": {
            name: "Panko XP",
            attachment: true,
            reasoning: true,
            tool_call: true,
            temperature: false,
            modalities: { input: ["text", "image"], output: ["text"] },
            limit: { context: 128000, output: 16384 },
            cost: { input: 0, output: 0 },
          },
          "panko-code": {
            name: "Panko Code",
            attachment: true,
            reasoning: true,
            tool_call: true,
            temperature: false,
            modalities: { input: ["text", "image"], output: ["text"] },
            limit: { context: 128000, output: 16384 },
            cost: { input: 0, output: 0 },
          },
        },
      }
    },
    auth: {
      provider: "woozlit",
      async loader(getAuth) {
        const authData = (await getAuth()) as any
        log.info("woozlit auth loader invoked", { authData })
        if (!authData) return {}
        if (authData.type === "api" && authData.key) {
          log.info("woozlit auth returning api key")
          return {
            apiKey: authData.key,
            fetch: wrappedFetch,
            email: authData.metadata?.email,
            displayName: authData.metadata?.displayName,
            userId: authData.metadata?.userId,
          }
        }
        if (authData.type === "oauth") {
          const token = authData.access || authData.key
          log.info("woozlit auth resolving oauth token", { hasToken: !!token })
          if (token) {
            return {
              apiKey: token,
              fetch: wrappedFetch,
              email: authData.metadata?.email,
              displayName: authData.metadata?.displayName,
              userId: authData.metadata?.userId,
            }
          }
        }
        log.warn("woozlit auth returning empty configuration")
        return {}
      },
      methods: [
        {
          label: "Browser Login",
          type: "oauth" as const,
          authorize: async () => {
            const authHost = process.env.WOOZLIT_AUTH_HOST || "127.0.0.1"
            const server = createServer()
            await new Promise<void>((resolve, reject) => {
              server.listen(0, authHost, () => resolve())
              server.on("error", reject)
            })
            const addr = server.address()
            const port = typeof addr === "object" && addr ? addr.port : 0
            log.info("woozlit oauth server started", { port, authHost })

            const redirectUri = `http://${authHost}:${port}/`
            const authUrl = `${WOOZLIT_BASE_URL}/login?callback=${encodeURIComponent(redirectUri)}&desktop=1`

            openBrowser(authUrl)

            const serverCallbackPromise = new Promise<{ token: string; userId: string; email: string; displayName: string }>((resolve, reject) => {
              const timeout = setTimeout(() => {
                server.close()
                reject(new Error("Authorization timeout"))
              }, 5 * 60 * 1000)

              server.on("request", (req, res) => {
                const url = new URL(req.url || "/", `http://${authHost}`)
                log.info("woozlit oauth callback received", { path: url.pathname, query: url.search.substring(0, 100) })

                if (url.pathname === "/favicon.ico") {
                  res.writeHead(404, { Connection: "close" })
                  res.end()
                  return
                }

                const token = url.searchParams.get("token")
                const userId = url.searchParams.get("userId") || ""
                const email = url.searchParams.get("email") || ""
                const displayName = url.searchParams.get("displayName") || ""

                if (!token) {
                  log.warn("woozlit oauth callback missing token")
                  res.writeHead(400, { "Content-Type": "text/html; charset=utf-8" })
                  res.end(
                    createDesktopAuthResponse(
                      "Login could not be verified",
                      "Return to the terminal and try signing in again."
                    )
                  )
                  reject(new Error("Missing token parameter"))
                  return
                }

                res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" })
                res.end(
                  createDesktopAuthResponse(
                    "You're all set",
                    "You can close this window and return to the terminal."
                  )
                )
                clearTimeout(timeout)
                resolve({ token, userId, email, displayName })
              })
            })
            serverCallbackPromise.catch(() => {})

            return {
              url: authUrl,
              method: "auto" as const,
              instructions: "Complete authorization in the browser to log in to Woozlit.",
              callback: async (code?: string) => {
                log.info("woozlit oauth callback invoked", { codeLength: code?.length })
                const customToken = code ? code.trim() : (await serverCallbackPromise).token
                log.info("woozlit oauth customToken obtained", { customTokenLength: customToken?.length })
                server.close()
                try {
                  let tokenData: { idToken: string; refreshToken: string; expiresIn: number }
                  if (isCustomToken(customToken)) {
                    tokenData = await exchangeCustomTokenForIdToken(customToken)
                  } else {
                    tokenData = {
                      idToken: customToken,
                      refreshToken: "dummy",
                      expiresIn: 30 * 24 * 60 * 60,
                    }
                  }
                  log.info("woozlit oauth token resolved successfully")
                  const newAuth = {
                    type: "oauth",
                    access: tokenData.idToken,
                    refresh: tokenData.refreshToken,
                    expires: Date.now() + tokenData.expiresIn * 1000,
                  }
                  cached = { idToken: tokenData.idToken, expires: newAuth.expires }
                  const decoded = decodeJwt(tokenData.idToken)
                  const metadata = {
                    userId: decoded?.user_id || decoded?.sub || (code ? "" : (await serverCallbackPromise).userId),
                    email: decoded?.email || (code ? "" : (await serverCallbackPromise).email),
                    displayName: decoded?.name || decoded?.displayName || (code ? "" : (await serverCallbackPromise).displayName),
                  }
                  return {
                    type: "success" as const,
                    refresh: tokenData.refreshToken,
                    access: tokenData.idToken,
                    expires: newAuth.expires,
                    metadata,
                  }
                } catch (err) {
                  log.warn("Failed to exchange custom token, falling back to direct API key usage", { error: err })
                  const decoded = decodeJwt(customToken)
                  const metadata = {
                    userId: decoded?.user_id || decoded?.sub || (code ? "" : (await serverCallbackPromise).userId),
                    email: decoded?.email || (code ? "" : (await serverCallbackPromise).email),
                    displayName: decoded?.name || decoded?.displayName || (code ? "" : (await serverCallbackPromise).displayName),
                  }
                  return {
                    type: "success" as const,
                    access: customToken,
                    refresh: "dummy",
                    expires: Date.now() + 30 * 24 * 60 * 60 * 1000,
                    metadata,
                  }
                }
              },
            }
          },
        },
      ],
    },
    "chat.headers": async (input, output) => {
      if (input.model.providerID !== "woozlit") return
      output.headers["X-Woozlit-Source"] = "woozlitcode-cli"
    },
  }
}

export async function AnthropicProxyPlugin(_input: PluginInput): Promise<Hooks> {
  return {
    auth: {
      provider: "anthropic",
      async loader(_getAuth, provider) {
        if (!provider?.options?.baseURL) return {}
        return {
          async fetch(url: any, init: any) {
            if (init?.headers && typeof init.headers === "object" && !Array.isArray(init.headers)) {
              delete init.headers["anthropic-beta"]
            }
            const res = await fetch(url, init)
            if (!res.body || !res.headers.get("content-type")?.includes("text/event-stream")) return res
            const reader = res.body.getReader()
            const decoder = new TextDecoder()
            let done = false
            let buffer = ""
            const body = new ReadableStream<Uint8Array>({
              async pull(ctrl) {
                if (done) { ctrl.close(); return }
                const chunk = await reader.read()
                if (chunk.done) { ctrl.close(); return }
                ctrl.enqueue(chunk.value)
                buffer += decoder.decode(chunk.value, { stream: true })
                if (buffer.includes("\nevent: message_stop\n") || buffer.includes("\ndata: {\"type\":\"message_stop\"}")) {
                  done = true
                  void reader.cancel()
                  ctrl.close()
                }
                if (buffer.length > 512) buffer = buffer.slice(-256)
              },
              cancel() { reader.cancel() },
            })
            return new Response(body, { headers: res.headers, status: res.status })
          },
        }
      },
      methods: [],
    },
  }
}
