import { describe, it, expect, beforeEach, afterEach } from 'vitest'
import { mkdtemp, readFile, readdir, rm } from 'fs/promises'
import { join } from 'path'
import { tmpdir } from 'os'

import { exportCsv, exportJson, type PeriodExport } from '../src/export.js'
import type { ProjectSummary } from '../src/types.js'

let tmpDir: string

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

afterEach(async () => {
  await rm(tmpDir, { recursive: true, force: true })
})

function makeProject(projectPath: string): ProjectSummary {
  return {
    project: projectPath,
    projectPath,
    sessions: [
      {
        sessionId: 'sess-001',
        project: projectPath,
        firstTimestamp: '2026-04-14T10:00:00Z',
        lastTimestamp: '2026-04-14T10:01:00Z',
        totalCostUSD: 1.23,
        totalInputTokens: 100,
        totalOutputTokens: 50,
        totalCacheReadTokens: 0,
        totalCacheWriteTokens: 0,
        apiCalls: 1,
        turns: [
          {
            userMessage: '=SUM(1,2)',
            timestamp: '2026-04-14T10:00:00Z',
            sessionId: 'sess-001',
            category: 'coding',
            retries: 0,
            hasEdits: true,
            assistantCalls: [
              {
                provider: 'claude',
                model: '+danger-model',
                usage: {
                  inputTokens: 100,
                  outputTokens: 50,
                  cacheCreationInputTokens: 0,
                  cacheReadInputTokens: 0,
                  cachedInputTokens: 0,
                  reasoningTokens: 0,
                  webSearchRequests: 0,
                },
                costUSD: 1.23,
                tools: ['Read'],
                mcpTools: [],
                skills: [],
                hasAgentSpawn: false,
                hasPlanMode: false,
                speed: 'standard',
                timestamp: '2026-04-14T10:00:00Z',
                bashCommands: ['@malicious'],
                deduplicationKey: 'dedup-1',
              },
            ],
          },
        ],
        modelBreakdown: {
          '+danger-model': {
            calls: 1,
            costUSD: 1.23,
            tokens: {
              inputTokens: 100,
              outputTokens: 50,
              cacheCreationInputTokens: 0,
              cacheReadInputTokens: 0,
              cachedInputTokens: 0,
              reasoningTokens: 0,
              webSearchRequests: 0,
            },
          },
        },
        toolBreakdown: {
          Read: { calls: 1 },
        },
        mcpBreakdown: {},
        bashBreakdown: {
          '@malicious': { calls: 1 },
        },
        categoryBreakdown: {
          coding: { turns: 1, costUSD: 1.23, retries: 0, editTurns: 1, oneShotTurns: 1 },
          debugging: { turns: 0, costUSD: 0, retries: 0, editTurns: 0, oneShotTurns: 0 },
          feature: { turns: 0, costUSD: 0, retries: 0, editTurns: 0, oneShotTurns: 0 },
          refactoring: { turns: 0, costUSD: 0, retries: 0, editTurns: 0, oneShotTurns: 0 },
          testing: { turns: 0, costUSD: 0, retries: 0, editTurns: 0, oneShotTurns: 0 },
          exploration: { turns: 0, costUSD: 0, retries: 0, editTurns: 0, oneShotTurns: 0 },
          planning: { turns: 0, costUSD: 0, retries: 0, editTurns: 0, oneShotTurns: 0 },
          delegation: { turns: 0, costUSD: 0, retries: 0, editTurns: 0, oneShotTurns: 0 },
          git: { turns: 0, costUSD: 0, retries: 0, editTurns: 0, oneShotTurns: 0 },
          'build/deploy': { turns: 0, costUSD: 0, retries: 0, editTurns: 0, oneShotTurns: 0 },
          conversation: { turns: 0, costUSD: 0, retries: 0, editTurns: 0, oneShotTurns: 0 },
          brainstorming: { turns: 0, costUSD: 0, retries: 0, editTurns: 0, oneShotTurns: 0 },
          general: { turns: 0, costUSD: 0, retries: 0, editTurns: 0, oneShotTurns: 0 },
        },
        skillBreakdown: {},
      },
    ],
    totalCostUSD: 1.23,
    totalApiCalls: 1,
  }
}

