/**
 * Action Receipts — pure helpers + append-only JSONL persistence.
 */

import { createHash, randomUUID } from 'crypto'
import { appendFile, mkdir, readFile } from 'fs/promises'
import { dirname, join, relative } from 'path'
import { getCwd } from '../../utils/cwd.js'
import { logForDebugging } from '../../utils/debug.js'
import type { Receipt, ReceiptOperation } from './types.js'

const RECEIPTS_FILE = '.sandcode/receipts.jsonl'
const MAX_RECEIPTS_TO_LIST = 200

export function hashContent(content: string | Buffer): string {
  return createHash('sha256').update(content).digest('hex')
}

/**
 * Compute the relative, forward-slash-normalized path from cwd to an
 * absolute path. Exported for testing.
 */
export function normalizeFilePath(absolutePath: string, cwd = getCwd()): string {
  const rel = relative(cwd, absolutePath)
  // Paths outside cwd keep the absolute form (just normalized slashes);
  // inside cwd they become repo-relative.
  const out = rel && !rel.startsWith('..') ? rel : absolutePath
  return out.replace(/\\/g, '/')
}

export type ReceiptInputs = {
  sessionId: string
  absoluteFilePath: string
  operation: ReceiptOperation
  contentBefore: string | Buffer | null
  contentAfter: string | Buffer | null
  model?: string
  now?: number
  cwd?: string
}

/**
 * Build a Receipt from the inputs at the call site. Pure — no I/O.
 */
export function createReceipt(inputs: ReceiptInputs): Receipt {
  const before = inputs.contentBefore
  const after = inputs.contentAfter
  return {
    version: 1,
    id: randomUUID().slice(0, 8),
    timestamp: inputs.now ?? Date.now(),
    session_id: inputs.sessionId,
    file_path: normalizeFilePath(inputs.absoluteFilePath, inputs.cwd),
    operation: inputs.operation,
    hash_before: before === null ? null : hashContent(before),
    hash_after: after === null ? null : hashContent(after),
    bytes_before: before === null ? 0 : Buffer.byteLength(before),
    bytes_after: after === null ? 0 : Buffer.byteLength(after),
    model: inputs.model,
  }
}

function getReceiptsPath(): string {
  return join(getCwd(), RECEIPTS_FILE)
}

/**
 * Append a receipt as a single JSON line. Best-effort: errors are
 * logged but never thrown, so an audit-trail failure cannot break a
 * user-initiated edit.
 */
export async function appendReceipt(receipt: Receipt): Promise<void> {
  const path = getReceiptsPath()
  try {
    await mkdir(dirname(path), { recursive: true })
    await appendFile(path, JSON.stringify(receipt) + '\n', 'utf-8')
  } catch (e) {
    logForDebugging(`Failed to append receipt: ${String(e)}`)
  }
}

/**
 * Parse a newline-delimited JSON blob into receipts, skipping malformed
 * lines. Exported for testing the pure parser.
 */
export function parseReceiptsJsonl(raw: string): Receipt[] {
  const out: Receipt[] = []
  for (const line of raw.split('\n')) {
    const trimmed = line.trim()
    if (!trimmed) continue
    try {
      const parsed = JSON.parse(trimmed) as Receipt
      if (parsed && parsed.version === 1) out.push(parsed)
    } catch {
      // Skip malformed lines defensively
    }
  }
  return out
}

/**
 * Load recent receipts from disk, most recent first.
 */
export async function listReceipts(limit = 50): Promise<Receipt[]> {
  try {
    const raw = await readFile(getReceiptsPath(), 'utf-8')
    const all = parseReceiptsJsonl(raw)
    return all.slice(-Math.min(limit, MAX_RECEIPTS_TO_LIST)).reverse()
  } catch {
    return []
  }
}

function shortHash(h: string | null): string {
  if (h === null) return '—'
  return h.slice(0, 8)
}

function formatByteDelta(before: number, after: number): string {
  const delta = after - before
  if (delta === 0) return '(0 bytes)'
  const sign = delta > 0 ? '+' : ''
  return `(${sign}${delta} bytes)`
}

/**
 * Render a single receipt as a one-line summary for /receipts.
 */
export function formatReceiptLine(r: Receipt): string {
  const when = new Date(r.timestamp).toISOString().replace('T', ' ').slice(0, 19)
  const op = r.operation.padEnd(6)
  const hashes = `${shortHash(r.hash_before)} → ${shortHash(r.hash_after)}`
  const bytes = formatByteDelta(r.bytes_before, r.bytes_after)
  const modelSuffix = r.model ? ` [${r.model}]` : ''
  return `${when}  ${op}  ${r.file_path}  ${hashes} ${bytes}${modelSuffix}`
}
