import type { McpServerSettings } from './schema'
import { resolveDimcodeConfigDir } from '#cli/core/storage-paths'
import { normalizeNum, normalizeStr } from './user-store'

type RuntimeGlobals = typeof globalThis & {
  process?: NodeJS.Process
}

type GoatChainStoreLike = Readonly<{
  state?: {
    tools?: {
      websearch?: {
        apiKey?: string
        apiEndpoint?: string
        numResults?: number
      }
    }
    mcp?: {
      servers?: McpServerSettings[]
    }
  }
}>

type ToolsHydrationState = {
  tools?: {
    websearch?: {
      apiKey?: string
      apiEndpoint?: string
      numResults?: number
    }
  }
  mcp?: {
    servers?: McpServerSettings[]
  }
}

export type McpJsonV1 = Readonly<{
  mcpServers: Record<string, unknown>
}>

export type DimcodeUserToolsV1 = Readonly<{
  version: 1
  updatedAt: number
  settings?: Readonly<{
    websearch?: Readonly<{
      apiKey?: string
      apiEndpoint?: string
      numResults?: number
    }>
    mcpServers?: ReadonlyArray<McpServerSettings>
  }>
}>

const DEFAULT_TOOLS_CONFIG = {
  websearch: {
    apiKey: '',
    apiEndpoint: 'https://google.serper.dev/search?format=json',
    numResults: 10,
  },
  mcpServers: [] as McpServerSettings[],
} as const

function runtimeGlobals(): RuntimeGlobals {
  return globalThis as RuntimeGlobals
}

function runtimePidLabel(): string {
  const pid = runtimeGlobals().process?.pid
  return typeof pid === 'number' ? String(pid) : 'p'
}

function isRecord(value: unknown): value is Record<string, unknown> {
  return value != null && typeof value === 'object' && !Array.isArray(value)
}

function isDimcodeUserToolsV1(value: unknown): value is DimcodeUserToolsV1 {
  return isRecord(value) && value.version === 1
}

export async function loadDimcodeUserTools(
  env: Record<string, string | undefined>,
): Promise<{ dir: string, filePath: string, tools: DimcodeUserToolsV1 }> {
  const path = await import('node:path')
  const dir = resolveDimcodeConfigDir(env)
  const filePath = path.join(dir, 'tools.json')
  try {
    const { readFile } = await import('node:fs/promises')
    const raw = await readFile(filePath, 'utf8')
    const parsed = JSON.parse(raw)
    if (isDimcodeUserToolsV1(parsed))
      return { dir, filePath, tools: parsed }
  }
  catch {
    // ignore
  }
  return { dir, filePath, tools: { version: 1, updatedAt: 0 } as const }
}

export async function saveDimcodeUserTools(
  filePath: string,
  tools: DimcodeUserToolsV1,
): Promise<void> {
  const [{ mkdir, writeFile, rename, chmod }, path] = await Promise.all([
    import('node:fs/promises'),
    import('node:path'),
  ])
  const dir = path.dirname(filePath)
  await mkdir(dir, { recursive: true })
  const pid = runtimePidLabel()
  const nonce = `${Date.now()}-${Math.random().toString(16).slice(2)}`
  const tmp = `${filePath}.${pid}.${nonce}.tmp`
  const data = `${JSON.stringify(tools, null, 2)}\n`
  await writeFile(tmp, data, { encoding: 'utf8', mode: 0o600 })
  await rename(tmp, filePath)
  try {
    await chmod(filePath, 0o600)
  }
  catch {
    // ignore
  }
}

export function getMcpJsonPath(dimcodeDir: string): string {
  // Returns path to mcp.json in the same directory as tools.json
  // e.g. ~/.dimcode/mcp.json
  return `${dimcodeDir}/mcp.json`
}

