import { createHash } from 'node:crypto'
import { outcomeValueUsd } from '../registry.ts'
import type { ActionForDetection, OutcomeEvent } from './types.ts'

const MIN_EDIT_LOC = 50
const MIN_NEW_FILE_LOC = 100

const RE_GIT_COMMIT = /\bgit\s+commit\b/
const RE_GH_PR_CREATE = /\bgh\s+pr\s+create\b/
const RE_GH_PR_MERGE = /\bgh\s+pr\s+merge\b/
const RE_NPM_PUBLISH = /\bnpm\s+publish\b/
const RE_DEPLOY_CRON = /\bcrontab\s+-?[ei]\b|systemctl\s+enable\s|launchctl\s+bootstrap\s/
const RE_GH_RUN_OK = /\bgh\s+run\s+(watch|view|rerun)\b/
const RE_TEST_RUN =
  /\b(?:npm|pnpm|yarn|bun)\s+(?:run\s+)?test\b|\bpytest\b|\bcargo\s+test\b|\bgo\s+test\b|\bvitest(?:\s+run)?\b|\bjest\b|\bmocha\b/

/**
 * Detect Thomas outcomes from an OpenClaw model_call action.
 *
 * OpenClaw declares lowercase snake_case tool names in its system prompt:
 *   read, write, edit, exec, process, web_search, web_fetch,
 *   sessions_*, subagents, session_status, memory_*, sessions_spawn,
 *   sessions_yield
 *
 * Sub-agent identifier: extracted from the system prompt's "running inside
 * OpenClaw" + persona header if present. Sub-agent name attached to each
 * outcome event as `subAgent` for future per-persona attribution.
 */
export function detectOpenClaw(action: ActionForDetection): OutcomeEvent[] {
  if (action.kind !== 'model_call') return []
  const response = action.payload?.response
  if (!Array.isArray(response)) return []

  const subAgent = extractSubAgent(action.payload?.systemPrompt)
  const events: OutcomeEvent[] = []

  for (const block of response) {
    if (block?.type !== 'tool_use') continue
    const out = mapToolUse(block, action, subAgent)
    if (out) events.push(out)
  }

  return events
}

// eslint-disable-next-line @typescript-eslint/no-explicit-any
function mapToolUse(
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
  block: any,
  action: ActionForDetection,
  subAgent: string | undefined,
): OutcomeEvent | null {
  const name: string = block.name ?? ''
  const input = block.input ?? {}
  const toolUseId: string | undefined = typeof block.id === 'string' ? block.id : undefined

  const make = (kind: string, fingerprintSource: string): OutcomeEvent => ({
    kind,
    valueUsd: outcomeValueUsd(kind),
    fingerprint: hash(`openclaw:${kind}:${fingerprintSource}`),
    detectedAt: action.ts,
    actionId: action.id,
    ...(subAgent ? { subAgent } : {}),
    ...(toolUseId ? { toolUseId } : {}),
  })

  switch (name) {
    case 'write': {
      const content: string = input.content ?? input.text ?? ''
      const loc = content.split('\n').length
      if (loc < MIN_NEW_FILE_LOC) {
        if (loc >= MIN_EDIT_LOC) return make('file_edited', `${input.path ?? ''}|${loc}`)
        return null
      }
      return make('file_created', `${input.path ?? ''}|${loc}`)
    }

    case 'edit': {
      const loc = estimateEditLoc(input)
      if (loc < MIN_EDIT_LOC) return null
      return make('file_edited', `${input.path ?? ''}|${loc}`)
    }

    case 'exec': {
      const cmd: string = input.command ?? input.script ?? ''
      if (!cmd) return null
      if (RE_GH_PR_CREATE.test(cmd)) return make('pr_opened', cmd)
      if (RE_GH_PR_MERGE.test(cmd)) return make('pr_merged', cmd)
      if (RE_GH_RUN_OK.test(cmd)) return make('ci_passes', cmd)
      if (RE_TEST_RUN.test(cmd)) return make('test_passes', cmd)
      if (RE_GIT_COMMIT.test(cmd)) return make('commit_made', cmd)
      if (RE_NPM_PUBLISH.test(cmd)) return make('automation_deployed', cmd)
      if (RE_DEPLOY_CRON.test(cmd)) return make('automation_deployed', cmd)
      return null
    }

    case 'web_search':
    case 'web_fetch':
      return make('search_synthesized', input.query ?? input.url ?? '')

    case 'sessions_spawn':
      return make(
        'sub_task_delegated',
        `${input.persona ?? input.type ?? 'isolated'}|${(input.prompt ?? input.task ?? '').slice(0, 80)}`,
      )

    // Read-only / admin tools — no outcome
    case 'read':
    case 'process':
    case 'sessions_list':
    case 'sessions_history':
    case 'sessions_send':
    case 'subagents':
    case 'session_status':
    case 'memory_get':
    case 'memory_search':
    case 'sessions_yield':
      return null

    default:
      return null
  }
}

// eslint-disable-next-line @typescript-eslint/no-explicit-any
function estimateEditLoc(input: any): number {
  const old: string = input.old_text ?? input.old_string ?? ''
  const next: string = input.new_text ?? input.new_string ?? ''
  return Math.max(old.split('\n').length, next.split('\n').length)
}

function extractSubAgent(systemPrompt: string | undefined): string | undefined {
  if (!systemPrompt) return undefined
  // Common OpenClaw markers — TODO: refine when sub-agent personas are observed
  // in real traces. The default agent is just "OpenClaw" without a persona.
  const m =
    /^You are (?:an? )?["'`]?([A-Z][\w-]+)["'`]? (?:running |inside )/m.exec(systemPrompt) ||
    /^##\s*Agent:\s*([^\n]+)/m.exec(systemPrompt)
  if (m?.[1]) {
    const name = m[1].trim()
    return name.toLowerCase() === 'a personal assistant' ? undefined : name
  }
  return undefined
}

function hash(s: string): string {
  return createHash('sha256').update(s).digest('hex').slice(0, 16)
}
