import { AuthAnthropic } from "../../auth/anthropic"
import { Auth } from "../../auth"
import { cmd } from "./cmd"
import * as prompts from "@clack/prompts"
import open from "open"
import { UI } from "../ui"
import { ModelsDev } from "../../provider/models"
import { map, pipe, sortBy, values } from "remeda"

export const AuthCommand = cmd({
  command: "auth",
  describe: "manage credentials",
  builder: (yargs) =>
    yargs
      .command(AuthLoginCommand)
      .command(AuthLogoutCommand)
      .command(AuthListCommand)
      .demandCommand(),
  async handler() {},
})

export const AuthListCommand = cmd({
  command: "list",
  aliases: ["ls"],
  describe: "list providers",
  async handler() {
    UI.empty()
    prompts.intro("Credentials")
    const results = await Auth.all().then((x) => Object.entries(x))
    const database = await ModelsDev.get()

    for (const [providerID, result] of results) {
      const name = database[providerID]?.name || providerID
      prompts.log.info(`${name} ${UI.Style.TEXT_DIM}(${result.type})`)
    }

    prompts.outro(`${results.length} credentials`)
  },
})

export const AuthLoginCommand = cmd({
  command: "login",
  describe: "login to a provider",
  async handler() {
    UI.empty()
    prompts.intro("Add credential")
    const providers = await ModelsDev.get()
    const priority: Record<string, number> = {
      anthropic: 0,
      openai: 1,
      google: 2,
    }
    let provider = await prompts.select({
      message: "Select provider",
      maxItems: 8,
      options: [
        ...pipe(
          providers,
          values(),
          sortBy(
            (x) => priority[x.id] ?? 99,
            (x) => x.name ?? x.id,
          ),
          map((x) => ({
            label: x.name,
            value: x.id,
            hint: priority[x.id] === 0 ? "recommended" : undefined,
          })),
        ),
        {
          value: "other",
          label: "Other",
        },
      ],
    })

    if (prompts.isCancel(provider)) throw new UI.CancelledError()

    if (provider === "other") {
      provider = await prompts.text({
        message: "Enter provider id",
        validate: (x) =>
          x.match(/^[a-z-]+$/) ? undefined : "a-z and hyphens only",
      })
      if (prompts.isCancel(provider)) throw new UI.CancelledError()
      provider = provider.replace(/^@ai-sdk\//, "")
      if (prompts.isCancel(provider)) throw new UI.CancelledError()
      prompts.log.warn(
        `This only stores a credential for ${provider} - you will need configure it in opencode.json, check the docs for examples.`,
      )
    }

    if (provider === "amazon-bedrock") {
      prompts.log.info(
        "Amazon bedrock can be configured with standard AWS environment variables like AWS_PROFILE or AWS_ACCESS_KEY_ID",
      )
      prompts.outro("Done")
      return
    }

    if (provider === "anthropic") {
      const method = await prompts.select({
        message: "Login method",
        options: [
          {
            label: "Claude Pro/Max",
            value: "oauth",
          },
          {
            label: "API Key",
            value: "api",
          },
        ],
      })
      if (prompts.isCancel(method)) throw new UI.CancelledError()

      if (method === "oauth") {
        // some weird bug where program exits without this
        await new Promise((resolve) => setTimeout(resolve, 10))
        const { url, verifier } = await AuthAnthropic.authorize()
        prompts.note("Trying to open browser...")
        try {
          await open(url)
        } catch (e) {
          prompts.log.error(
            "Failed to open browser perhaps you are running without a display or X server, please open the following URL in your browser:",
          )
        }
        prompts.log.info(url)

        const code = await prompts.text({
          message: "Paste the authorization code here: ",
          validate: (x) => (x.length > 0 ? undefined : "Required"),
        })
        if (prompts.isCancel(code)) throw new UI.CancelledError()

        await AuthAnthropic.exchange(code, verifier)
          .then(() => {
            prompts.log.success("Login successful")
          })
          .catch(() => {
            prompts.log.error("Invalid code")
          })
        prompts.outro("Done")
        return
      }
    }

    const key = await prompts.password({
      message: "Enter your API key",
      validate: (x) => (x.length > 0 ? undefined : "Required"),
    })
    if (prompts.isCancel(key)) throw new UI.CancelledError()
    await Auth.set(provider, {
      type: "api",
      key,
    })

    prompts.outro("Done")
  },
})

export const AuthLogoutCommand = cmd({
  command: "logout",
  describe: "logout from a configured provider",
  async handler() {
    UI.empty()
    const credentials = await Auth.all().then((x) => Object.entries(x))
    prompts.intro("Remove credential")
    if (credentials.length === 0) {
      prompts.log.error("No credentials found")
      return
    }
    const database = await ModelsDev.get()
    const providerID = await prompts.select({
      message: "Select provider",
      options: credentials.map(([key, value]) => ({
        label:
          (database[key]?.name || key) +
          UI.Style.TEXT_DIM +
          " (" +
          value.type +
          ")",
        value: key,
      })),
    })
    if (prompts.isCancel(providerID)) throw new UI.CancelledError()
    await Auth.remove(providerID)
    prompts.outro("Logout successful")
  },
})
