import path from "path"
import { exec } from "child_process"
import { Filesystem } from "@/util/filesystem"
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"
import { AppRuntime } from "@/effect/app-runtime"
import { Git } from "@/git"
import { setTimeout as sleep } from "node:timers/promises"
import { WORKFLOW_FILE } from "./github-types"
import { parseGitHubRemote } from "./github-util"

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 AppRuntime.runPromise(ModelsDev.Service.use((s) => s.get())).then((p) => {
            // TODO: add guide for copilot, for now just hide it
            delete p["github-copilot"]
            return p
          })

          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 `/saeeol summarize` to see the agent in action",
              ].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 AppRuntime.runPromise(
              Git.Service.use((git) => git.run(["remote", "get-url", "origin"], { cwd: Instance.worktree })),
            ).then((x) => x.text().trim())
            const parsed = parseGitHubRemote(info)
            if (!parsed) {
              prompts.log.error(`Could not find git repository. Please run this command from a git repository.`)
              throw new UI.CancelledError()
            }
            return { owner: parsed.owner, repo: parsed.repo, root: Instance.worktree }
          }

          async function promptProvider() {
            const priority: Record<string, number> = {
              saeeol: 0,
              anthropic: 1,
              openai: 2,
              google: 3,
            }
            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,
                })),
              ),
            })

            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/saeeolconnect"
            const command =
              process.platform === "darwin"
                ? `open "${url}"`
                : process.platform === "win32"
                  ? `start "" "${url}"`
                  : `xdg-open "${url}"`

            exec(command, { windowsHide: true }, (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 sleep(1000)
            } while (true) // oxlint-disable-line no-constant-condition

            s.stop("Installed GitHub app")

            async function getInstallation() {
              return await fetch(`https://api.saeeol.ai/api/integrations/github/check-installation?owner=${app.owner}`)
                .then((res) => res.json())
                .then((data) => data.installation)
            }
          }

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

            const saeeolGatewayEnv =
              provider === "saeeol"
                ? `\n          SAEEOL_API_KEY: \${{ secrets.SAEEOL_API_KEY }}\n          SAEEOL_ORG_ID: \${{ secrets.SAEEOL_ORG_ID }}`
                : ""

            const envStr = providerEnvStr || saeeolGatewayEnv ? `\n        env:${providerEnvStr}${saeeolGatewayEnv}` : ""

            await Filesystem.write(
              path.join(app.root, WORKFLOW_FILE),
              `name: saeeol

on:
  issue_comment:
    types: [created]
  pull_request_review_comment:
    types: [created]

jobs:
  saeeol:
    if: |
      contains(github.event.comment.body, ' /kc') ||
      startsWith(github.event.comment.body, '/kc') ||
      contains(github.event.comment.body, ' /saeeol') ||
      startsWith(github.event.comment.body, '/saeeol')
    runs-on: ubuntu-latest
    permissions:
      id-token: write
      contents: read
      pull-requests: read
      issues: read
    steps:
      - name: Checkout repository
        uses: actions/checkout@v6
        with:
          persist-credentials: false

      - name: Run Saeeol
        uses: Saeeol-Org/saeeol/github@latest${envStr}
        with:
          model: ${provider}/${model}`,
            )

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