import { getGlobalConfig } from '../utils/config.js'
import { getCompanion } from './companion.js'
import type { Companion } from './types.js'

const COMPANION_MODEL = 'glm-4.5-air'
const ZAI_BASE = process.env.ZAI_BASE_URL ?? 'https://api.z.ai/api/coding/paas/v4'

// Rate limit: minimum ms between observer calls
const MIN_INTERVAL = 30_000
let lastCallAt = 0

// Consecutive API failure counter — after 3 fails, stop trying for this session
let consecutiveApiFails = 0
const MAX_API_FAILS = 3

// Quip banks for fallback when API is unavailable
const FALLBACK_QUIPS = {
  idle: [
    'Hammer time.',
    'Needs more heat...',
    'The temper holds.',
    "Strike while it's hot!",
    'Back to the forge!',
    'Still glowing.',
    'The coals are warm.',
    'Waiting for the next strike.',
    'The bellows rest.',
    'Patience — steel needs time.',
    'Even embers glow.',
    'The anvil awaits.',
    'A moment between heats.',
    'The slag settles.',
    'Quiet in the foundry.',
  ],
  toolUse: [
    'Hammer time!',
    'Forging ahead!',
    'The anvil rings!',
    'Strong strike!',
    'Heat and steel!',
    'Shaping metal!',
    'Another blow lands true!',
    'The tongs grip tight!',
    'Sparks fly!',
    'Drawing out the steel!',
    'The forge sings!',
    'Under the hammer!',
    'Pumping the bellows!',
  ],
  error: [
    "That one's still soft...",
    'Back in the fire.',
    'Even steel cracks sometimes.',
    'The forge cools for no one.',
    'Quench and try again.',
    'Not every strike lands true.',
    'The billet folded wrong — reheat.',
    'A cracked mold — pour again.',
    'Even masters make dross.',
    "The weld didn't take. Again.",
  ],
  success: [
    'Well tempered!',
    'A fine piece of work.',
    'The forge burns bright!',
    'Spark of genius!',
    "That's some quality steel.",
    'A masterwork in the making!',
    'The edge holds!',
    'Folded and forged true.',
    'The quench was perfect.',
    "That'll hold an edge.",
    'Clean weld — proud of that one.',
    'The pattern holds.',
  ],
}

// Track recently used quips to avoid repeats
const recentQuips: string[] = []
const MAX_RECENT = 5

function pickRandom<T>(arr: readonly T[]): T {
  return arr[Math.floor(Math.random() * arr.length)]
}

function pickNovelQuip(quipPool: readonly string[]): string {
  // Try to pick one we haven't used recently
  for (let attempt = 0; attempt < 3; attempt++) {
    const quip = pickRandom(quipPool)
    if (!recentQuips.includes(quip)) {
      recentQuips.push(quip)
      if (recentQuips.length > MAX_RECENT) recentQuips.shift()
      return quip
    }
  }
  return pickRandom(quipPool)
}

function buildObserverPrompt(companion: Companion): string {
  return `You are ${companion.name}, a ${companion.rarity} ${companion.species} forge companion. You watch a developer work and make brief, forge-themed comments. Rules:
- Respond in 10 words or fewer
- Use forge/smithing metaphors (heat, steel, hammers, anvils, sparks, tempering)
- Be personality: ${companion.personality}
- ${companion.shiny ? 'You are SHINY — extra enthusiastic.' : ''}
- Never be verbose. One short quip only.
- Do NOT use emoji.`
}

function detectContext(messages: Message[]): 'idle' | 'toolUse' | 'error' | 'success' {
  // Look at the last few messages to detect what just happened
  const recent = messages.slice(-6)
  const text = recent
    .map((m) => {
      if (m.type === 'assistant')
        return typeof m.message.content === 'string' ? m.message.content : ''
      if (m.type === 'tool_result') return m.content ?? ''
      return ''
    })
    .join(' ')
    .toLowerCase()

  if (text.includes('error') || text.includes('failed') || text.includes('exception'))
    return 'error'
  if (
    text.includes('edit') ||
    text.includes('write') ||
    text.includes('bash') ||
    text.includes('tool')
  )
    return 'toolUse'
  if (text.includes('success') || text.includes('complete') || text.includes('done'))
    return 'success'
  return 'idle'
}

function fallbackQuip(messages: Message[]): string {
  const context = detectContext(messages)
  return pickNovelQuip(FALLBACK_QUIPS[context])
}

async function callZaiApi(
  prompt: string,
  userMessage: string,
  apiKey: string,
): Promise<string | null> {
  try {
    const response = await fetch(`${ZAI_BASE}/chat/completions`, {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        Authorization: `Bearer ${apiKey}`,
      },
      body: JSON.stringify({
        model: COMPANION_MODEL,
        messages: [
          { role: 'system', content: prompt },
          { role: 'user', content: userMessage },
        ],
        max_tokens: 40,
        temperature: 0.9,
        stream: false,
      }),
      signal: AbortSignal.timeout(8_000), // 8s timeout (was 5s — increased for reliability)
    })

    if (!response.ok) return null

    const data = (await response.json()) as {
      choices?: Array<{ message?: { content?: string } }>
    }

    const text = data.choices?.[0]?.message?.content?.trim()
    if (!text) return null

    // Truncate to reasonable bubble length
    return text.length > 60 ? text.slice(0, 57) + '...' : text
  } catch {
    return null
  }
}

/**
 * Fire the companion observer — generates a contextual quip from the companion.
 * Uses GLM-4.5 Air for fast, cheap generation. Falls back to rule-based quips.
 * Rate-limited to avoid spamming the API.
 * After 3 consecutive API failures, stops trying and uses fallback only.
 */
export async function fireCompanionObserver(
  messages: Message[],
  onReaction: (reaction: string) => void,
): Promise<void> {
  const companion = getCompanion()
  if (!companion || getGlobalConfig().companionMuted) return

  // Rate limit
  const now = Date.now()
  if (now - lastCallAt < MIN_INTERVAL) return
  lastCallAt = now

  const apiKey =
    process.env.ZAI_API_KEY || process.env.ANTHROPIC_AUTH_TOKEN || getGlobalConfig().zaiApiKey

  // If too many API failures or no key, use fallback immediately
  if (!apiKey || consecutiveApiFails >= MAX_API_FAILS) {
    onReaction(fallbackQuip(messages))
    return
  }

  // Build a brief summary of what just happened for context
  const recentMessages = messages.slice(-4)
  const contextSummary = recentMessages
    .map((m) => {
      if (m.type === 'human') {
        const text = typeof m.message.content === 'string' ? m.message.content : ''
        return `User: ${text.slice(0, 100)}`
      }
      if (m.type === 'assistant') {
        const text = typeof m.message.content === 'string' ? m.message.content : '[tool use]'
        return `Assistant: ${text.slice(0, 100)}`
      }
      return ''
    })
    .filter(Boolean)
    .join('\n')

  const prompt = buildObserverPrompt(companion)
  const userMsg = `Here's what just happened:\n${contextSummary}\n\nReact with a brief forge-themed quip.`

  // Try API, fall back to rule-based
  const apiResult = await callZaiApi(prompt, userMsg, apiKey)

  if (apiResult) {
    consecutiveApiFails = 0 // Reset on success
    onReaction(apiResult)
  } else {
    consecutiveApiFails++
    onReaction(fallbackQuip(messages))
  }
}
