/**
 * Git 工作区准备：读 WS_GIT_* 环境变量、clone/pull、探测工作区。
 * WS init 由 prepareForAgent 调用；与 CodeTool 仅通过环境变量约定解耦。
 */

import { spawn } from 'node:child_process'
import { access, mkdir } from 'node:fs/promises'
import { dirname, isAbsolute, join } from 'node:path'
import { configure, getCachedConfigure, resetConfigure } from './hooks.js'

/** agent 根下的工具工作区容器目录 */
const AGENT_WORKSPACE_SUBDIR = 'workspace'
const GIT_REPO_SUBDIR = 'repo'
const DEFAULT_GIT_TIMEOUT_MS = 120 * 1000
/** session init 远程探测超时，短于 clone/pull，避免地址不通时长时间阻塞 */
const DEFAULT_GIT_PROBE_TIMEOUT_MS = 15 * 1000

const GIT_SSH_BATCH_MODE =
  'core.sshCommand=ssh -o BatchMode=yes -o StrictHostKeyChecking=yes'

const GIT_NO_PROMPT_ENV: Record<string, string> = {
  GIT_TERMINAL_PROMPT: '0',
  GIT_ASKPASS: '',
}

export type GitCredentials = {
  username: string
  password: string
}

export type PrepareForAgentResult = 'ready' | 'skipped'

function getGitTimeoutMs(): number {
  const envValue = process.env.CLAUDE_CODE_PLUGIN_GIT_TIMEOUT_MS
  if (envValue) {
    const parsed = parseInt(envValue, 10)
    if (!Number.isNaN(parsed) && parsed > 0) {
      return parsed
    }
  }
  return DEFAULT_GIT_TIMEOUT_MS
}

/** 远程 ls-remote 探测超时（可用 CLAUDE_CODE_PLUGIN_GIT_PROBE_TIMEOUT_MS 覆盖） */
function getGitProbeTimeoutMs(): number {
  const envValue = process.env.CLAUDE_CODE_PLUGIN_GIT_PROBE_TIMEOUT_MS
  if (envValue) {
    const parsed = parseInt(envValue, 10)
    if (!Number.isNaN(parsed) && parsed > 0) {
      return parsed
    }
  }
  return DEFAULT_GIT_PROBE_TIMEOUT_MS
}

/** 日志脱敏：隐藏 http(s) URL 中的 user:pass */
export function redactUrlCredentials(urlString: string): string {
  try {
    const parsed = new URL(urlString)
    const isHttp = parsed.protocol === 'http:' || parsed.protocol === 'https:'
    if (isHttp && (parsed.username || parsed.password)) {
      if (parsed.username) parsed.username = '***'
      if (parsed.password) parsed.password = '***'
      return parsed.toString()
    }
  } catch {
    // 非 URL 原样返回
  }
  return urlString
}

/**
 * 读取 WS_GIT_USERNAME / WS_GIT_PASSWORD。
 * 业务语义：两者均设置才生效；仅设置其一则 fail-fast。
 */
export function readGitCredentials(): GitCredentials | null {
  const username = process.env.WS_GIT_USERNAME?.trim() ?? ''
  const password = process.env.WS_GIT_PASSWORD?.trim() ?? ''
  if (!username && !password) {
    return null
  }
  if (!username || !password) {
    throw new Error(
      'WS_GIT_USERNAME 与 WS_GIT_PASSWORD 必须成对设置',
    )
  }
  return { username, password }
}

/**
 * 读取 WS_GIT_USER_EMAIL，用于 git commit 作者邮箱。
 * 业务语义：未设置时返回 null，不注入 user.email；多用户环境避免依赖全局 git config。
 */
export function readGitUserEmail(): string | null {
  const email = process.env.WS_GIT_USER_EMAIL?.trim() ?? ''
  return email || null
}

/** HTTPS URL 注入 user:pass；SSH URL（git@）原样返回 */
export function withHttpsCredentials(
  url: string,
  creds: GitCredentials | null,
): string {
  if (!creds) {
    return url
  }
  try {
    const parsed = new URL(url)
    if (parsed.protocol !== 'http:' && parsed.protocol !== 'https:') {
      return url
    }
    parsed.username = encodeURIComponent(creds.username)
    parsed.password = encodeURIComponent(creds.password)
    return parsed.toString()
  } catch {
    return url
  }
}

