import path from "path"
import { $ } from "bun"
import { exec } from "child_process"
import * as prompts from "@clack/prompts"
import { map, pipe, sortBy, values } from "remeda"
import { UI } from "../ui"
import { cmd } from "./cmd"
import { ModelsDev } from "../../provider/models"
import { Instance } from "../../project/instance"

const WORKFLOW_FILE = ".github/workflows/opencode.yml"

export const GithubCommand = cmd({
  command: "github",
  describe: "manage GitHub agent",
  builder: (yargs) => yargs.command(GithubInstallCommand).demandCommand(),
  async handler() {},
})

export const GithubInstallCommand = cmd({
  command: "install",
  describe: "install the GitHub agent",
  async handler() {
    await Instance.provide({
      directory: process.cwd(),
      async fn() {
        UI.empty()
        prompts.intro("Install GitHub agent")
        const app = await getAppInfo()
        await installGitHubApp()

        const providers = await ModelsDev.get()
        const provider = await promptProvider()
        const model = await promptModel()
        //const key = await promptKey()

        await addWorkflowFiles()
        printNextSteps()

        function printNextSteps() {
          let step2
          if (provider === "amazon-bedrock") {
            step2 =
              "Configure OIDC in AWS - https://docs.github.com/en/actions/how-tos/security-for-github-actions/security-hardening-your-deployments/configuring-openid-connect-in-amazon-web-services"
          } else {
            step2 = [
              `    2. Add the following secrets in org or repo (${app.owner}/${app.repo}) settings`,
              "",
              ...providers[provider].env.map((e) => `       - ${e}`),
            ].join("\n")
          }

          prompts.outro(
            [
              "Next steps:",
              "",
              `    1. Commit the \`${WORKFLOW_FILE}\` file and push`,
              step2,
              "",
              "    3. Go to a GitHub issue and comment `/oc summarize` to see the agent in action",
              "",
              "   Learn more about the GitHub agent - https://opencode.ai/docs/github/#usage-examples",
            ].join("\n"),
          )
        }

        async function getAppInfo() {
          const project = Instance.project
          if (project.vcs !== "git") {
            prompts.log.error(`Could not find git repository. Please run this command from a git repository.`)
            throw new UI.CancelledError()
          }

          // Get repo info
          const info = await $`git remote get-url origin`
            .quiet()
            .nothrow()
            .text()
            .then((text) => text.trim())
          // match https or git pattern
          // ie. https://github.com/sst/opencode.git
          // ie. https://github.com/sst/opencode
          // ie. git@github.com:sst/opencode.git
          // ie. git@github.com:sst/opencode
          // ie. ssh://git@github.com/sst/opencode.git
          // ie. ssh://git@github.com/sst/opencode
          const parsed = info.match(/^(?:(?:https?|ssh):\/\/)?(?:git@)?github\.com[:/]([^/]+)\/([^/.]+?)(?:\.git)?$/)
          if (!parsed) {
            prompts.log.error(`Could not find git repository. Please run this command from a git repository.`)
            throw new UI.CancelledError()
          }
          const [, owner, repo] = parsed
          return { owner, repo, root: Instance.worktree }
        }

        async function promptProvider() {
          const priority: Record<string, number> = {
            opencode: 0,
            anthropic: 1,
            "github-copilot": 2,
            openai: 3,
            google: 4,
            openrouter: 5,
            vercel: 6,
          }
          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] <= 1 ? "recommended" : undefined,
              })),
            ),
          })

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

          return provider
        }

        async function promptModel() {
          const providerData = providers[provider]!

          const model = await prompts.select({
            message: "Select model",
            maxItems: 8,
            options: pipe(
              providerData.models,
              values(),
              sortBy((x) => x.name ?? x.id),
              map((x) => ({
                label: x.name ?? x.id,
                value: x.id,
              })),
            ),
          })

          if (prompts.isCancel(model)) throw new UI.CancelledError()
          return model
        }

        async function installGitHubApp() {
          const s = prompts.spinner()
          s.start("Installing GitHub app")

          // Get installation
          const installation = await getInstallation()
          if (installation) return s.stop("GitHub app already installed")

          // Open browser
          const url = "https://github.com/apps/opencode-agent"
          const command =
            process.platform === "darwin"
              ? `open "${url}"`
              : process.platform === "win32"
                ? `start "${url}"`
                : `xdg-open "${url}"`

          exec(command, (error) => {
            if (error) {
              prompts.log.warn(`Could not open browser. Please visit: ${url}`)
            }
          })

          // Wait for installation
          s.message("Waiting for GitHub app to be installed")
          const MAX_RETRIES = 120
          let retries = 0
          do {
            const installation = await getInstallation()
            if (installation) break

            if (retries > MAX_RETRIES) {
              s.stop(
                `Failed to detect GitHub app installation. Make sure to install the app for the \`${app.owner}/${app.repo}\` repository.`,
              )
              throw new UI.CancelledError()
            }

            retries++
            await new Promise((resolve) => setTimeout(resolve, 1000))
          } while (true)

          s.stop("Installed GitHub app")

          async function getInstallation() {
            return await fetch(
              `https://api.opencode.ai/get_github_app_installation?owner=${app.owner}&repo=${app.repo}`,
            )
              .then((res) => res.json())
              .then((data) => data.installation)
          }
        }

        async function addWorkflowFiles() {
          const envStr =
            provider === "amazon-bedrock"
              ? ""
              : `\n        env:${providers[provider].env.map((e) => `\n          ${e}: \${{ secrets.${e} }}`).join("")}`

          await Bun.write(
            path.join(app.root, WORKFLOW_FILE),
            `
name: opencode

on:
  issue_comment:
    types: [created]

jobs:
  opencode:
    if: |
      contains(github.event.comment.body, ' /oc') ||
      startsWith(github.event.comment.body, '/oc') ||
      contains(github.event.comment.body, ' /opencode') ||
      startsWith(github.event.comment.body, '/opencode')
    runs-on: ubuntu-latest
    permissions:
      contents: read
      id-token: write
    steps:
      - name: Checkout repository
        uses: actions/checkout@v4

      - name: Run opencode
        uses: sst/opencode/github@latest${envStr}
        with:
          model: ${provider}/${model}
`.trim(),
          )

          prompts.log.success(`Added workflow file: "${WORKFLOW_FILE}"`)
        }
      },
    })
  },
})
