import { existsSync, mkdtempSync, rmSync } from 'node:fs'
import { tmpdir } from 'node:os'
import { join } from 'node:path'
import Database from 'better-sqlite3'
import { afterAll, describe, expect, it } from 'vitest'
import { newActionId, newRunId } from '../../core/ids.ts'
import type { AgentPacket } from '../../core/packet.ts'

const tmp = mkdtempSync(join(tmpdir(), 'thomas-archive-'))
process.env.THOMAS_HOME = tmp

const { appendPacket } = await import('./writer.ts')
const { runPrune } = await import('./prune.ts')
const { getRawDb, closeDb } = await import('./db.ts')
const { paths } = await import('../../core/paths.ts')

afterAll(() => {
  closeDb()
  rmSync(tmp, { recursive: true, force: true })
})

const DAY_A = Date.parse('2026-01-10T12:00:00Z')
const DAY_B = Date.parse('2026-01-11T12:00:00Z')
const RECENT = Date.parse('2026-03-01T12:00:00Z')
const CUTOFF = Date.parse('2026-02-01T00:00:00Z')

function packet(ts: number): AgentPacket {
  return {
    id: newActionId(),
    runId: newRunId(),
    threadId: 'th_archivetest',
    ts,
    durMs: 100,
    sourceAgent: 'claude-code',
    cost: { usd: 0.01, tokensIn: 10, tokensOut: 5 },
    payload: {
      kind: 'model_call',
      protocol: 'anthropic',
      endpoint: 'https://api.anthropic.com/v1/messages',
      model: 'claude-opus-4-7',
      messages: [{ role: 'user', content: `at ${ts}` }],
      stream: false,
      status: 200,
      response: [{ type: 'text', text: 'ok' }],
    },
  }
}

describe('prune archives evicted data into per-day files', () => {
  it('writes archive/YYYY-MM-DD.db before deleting, keeps recent data live', async () => {
    await appendPacket(packet(DAY_A))
    await appendPacket(packet(DAY_B))
    await appendPacket(packet(RECENT))

    const result = await runPrune(CUTOFF)

    // Two old days archived; recent survives.
    expect(result.archivedDays.sort()).toEqual(['2026-01-10', '2026-01-11'])
    expect(result.deletedActions).toBe(2)

    // Active store: only the recent action remains.
    const raw = await getRawDb()
    const live = raw.prepare('SELECT COUNT(*) AS n FROM actions').get() as { n: number }
    expect(live.n).toBe(1)

    // Each archive file exists and holds exactly that day's action + payload.
    for (const day of ['2026-01-10', '2026-01-11']) {
      const file = join(paths.wire.tracesArchive, `${day}.db`)
      expect(existsSync(file)).toBe(true)
      const adb = new Database(file, { readonly: true })
      try {
        const a = adb.prepare('SELECT COUNT(*) AS n FROM actions').get() as { n: number }
        const p = adb.prepare('SELECT COUNT(*) AS n FROM action_payloads').get() as { n: number }
        const t = adb.prepare('SELECT COUNT(*) AS n FROM threads').get() as { n: number }
        expect(a.n).toBe(1)
        expect(p.n).toBe(1)
        expect(t.n).toBe(1) // the shared thread is copied into each day archive
      } finally {
        adb.close()
      }
    }
  })

  it('is idempotent — re-archiving the same day does not duplicate', async () => {
    // Re-insert a row on an already-archived day, prune again.
    await appendPacket(packet(DAY_A + 1000))
    const result = await runPrune(CUTOFF)
    expect(result.archivedDays).toContain('2026-01-10')

    const file = join(paths.wire.tracesArchive, '2026-01-10.db')
    const adb = new Database(file, { readonly: true })
    try {
      // First archive had 1 action; now 2 (the new row), no dupes of the first.
      const a = adb.prepare('SELECT COUNT(*) AS n FROM actions').get() as { n: number }
      expect(a.n).toBe(2)
    } finally {
      adb.close()
    }
  })
})
