import type Anthropic from '@sandcode/sdk'
import type { ClientOptions } from '@sandcode/sdk'
import { getUserAgent } from 'src/utils/http.js'
import { getAPIProvider, getCustomProviderConfig } from 'src/utils/model/providers.js'
import { getGlobalConfig } from 'src/utils/config.js'
import { getProxyFetchOptions } from 'src/utils/proxy.js'
import { getSessionId } from '../../bootstrap/state.js'
import { logForDebugging } from '../../utils/debug.js'
import { createOpenAICompatClient, type OpenAICompatProviderConfig } from './openaiCompatAdapter.js'
import { isEnvTruthy } from '../../utils/envUtils.js'

/**
 * Environment variables for supported providers:
 *
 * z.ai (default):
 * - ZAI_API_KEY: Required. Your z.ai API key.
 * - ZAI_BASE_URL: Optional override (default: Coding Plan endpoint).
 * - ZAI_CODING_PLAN=0: Opt out of Coding Plan endpoint.
 *
 * OpenAI:
 * - OPENAI_API_KEY, OPENAI_BASE_URL
 *
 * Gemini (OpenAI-compat endpoint):
 * - GEMINI_API_KEY, GEMINI_BASE_URL
 *
 * DeepSeek:
 * - DEEPSEEK_API_KEY, DEEPSEEK_BASE_URL
 *
 * Mistral:
 * - MISTRAL_API_KEY, MISTRAL_BASE_URL
 *
 * OpenRouter:
 * - OPENROUTER_API_KEY
 *
 * Generic OpenAI-compat (Ollama, vLLM, etc.):
 * - OPENAI_COMPAT_API_KEY, OPENAI_COMPAT_BASE_URL
 *
 * Custom providers (add via /provider add):
 * - Stored in global config, take priority over built-ins.
 *
 * Shared:
 * - SANDCODE_DEFAULT_MODEL: Override default model for the active provider.
 * - API_TIMEOUT_MS: Override request timeout (default: 10 min, or 50 min for zai).
 * - ANTHROPIC_CUSTOM_HEADERS: Newline-separated `Name: Value` pairs for extra headers.
 */

export async function getAnthropicClient({
  maxRetries,
  fetchOverride,
  source,
}: {
  apiKey?: string
  maxRetries: number
  model?: string
  fetchOverride?: ClientOptions['fetch']
  source?: string
}): Promise<Anthropic> {
  const containerId = process.env.SANDCODE_CONTAINER_ID
  const remoteSessionId = process.env.SANDCODE_REMOTE_SESSION_ID
  const clientApp = process.env.SANDCODE_AGENT_SDK_CLIENT_APP
  const customHeaders = getCustomHeaders()
  const defaultHeaders: { [key: string]: string } = {
    'x-app': 'cli',
    'User-Agent': getUserAgent(),
    'X-Sandcode-Session-Id': getSessionId(),
    ...customHeaders,
    ...(containerId ? { 'x-sandcode-remote-container-id': containerId } : {}),
    ...(remoteSessionId ? { 'x-sandcode-remote-session-id': remoteSessionId } : {}),
    ...(clientApp ? { 'x-client-app': clientApp } : {}),
  }

  const resolvedFetch = buildFetch(fetchOverride, source)

  // z.ai Coding Plan extended operations may legitimately take ~50 min; all
  // other providers default to 10 min. Override with API_TIMEOUT_MS.
  const defaultTimeoutMs = getAPIProvider() === 'zai' ? 3_000_000 : 600 * 1000
  const sharedArgs = {
    defaultHeaders,
    maxRetries,
    timeout: parseInt(process.env.API_TIMEOUT_MS || String(defaultTimeoutMs), 10),
    fetchOptions: getProxyFetchOptions({
      forAnthropicAPI: false,
    }) as ClientOptions['fetchOptions'],
    ...(resolvedFetch && { fetch: resolvedFetch }),
  }

  const provider = getAPIProvider()

  // Custom providers (user-added via /provider add) — check FIRST so they
  // take priority over built-in provider names.
  const customProviderCfg = getCustomProviderConfig()
  if (customProviderCfg) {
    const config: OpenAICompatProviderConfig = {
      name: customProviderCfg.name,
      baseUrl: customProviderCfg.baseUrl,
      apiKey: customProviderCfg.apiKey,
      defaultModel: process.env.SANDCODE_DEFAULT_MODEL ?? customProviderCfg.defaultModel,
      extraHeaders: customProviderCfg.extraHeaders,
    }
    logForDebugging(
      `[API:custom:${config.name}] apiKey=${config.apiKey ? 'set' : 'missing'}, baseURL=${config.baseUrl}`,
    )
    return createOpenAICompatClient({ ...config, ...sharedArgs })
  }

  const builtIn = BUILT_IN_PROVIDERS[provider]
  if (builtIn) {
    const config = builtIn()
    logForDebugging(
      `[API:${config.name}] apiKey=${config.apiKey ? 'set' : 'missing'}, baseURL=${config.baseUrl}`,
    )
    return createOpenAICompatClient({ ...config, ...sharedArgs })
  }

  throw new Error(
    `No provider configured for "${provider}". Run /provider add to configure a provider, or set ZAI_API_KEY for z.ai (default).`,
  )
}

type ProviderFactory = () => OpenAICompatProviderConfig

