/**
 * End-to-end integration tests for the receipts persistence layer.
 *
 * These exist to catch the class of regression where the pure helpers
 * stay correct but the wire-in to disk or the Tool call path breaks —
 * the kind of bug where `receipts.ts` still unit-tests green but
 * `node dist/cli.js` fails to write a receipt on a real edit.
 */

import { describe, it, expect, beforeEach, afterEach } from 'vitest'
import { readFile, rm } from 'fs/promises'
import { existsSync } from 'fs'
import { mkdtemp } from 'fs/promises'
import { tmpdir } from 'os'
import { join } from 'path'
import { runWithCwdOverride } from '../../../utils/cwd.js'
import { appendReceipt, createReceipt, listReceipts, parseReceiptsJsonl } from '../receipts.js'

let scratch: string

beforeEach(async () => {
  scratch = await mkdtemp(join(tmpdir(), 'sandcode-receipts-'))
})

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

describe('receipts persistence (integration)', () => {
  it('writes a single receipt to .sandcode/receipts.jsonl under the active cwd', async () => {
    await runWithCwdOverride(scratch, async () => {
      const receipt = createReceipt({
        sessionId: 'sess-1',
        absoluteFilePath: join(scratch, 'src', 'foo.ts'),
        operation: 'edit',
        contentBefore: 'old',
        contentAfter: 'new',
        cwd: scratch,
      })
      await appendReceipt(receipt)
    })

    const path = join(scratch, '.sandcode', 'receipts.jsonl')
    expect(existsSync(path)).toBe(true)
    const raw = await readFile(path, 'utf-8')
    const parsed = parseReceiptsJsonl(raw)
    expect(parsed).toHaveLength(1)
    expect(parsed[0].operation).toBe('edit')
  })

  it('appends multiple receipts across calls (newline-delimited)', async () => {
    await runWithCwdOverride(scratch, async () => {
      for (let i = 0; i < 3; i++) {
        await appendReceipt(
          createReceipt({
            sessionId: 'sess',
            absoluteFilePath: join(scratch, `f${i}.ts`),
            operation: 'write',
            contentBefore: null,
            contentAfter: `v${i}`,
            cwd: scratch,
          }),
        )
      }
      const out = await listReceipts(50)
      expect(out).toHaveLength(3)
      // listReceipts returns most-recent first
      expect(out[0].file_path).toContain('f2.ts')
      expect(out[2].file_path).toContain('f0.ts')
    })
  })

  it('swallows write errors rather than throwing into the caller', async () => {
    // Point cwd at a non-existent nested path — appendReceipt will
    // attempt mkdir + append. If it swallows errors as documented,
    // the call resolves normally; if it throws, the test fails.
    const bogusCwd = join(scratch, 'does', 'not', 'exist', 'yet')
    await runWithCwdOverride(bogusCwd, async () => {
      await expect(
        appendReceipt(
          createReceipt({
            sessionId: 's',
            absoluteFilePath: join(bogusCwd, 'x.ts'),
            operation: 'edit',
            contentBefore: 'a',
            contentAfter: 'b',
            cwd: bogusCwd,
          }),
        ),
      ).resolves.toBeUndefined()
    })
  })

  it('listReceipts returns empty array when no file exists (not a throw)', async () => {
    await runWithCwdOverride(scratch, async () => {
      const out = await listReceipts()
      expect(out).toEqual([])
    })
  })

  it('respects the limit param and caps at MAX_RECEIPTS_TO_LIST (200)', async () => {
    await runWithCwdOverride(scratch, async () => {
      for (let i = 0; i < 10; i++) {
        await appendReceipt(
          createReceipt({
            sessionId: 'sess',
            absoluteFilePath: join(scratch, `f${i}.ts`),
            operation: 'edit',
            contentBefore: 'x',
            contentAfter: 'y',
            cwd: scratch,
          }),
        )
      }
      expect(await listReceipts(3)).toHaveLength(3)
      expect(await listReceipts(999)).toHaveLength(10)
    })
  })
})