describe('exportCsv', () => {
  it('prefixes formula-like cells to prevent CSV injection', async () => {
    const periods: PeriodExport[] = [
      {
        label: '30 Days',
        projects: [makeProject('=cmd,calc')],
      },
    ]

    const outputPath = join(tmpDir, 'report.csv')
    const folder = await exportCsv(periods, outputPath)
    // exportCsv now writes a folder of clean one-table-per-file CSVs, so the formula-prefix
    // guard is scattered across files. Concatenate them for the assertion surface.
    const [projects, models, shell] = await Promise.all([
      readFile(join(folder, 'projects.csv'), 'utf-8'),
      readFile(join(folder, 'models.csv'), 'utf-8'),
      readFile(join(folder, 'shell-commands.csv'), 'utf-8'),
    ])
    const content = projects + models + shell

    expect(content).toContain("\"'=cmd,calc\"")
    expect(content).toContain("'+danger-model")
    expect(content).toContain("'@malicious")
  })

  it('escapes tab and carriage-return prefixes in CSV cells', async () => {
    const periods: PeriodExport[] = [
      {
        label: '30 Days',
        projects: [makeProject('\tcmd'), makeProject('\rcmd')],
      },
    ]

    const outputPath = join(tmpDir, 'tab-cr.csv')
    const folder = await exportCsv(periods, outputPath)
    const projects = await readFile(join(folder, 'projects.csv'), 'utf-8')
    expect(projects).toContain("'\tcmd")
    expect(projects).toContain("'\rcmd")
  })

  it('includes per-model efficiency metrics', async () => {
    const periods: PeriodExport[] = [
      {
        label: '30 Days',
        projects: [makeProject('app')],
      },
    ]

    const outputPath = join(tmpDir, 'models.csv')
    const folder = await exportCsv(periods, outputPath)
    const models = await readFile(join(folder, 'models.csv'), 'utf-8')

    expect(models).toContain('Edit Turns')
    expect(models).toContain('One-shot Rate (%)')
    expect(models).toContain('Retries/Edit')
    expect(models).toContain('Cost/Edit')
    expect(models).toContain(',1,100,0,')
  })

  it('does not crash when periods array is empty', async () => {
    const outputPath = join(tmpDir, 'empty.csv')
    const folder = await exportCsv([], outputPath)
    const entries = await readdir(folder)
    expect(entries.length).toBeGreaterThanOrEqual(0)
  })

  it('describes detail files without hardcoding a 30-day window', async () => {
    const periods: PeriodExport[] = [
      {
        label: '2026-04-07 to 2026-04-10',
        projects: [makeProject('app')],
      },
    ]

    const outputPath = join(tmpDir, 'custom.csv')
    const folder = await exportCsv(periods, outputPath)
    const readme = await readFile(join(folder, 'README.txt'), 'utf-8')

    expect(readme).toContain('selected detail period')
    expect(readme).not.toContain('30-day window')
  })

  it('writes MCP server usage to mcp.csv', async () => {
    const project = makeProject('app')
    project.sessions[0]!.mcpBreakdown = { node_repl: { calls: 5 } }
    const periods: PeriodExport[] = [{ label: '30 Days', projects: [project] }]

    const folder = await exportCsv(periods, join(tmpDir, 'mcp.csv'))
    const mcp = await readFile(join(folder, 'mcp.csv'), 'utf-8')

    expect(mcp).toContain('Server,Calls,Share (%)')
    expect(mcp).toContain('node_repl,5,100')
  })
})

describe('exportJson', () => {
  it('includes an mcp section with per-server usage', async () => {
    const project = makeProject('app')
    project.sessions[0]!.mcpBreakdown = { node_repl: { calls: 3 }, github: { calls: 1 } }
    const periods: PeriodExport[] = [{ label: '30 Days', projects: [project] }]

    const outputPath = join(tmpDir, 'export.json')
    const saved = await exportJson(periods, outputPath)
    const data = JSON.parse(await readFile(saved, 'utf-8'))

    expect(Array.isArray(data.mcp)).toBe(true)
    expect(data.mcp).toEqual([
      { Server: 'node_repl', Calls: 3, 'Share (%)': 75 },
      { Server: 'github', Calls: 1, 'Share (%)': 25 },
    ])
  })
})
