import { createHash } from 'crypto'
import { afterEach, beforeEach, describe, expect, it } from 'vitest'
import { mkdir, mkdtemp, rm, writeFile } from 'fs/promises'
import { join } from 'path'
import { tmpdir } from 'os'

import { createKimiProvider } from '../../src/providers/kimi.js'
import type { ParsedProviderCall } from '../../src/providers/types.js'

let tmpDir: string

beforeEach(async () => {
  tmpDir = await mkdtemp(join(tmpdir(), 'kimi-test-'))
})

afterEach(async () => {
  delete process.env.KIMI_MODEL_NAME
  await rm(tmpDir, { recursive: true, force: true })
})

function md5(value: string): string {
  return createHash('md5').update(value, 'utf-8').digest('hex')
}

function record(timestamp: number, type: string, payload: Record<string, unknown>): string {
  return JSON.stringify({
    timestamp,
    message: { type, payload },
  })
}

async function writeSession(workDir: string, sessionId: string, lines: string[]): Promise<string> {
  const hash = md5(workDir)
  const sessionDir = join(tmpDir, 'sessions', hash, sessionId)
  await mkdir(sessionDir, { recursive: true })
  const wirePath = join(sessionDir, 'wire.jsonl')
  await writeFile(wirePath, [
    JSON.stringify({ type: 'metadata', protocol_version: '2' }),
    ...lines,
  ].join('\n') + '\n')
  return wirePath
}

async function collect(provider: ReturnType<typeof createKimiProvider>, path: string, seen = new Set<string>()): Promise<ParsedProviderCall[]> {
  const parser = provider.createSessionParser({ path, project: 'app', provider: 'kimi' }, seen)
  const calls: ParsedProviderCall[] = []
  for await (const call of parser.parse()) calls.push(call)
  return calls
}

describe('Kimi provider', () => {
  it('discovers session and subagent wire logs under KIMI_SHARE_DIR layout', async () => {
    const workDir = '/Users/test/work/app'
    const hash = md5(workDir)
    await writeFile(join(tmpDir, 'kimi.json'), JSON.stringify({
      work_dirs: [{ path: workDir, kaos: 'local', last_session_id: 'sess-1' }],
    }))

    const sessionDir = join(tmpDir, 'sessions', hash, 'sess-1')
    const subagentDir = join(sessionDir, 'subagents', 'agent-1')
    await mkdir(subagentDir, { recursive: true })
    await writeFile(join(sessionDir, 'wire.jsonl'), '\n')
    await writeFile(join(subagentDir, 'wire.jsonl'), '\n')

    const sources = await createKimiProvider(tmpDir).discoverSessions()

    expect(sources).toHaveLength(2)
    expect(sources.map(s => s.project)).toEqual(['app', 'app'])
    expect(sources.map(s => s.provider)).toEqual(['kimi', 'kimi'])
    expect(sources.map(s => s.path).sort()).toEqual([
      join(sessionDir, 'subagents', 'agent-1', 'wire.jsonl'),
      join(sessionDir, 'wire.jsonl'),
    ].sort())
  })

  it('parses Kimi wire StatusUpdate usage, tools, bash commands, and configured model', async () => {
    await writeFile(join(tmpDir, 'config.toml'), [
      'default_model = "kimi-code/k2"',
      '',
      '[models."kimi-code/k2"]',
      'model = "kimi-k2-thinking-turbo"',
    ].join('\n'))

    const wirePath = await writeSession('/Users/test/work/app', 'sess-1', [
      record(1776162400, 'TurnBegin', { user_input: 'add status endpoint' }),
      record(1776162401, 'ToolCall', {
        type: 'function',
        id: 'call-shell',
        function: { name: 'Shell', arguments: JSON.stringify({ command: 'git status && npm test' }) },
      }),
      record(1776162402, 'ToolCall', {
        type: 'function',
        id: 'call-read',
        function: { name: 'ReadFile', arguments: JSON.stringify({ path: 'src/index.ts' }) },
      }),
      record(1776162403, 'StatusUpdate', {
        message_id: 'msg-1',
        token_usage: {
          input_other: 100,
          input_cache_read: 25,
          input_cache_creation: 10,
          output: 40,
        },
      }),
    ])

    const calls = await collect(createKimiProvider(tmpDir), wirePath)

    expect(calls).toHaveLength(1)
    expect(calls[0]).toMatchObject({
      provider: 'kimi',
      model: 'kimi-k2-thinking-turbo',
      inputTokens: 100,
      outputTokens: 40,
      cacheReadInputTokens: 25,
      cacheCreationInputTokens: 10,
      cachedInputTokens: 25,
      tools: ['Bash', 'Read'],
      bashCommands: ['git', 'npm'],
      timestamp: '2026-04-14T10:26:43.000Z',
      deduplicationKey: 'kimi:sess-1:msg-1',
      userMessage: 'add status endpoint',
      sessionId: 'sess-1',
    })
    expect(calls[0]!.costUSD).toBeGreaterThan(0)
  })

  it('uses content parts, model payload overrides, and message-id deduplication', async () => {
    process.env.KIMI_MODEL_NAME = 'kimi-k2-thinking'
    const wirePath = await writeSession('/Users/test/work/app', 'sess-2', [
      record(1776023300, 'TurnBegin', {
        user_input: [
          { type: 'text', text: 'refactor parser' },
          { type: 'image_url', image_url: { url: 'file://diagram.png' } },
          { type: 'text', text: 'carefully' },
        ],
      }),
      record(1776023301, 'ToolCallRequest', {
        id: 'call-write',
        name: 'WriteFile',
        arguments: JSON.stringify({ path: 'src/parser.ts', content: 'x' }),
      }),
      record(1776023302, 'StatusUpdate', {
        message_id: 'msg-2',
        model_name: 'kimi-k2.6',
        token_usage: { input_other: 5, output: 7 },
      }),
      record(1776023303, 'StatusUpdate', {
        message_id: 'msg-2',
        model_name: 'kimi-k2.6',
        token_usage: { input_other: 5, output: 7 },
      }),
    ])

    const calls = await collect(createKimiProvider(tmpDir), wirePath)

    expect(calls).toHaveLength(1)
    expect(calls[0]).toMatchObject({
      model: 'kimi-k2.6',
      userMessage: 'refactor parser carefully',
      tools: ['Write'],
      deduplicationKey: 'kimi:sess-2:msg-2',
    })
  })

  it('skips non-usage updates and supports legacy input total fields defensively', async () => {
    const wirePath = await writeSession('/Users/test/work/app', 'sess-3', [
      record(1776023400, 'TurnBegin', { user_input: 'summarize' }),
      record(1776023401, 'StatusUpdate', { context_usage: 0.5 }),
      record(1776023402, 'StatusUpdate', {
        message_id: 'msg-3',
        token_usage: {
          input: 120,
          input_cache_read: 30,
          input_cache_creation: 10,
          output_tokens: 20,
        },
      }),
    ])

    const calls = await collect(createKimiProvider(tmpDir), wirePath)

    expect(calls).toHaveLength(1)
    expect(calls[0]).toMatchObject({
      inputTokens: 80,
      cacheReadInputTokens: 30,
      cacheCreationInputTokens: 10,
      outputTokens: 20,
      model: 'kimi-auto',
    })
  })
})