/**
 * Built-in OpenAI-compatible providers. Adding a new provider = add a factory
 * here and surface it in getAPIProvider()'s return union.
 */
const BUILT_IN_PROVIDERS: Record<string, ProviderFactory | undefined> = {
  zai: () => {
    const isCodingPlan = isEnvTruthy(process.env.ZAI_CODING_PLAN ?? '1')
    const defaultBaseUrl = isCodingPlan
      ? 'https://api.z.ai/api/coding/paas/v4'
      : 'https://api.z.ai/api/paas/v4'
    return {
      name: 'zai',
      baseUrl: process.env.ZAI_BASE_URL ?? defaultBaseUrl,
      apiKey: process.env.ZAI_API_KEY ?? getGlobalConfig().zaiApiKey ?? '',
      defaultModel: process.env.SANDCODE_DEFAULT_MODEL ?? 'GLM-5.1',
      hasReasoningContent: true,
      alwaysIncludesUsage: true,
      supportsToolStream: true,
      preserveThinking: true,
    }
  },
  openai: () => ({
    name: 'openai',
    baseUrl: process.env.OPENAI_BASE_URL ?? 'https://api.openai.com/v1',
    apiKey: process.env.OPENAI_API_KEY ?? getGlobalConfig().openaiApiKey ?? '',
    defaultModel: process.env.SANDCODE_DEFAULT_MODEL ?? 'gpt-4o',
    hasReasoningContent: true,
    maxTokensParam: 'max_tokens',
  }),
  gemini: () => ({
    name: 'gemini',
    baseUrl:
      process.env.GEMINI_BASE_URL ?? 'https://generativelanguage.googleapis.com/v1beta/openai',
    apiKey: process.env.GEMINI_API_KEY ?? getGlobalConfig().geminiApiKey ?? '',
    defaultModel: process.env.SANDCODE_DEFAULT_MODEL ?? 'gemini-2.5-pro',
  }),
  deepseek: () => ({
    name: 'deepseek',
    baseUrl: process.env.DEEPSEEK_BASE_URL ?? 'https://api.deepseek.com',
    apiKey: process.env.DEEPSEEK_API_KEY ?? getGlobalConfig().deepseekApiKey ?? '',
    defaultModel: process.env.SANDCODE_DEFAULT_MODEL ?? 'deepseek-chat',
    hasReasoningContent: true,
    preserveThinking: true,
  }),
  mistral: () => ({
    name: 'mistral',
    baseUrl: process.env.MISTRAL_BASE_URL ?? 'https://api.mistral.ai/v1',
    apiKey: process.env.MISTRAL_API_KEY ?? getGlobalConfig().mistralApiKey ?? '',
    defaultModel: process.env.SANDCODE_DEFAULT_MODEL ?? 'mistral-large-latest',
  }),
  openrouter: () => ({
    name: 'openrouter',
    baseUrl: 'https://openrouter.ai/api/v1',
    apiKey: process.env.OPENROUTER_API_KEY ?? getGlobalConfig().openrouterApiKey ?? '',
    defaultModel: process.env.SANDCODE_DEFAULT_MODEL ?? 'openai/gpt-4o',
    extraHeaders: {
      'HTTP-Referer': 'https://sandcode.sh',
      'X-Title': 'SANDCODE',
    },
  }),
  'openai-compat': () => ({
    name: 'openai-compat',
    baseUrl:
      process.env.OPENAI_COMPAT_BASE_URL ??
      getGlobalConfig().openaiCompatBaseUrl ??
      'http://localhost:11434/v1',
    apiKey: process.env.OPENAI_COMPAT_API_KEY ?? getGlobalConfig().openaiCompatApiKey ?? 'ollama',
    defaultModel:
      process.env.SANDCODE_DEFAULT_MODEL ?? getGlobalConfig().openaiCompatDefaultModel ?? 'default',
  }),
}

function getCustomHeaders(): Record<string, string> {
  const customHeaders: Record<string, string> = {}
  const customHeadersEnv = process.env.ANTHROPIC_CUSTOM_HEADERS

  if (!customHeadersEnv) return customHeaders

  const headerStrings = customHeadersEnv.split(/\n|\r\n/)
  for (const headerString of headerStrings) {
    if (!headerString.trim()) continue
    // Split on first `:` to avoid regex backtracking on malformed long lines.
    const colonIdx = headerString.indexOf(':')
    if (colonIdx === -1) continue
    const name = headerString.slice(0, colonIdx).trim()
    const value = headerString.slice(colonIdx + 1).trim()
    if (name) customHeaders[name] = value
  }

  return customHeaders
}

function buildFetch(
  fetchOverride: ClientOptions['fetch'],
  source: string | undefined,
): ClientOptions['fetch'] {
  // eslint-disable-next-line eslint-plugin-n/no-unsupported-features/node-builtins
  const inner = fetchOverride ?? globalThis.fetch
  return (input, init) => {
    try {
      // eslint-disable-next-line eslint-plugin-n/no-unsupported-features/node-builtins
      const url = input instanceof Request ? input.url : String(input)
      logForDebugging(`[API REQUEST] ${new URL(url).pathname} source=${source ?? 'unknown'}`)
    } catch {
      // never let logging crash the fetch
    }
    return inner(input, init)
  }
}