export async function loadMcpJson(
  env: Record<string, string | undefined>,
): Promise<{ dir: string, filePath: string, servers: McpServerSettings[] }> {
  const path = await import('node:path')
  const dir = resolveDimcodeConfigDir(env)
  const filePath = path.join(dir, 'mcp.json')
  try {
    const { readFile } = await import('node:fs/promises')
    const raw = await readFile(filePath, 'utf8')
    const parsed = JSON.parse(raw)
    if (isRecord(parsed)) {
      const mcpServers = parsed.mcpServers
      if (Array.isArray(mcpServers)) {
        return { dir, filePath, servers: mcpServers as McpServerSettings[] }
      }
      // If mcpServers is an object (map format), convert keys to array with name
      if (isRecord(mcpServers)) {
        const servers: McpServerSettings[] = []
        for (const [name, value] of Object.entries(mcpServers)) {
          if (!isRecord(value))
            continue
          servers.push({ ...value, name } as McpServerSettings)
        }
        return { dir, filePath, servers }
      }
    }
  }
  catch {
    // ignore - file doesn't exist yet
  }
  return { dir, filePath, servers: [] }
}

export async function saveMcpJson(
  filePath: string,
  servers: readonly McpServerSettings[],
): Promise<void> {
  const [{ mkdir, writeFile, rename, chmod }, path] = await Promise.all([
    import('node:fs/promises'),
    import('node:path'),
  ])
  const dir = path.dirname(filePath)
  await mkdir(dir, { recursive: true })
  const pid = runtimePidLabel()
  const nonce = `${Date.now()}-${Math.random().toString(16).slice(2)}`
  const tmp = `${filePath}.${pid}.${nonce}.tmp`
  const data = `${JSON.stringify({ mcpServers: servers }, null, 2)}\n`
  await writeFile(tmp, data, { encoding: 'utf8', mode: 0o600 })
  await rename(tmp, filePath)
  try {
    await chmod(filePath, 0o600)
  }
  catch {
    // ignore
  }
}

export function buildUserToolsFromStore(
  opts: Readonly<{
    prev: DimcodeUserToolsV1
    store: GoatChainStoreLike
  }>,
): DimcodeUserToolsV1 {
  const prevSettings = opts.prev.settings ?? {}
  const storeState = opts.store.state
  const toolsState = storeState?.tools
  const websearch = toolsState?.websearch ?? {}
  const mcpServers = Array.isArray(storeState?.mcp?.servers)
    ? storeState.mcp.servers
    : []

  return {
    version: 1,
    updatedAt: Date.now(),
    settings: {
      ...prevSettings,
      websearch: {
        apiKey: websearch.apiKey ?? undefined,
        apiEndpoint: websearch.apiEndpoint ?? undefined,
        numResults: websearch.numResults ?? undefined,
      },
      mcpServers: mcpServers.length
        ? (JSON.parse(JSON.stringify(mcpServers)) as McpServerSettings[])
        : undefined,
    },
  }
}

export function hydrateToolsStateFromConfig(
  config: DimcodeUserToolsV1,
  state: ToolsHydrationState,
  mcpJsonServers?: McpServerSettings[],
): void {
  const saved = config.settings ?? {}
  const websearch = saved.websearch ?? {}
  // Prefer mcp.json servers; fall back to tools.json for backward compat
  const mcpServers = mcpJsonServers && mcpJsonServers.length
    ? mcpJsonServers
    : Array.isArray(saved.mcpServers) ? saved.mcpServers : []

  if (!state.tools)
    return

  if (state.tools.websearch) {
    state.tools.websearch.apiKey = normalizeStr(websearch.apiKey) ?? ''
    state.tools.websearch.apiEndpoint
      = normalizeStr(websearch.apiEndpoint)
        ?? DEFAULT_TOOLS_CONFIG.websearch.apiEndpoint
    state.tools.websearch.numResults
      = normalizeNum(websearch.numResults)
        ?? DEFAULT_TOOLS_CONFIG.websearch.numResults
  }

  if (!state.mcp)
    state.mcp = { servers: [] }

  state.mcp.servers = mcpServers.length
    ? JSON.parse(JSON.stringify(mcpServers))
    : [...DEFAULT_TOOLS_CONFIG.mcpServers]
}