/** 构造 git 命令额外 env：防交互 + 凭据 helper + 可选提交身份（user.name/user.email） */
export function buildGitCredentialEnv(
  creds: GitCredentials | null,
): Record<string, string> {
  const env: Record<string, string> = { ...GIT_NO_PROMPT_ENV }
  const configEntries: Array<{ key: string; value: string }> = []

  if (creds) {
    configEntries.push({
      key: 'credential.helper',
      value:
        `!f() { echo username=${creds.username}; echo password=${creds.password}; }; f`,
    })
  }

  const email = readGitUserEmail()
  if (email) {
    configEntries.push({ key: 'user.email', value: email })
    // 提交作者名与 HTTPS 域账号同源，避免多用户环境写全局 git config
    const name = process.env.WS_GIT_USERNAME?.trim() ?? ''
    if (name) {
      configEntries.push({ key: 'user.name', value: name })
    }
  }

  if (configEntries.length > 0) {
    env.GIT_CONFIG_COUNT = String(configEntries.length)
    configEntries.forEach((entry, index) => {
      env[`GIT_CONFIG_KEY_${index}`] = entry.key
      env[`GIT_CONFIG_VALUE_${index}`] = entry.value
    })
  }

  return env
}

/** 解析 agent 根下的 workspace 目录 */
function resolveAgentWorkspaceDir(operWorkspaceDir: string): string {
  return join(operWorkspaceDir, AGENT_WORKSPACE_SUBDIR)
}

/**
 * 解析 Git 工作目录：WS_GIT_WORK_DIR 优先，否则默认 workspace/repo。
 * 相对路径以 workspace/ 为基准；绝对路径原样使用。与 CodeTool 共用 env 约定。
 */
export function resolveGitWorkDir(operWorkspaceDir: string): string {
  const workDir = process.env.WS_GIT_WORK_DIR?.trim() ?? ''
  const workspaceDir = resolveAgentWorkspaceDir(operWorkspaceDir)
  if (!workDir) {
    return join(workspaceDir, GIT_REPO_SUBDIR)
  }
  return isAbsolute(workDir) ? workDir : join(workspaceDir, workDir)
}

async function pathExists(path: string): Promise<boolean> {
  try {
    await access(path)
    return true
  } catch {
    return false
  }
}

type GitExecResult = {
  code: number
  stdout: string
  stderr: string
}

/** 在指定 cwd 执行 git 子进程 */
function execGit(
  args: string[],
  options: {
    cwd?: string
    env?: Record<string, string>
    timeoutMs?: number
  },
): Promise<GitExecResult> {
  const timeoutMs = options.timeoutMs ?? getGitTimeoutMs()
  return new Promise((resolve, reject) => {
    const proc = spawn('git', args, {
      cwd: options.cwd,
      stdio: ['ignore', 'pipe', 'pipe'],
      env: { ...process.env, ...options.env },
    })
    let stdout = ''
    let stderr = ''
    const timer = setTimeout(() => {
      proc.kill('SIGKILL')
      reject(new Error(`git ${args.join(' ')} timed out after ${timeoutMs}ms`))
    }, timeoutMs)
    proc.stdout.on('data', c => (stdout += String(c)))
    proc.stderr.on('data', c => (stderr += String(c)))
    proc.on('error', err => {
      clearTimeout(timer)
      reject(err)
    })
    proc.on('close', code => {
      clearTimeout(timer)
      resolve({
        code: typeof code === 'number' ? code : 1,
        stdout,
        stderr,
      })
    })
  })
}

/** 探测目录是否为 git 工作区 */
async function isGitWorkTree(
  dir: string,
  gitEnv: Record<string, string>,
): Promise<boolean> {
  const result = await execGit(['rev-parse', '--is-inside-work-tree'], {
    cwd: dir,
    env: gitEnv,
  })
  return result.code === 0 && result.stdout.trim() === 'true'
}

/** 根据 git stderr 区分认证失败与网络不可达，便于 init 快速失败 */
function classifyGitRemoteFailure(stderr: string): 'auth' | 'network' | 'unknown' {
  const s = stderr.toLowerCase()
  if (
    /authentication failed/.test(s) ||
    /invalid credentials/.test(s) ||
    /\b401\b/.test(s) ||
    /\b403\b/.test(s) ||
    /access denied/.test(s) ||
    /permission denied/.test(s) ||
    /could not read username/.test(s) ||
    /authorization failed/.test(s) ||
    /terminal prompts disabled/.test(s)
  ) {
    return 'auth'
  }
  if (
    /timed out/.test(s) ||
    /connection refused/.test(s) ||
    /could not resolve/.test(s) ||
    /network is unreachable/.test(s) ||
    /no route to host/.test(s) ||
    /unable to access/.test(s) ||
    /failed to connect/.test(s) ||
    /name or service not known/.test(s) ||
    /couldn't connect to host/.test(s)
  ) {
    return 'network'
  }
  return 'unknown'
}

/**
 * clone/pull 前用 ls-remote 探测远程可达性与凭据。
 * 仅发起轻量握手，避免 session init 在不可达地址上挂到 clone 超时。
 */
