// `openthomas tool` — manage harness-provided tool providers (web_search
// backends today, more capabilities later). Thin client over the
// daemon's /api/tool-providers surface, mirroring `openthomas route` for
// model providers. Honors the CLI ↔ dashboard parity rule: every UI
// affordance is reachable from the terminal too.

import process from 'node:process'
import { Command } from 'commander'
import { logger } from '../../core/logger.ts'
import { DAEMON_BASE_URL } from '../../core/paths.ts'
import type { SearchBackend, ToolKind, ToolProvider } from '../../core/tool-providers.ts'
import { promptSecret } from '../util/prompt.ts'

const BACKENDS: SearchBackend[] = ['duckduckgo', 'brave', 'searxng', 'tavily', 'baidu']
const KINDS: ToolKind[] = ['web_search']
const KEYED_BACKENDS = new Set<SearchBackend>(['brave', 'tavily', 'baidu'])

type ToolProvidersResponse = {
  providers: ToolProvider[]
  active: Partial<Record<ToolKind, string>>
  secretRefs: string[]
}

async function apiFetch<T>(method: string, path: string, body?: unknown): Promise<T> {
  let res: Response
  try {
    res = await fetch(`${DAEMON_BASE_URL}${path}`, {
      method,
      ...(body !== undefined
        ? { headers: { 'content-type': 'application/json' }, body: JSON.stringify(body) }
        : {}),
      signal: AbortSignal.timeout(5000),
    })
  } catch {
    throw new Error(
      'cannot reach the OpenThomas daemon — start it with `openthomas daemon` or `openthomas wire`',
    )
  }
  const text = await res.text()
  let json: unknown = {}
  if (text) {
    try {
      json = JSON.parse(text)
    } catch {
      json = {}
    }
  }
  if (!res.ok) {
    const msg =
      json && typeof json === 'object' && 'error' in json
        ? String((json as { error: unknown }).error)
        : `HTTP ${res.status}`
    throw new Error(msg)
  }
  return json as T
}

function fail(message: string): void {
  logger.print(`error: ${message}`)
  process.exitCode = 1
}

async function run(fn: () => Promise<void>): Promise<void> {
  try {
    await fn()
  } catch (e) {
    fail((e as Error).message)
  }
}

// ── tool list ─────────────────────────────────────────────────────

const listCmd = new Command('list')
  .description('List tool providers, grouped by capability kind.')
  .action(() =>
    run(async () => {
      const r = await apiFetch<ToolProvidersResponse>('GET', '/api/tool-providers')
      if (r.providers.length === 0) {
        logger.print('No tool providers.')
        return
      }
      for (const kind of KINDS) {
        const ps = r.providers.filter((p) => p.kind === kind)
        if (ps.length === 0) continue
        const active = r.active[kind]
        logger.print(`${kind}:`)
        for (const p of ps) {
          const marker = p.id === active ? '*' : ' '
          const meta = [p.backend, p.origin === 'seeded' ? 'built-in' : '', p.authRef ?? '']
            .filter(Boolean)
            .join(' · ')
          logger.print(`  ${marker} ${p.id.padEnd(20)} ${meta}`)
        }
      }
    }),
  )

// ── tool add ──────────────────────────────────────────────────────

type AddOpts = {
  backend: string
  kind?: string
  label?: string
  baseUrl?: string
  key?: string
  costPerCall?: string
}

const addCmd = new Command('add')
  .description('Add a tool provider (or update an existing one with the same id).')
  .argument('<id>', 'provider id, e.g. my-brave')
  .requiredOption('--backend <backend>', `one of ${BACKENDS.join(' | ')}`)
  .option('--kind <kind>', `capability kind (default web_search). one of ${KINDS.join(' | ')}`, 'web_search')
  .option('--label <text>', 'display label')
  .option('--base-url <url>', 'backend base URL (SearXNG)')
  .option('--key <value>', 'API key (prompted without echo if omitted for keyed backends)')
  .option('--cost-per-call <usd>', 'USD per call — rolls up into run cost (0 = free)')
  .action((id: string, opts: AddOpts) =>
    run(async () => {
      if (!BACKENDS.includes(opts.backend as SearchBackend)) {
        return fail(`--backend must be one of ${BACKENDS.join(', ')}`)
      }
      const kind = (opts.kind ?? 'web_search') as ToolKind
      if (!KINDS.includes(kind)) {
        return fail(`--kind must be one of ${KINDS.join(', ')}`)
      }
      let apiKey = opts.key
      if (KEYED_BACKENDS.has(opts.backend as SearchBackend) && !apiKey) {
        apiKey = await promptSecret(`API key for ${id}: `)
        if (!apiKey) return fail('an API key is required for this backend')
      }
      const costNum = opts.costPerCall != null ? Number(opts.costPerCall) : undefined
      const costToSend =
        costNum != null && Number.isFinite(costNum) && costNum >= 0 ? costNum : undefined
      await apiFetch('POST', '/api/tool-providers', {
        id,
        kind,
        backend: opts.backend,
        ...(opts.label ? { label: opts.label } : {}),
        ...(opts.baseUrl ? { baseUrl: opts.baseUrl } : {}),
        ...(apiKey ? { apiKey } : {}),
        ...(costToSend != null ? { costPerCall: costToSend } : {}),
      })
      logger.print(
        `✓ tool provider ${id} (${opts.backend}) registered. ` +
          `Pin it to a route with: openthomas route capability set <route> ${kind} local --provider ${id}, ` +
          `or pick it on the Routing page's Capabilities row for the agent.`,
      )
    }),
  )

// ── tool rm ───────────────────────────────────────────────────────

const rmCmd = new Command('rm')
  .description('Remove a tool provider (and its stored key, if any).')
  .argument('<id>', 'provider id')
  .action((id: string) =>
    run(async () => {
      await apiFetch('DELETE', `/api/tool-providers?id=${encodeURIComponent(id)}`)
      logger.print(`✓ tool provider ${id} removed`)
    }),
  )

// ── tool use ──────────────────────────────────────────────────────

const useCmd = new Command('use')
  .description('Set the active provider for a kind. The MCP binary reads this on every call.')
  .argument('<kind>', `capability kind, one of ${KINDS.join(' | ')}`)
  .argument('<providerId>', 'tool-provider id to make active for that kind')
  .action((kind: string, providerId: string) =>
    run(async () => {
      if (!KINDS.includes(kind as ToolKind)) {
        return fail(`<kind> must be one of ${KINDS.join(', ')}`)
      }
      await apiFetch('POST', '/api/tool-providers/active', { kind, providerId })
      logger.print(`✓ ${kind} → ${providerId}`)
    }),
  )

// ── tool ──────────────────────────────────────────────────────────

export const toolCommand = new Command('tool')
  .description(
    'Manage OpenThomas tool providers (web_search backends today). Mirrors the Tool providers dashboard.',
  )
  .addCommand(listCmd)
  .addCommand(addCmd)
  .addCommand(rmCmd)
  .addCommand(useCmd)