async function probeGitRemote(params: {
  url: string
  gitEnv: Record<string, string>
}): Promise<void> {
  console.warn(
    `[git-tool] probe remote url=${redactUrlCredentials(params.url)}`,
  )
  const args = ['-c', GIT_SSH_BATCH_MODE, 'ls-remote', '--heads', params.url]
  let result: GitExecResult
  try {
    result = await execGit(args, {
      env: params.gitEnv,
      timeoutMs: getGitProbeTimeoutMs(),
    })
  } catch (e: unknown) {
    const msg = e instanceof Error ? e.message : String(e)
    if (/timed out/i.test(msg)) {
      throw new Error(
        `Git 远程探测超时（${getGitProbeTimeoutMs()}ms）: url=${redactUrlCredentials(params.url)}`,
      )
    }
    throw e
  }
  if (result.code !== 0) {
    const stderr = redactUrlCredentials(result.stderr)
    const kind = classifyGitRemoteFailure(stderr)
    const prefix =
      kind === 'auth'
        ? 'Git 远程认证失败'
        : kind === 'network'
          ? 'Git 远程不可达'
          : 'Git 远程探测失败'
    throw new Error(
      `${prefix}: url=${redactUrlCredentials(params.url)} exit=${result.code}\n${stderr}`.trim(),
    )
  }
}

/** git clone 到目标目录（浅克隆） */
async function wsGitClone(params: {
  url: string
  targetPath: string
  gitEnv: Record<string, string>
}): Promise<void> {
  const args = [
    '-c',
    GIT_SSH_BATCH_MODE,
    'clone',
    '--depth',
    '1',
    '--recurse-submodules',
    '--shallow-submodules',
  ]
  args.push(params.url, params.targetPath)

  console.warn(
    `[git-tool] clone url=${redactUrlCredentials(params.url)} target=${params.targetPath}`,
  )

  const result = await execGit(args, { env: params.gitEnv })
  if (result.code !== 0) {
    const stderr = redactUrlCredentials(result.stderr)
    throw new Error(
      `git clone 失败: url=${redactUrlCredentials(params.url)} exit=${result.code}\n${stderr}`.trim(),
    )
  }
}

/** 在已有仓库目录执行 git pull */
async function wsGitPull(params: {
  cwd: string
  gitEnv: Record<string, string>
}): Promise<void> {
  console.warn(`[git-tool] pull cwd=${params.cwd}`)
  const result = await execGit(['pull', 'origin', 'HEAD'], {
    cwd: params.cwd,
    env: params.gitEnv,
  })
  if (result.code !== 0) {
    throw new Error(
      `git pull 失败: cwd=${params.cwd} exit=${result.code}\n${result.stderr}`.trim(),
    )
  }
}

/**
 * WS init：按环境变量准备 git 工作区并 configure GitTool。
 * 未配置 WS_GIT_REPO_URL 且本地无 git 工作区时返回 skipped，不抛错。
 */
export async function prepareForAgent(params: {
  operWorkspaceDir: string
}): Promise<PrepareForAgentResult> {
  const repoUrl = process.env.WS_GIT_REPO_URL?.trim() ?? ''
  const creds = readGitCredentials()
  const gitWorkDir = resolveGitWorkDir(params.operWorkspaceDir)
  const gitEnv = buildGitCredentialEnv(creds)

  if (repoUrl) {
    const cloneUrl = withHttpsCredentials(repoUrl, creds)
    await probeGitRemote({ url: cloneUrl, gitEnv })
    const repoExists = await pathExists(gitWorkDir)
    if (!repoExists) {
      // clone 前创建 workspace/ 等父目录，避免 git clone 因路径不存在失败
      await mkdir(dirname(gitWorkDir), { recursive: true })
      await wsGitClone({
        url: cloneUrl,
        targetPath: gitWorkDir,
        gitEnv,
      })
    } else {
      await wsGitPull({ cwd: gitWorkDir, gitEnv })
    }
  } else {
    const repoReady =
      (await pathExists(gitWorkDir)) &&
      (await isGitWorkTree(gitWorkDir, gitEnv))
    if (!repoReady) {
      console.warn(
        `[git-tool] WS_GIT_REPO_URL 未配置且 ${gitWorkDir} 不是有效 git 工作区，跳过 GitTool 运行时配置`,
      )
      resetConfigure()
      return 'skipped'
    }
  }

  configure({
    getGitWorkDir: () => gitWorkDir,
    getGitCommandEnv: () => gitEnv,
  })
  return 'ready'
}

/** runtime 重载 git-tool 模块时恢复已 prepare 的 hooks */
export function ensureConfigured(): void {
  const cached = getCachedConfigure()
  if (cached) {
    configure(cached)
  }
}
