/**
 * efcore-history.ts — Immutable audit trail for destructive migration squashes.
 *
 * Every squash that DELETES migration files (and ALWAYS a `-BruteForce`
 * re-baseline) appends one JSON record under `<git-root>/.claude/Efcore-history/`.
 * One file per operation — never rewritten — so there is always a trace of who
 * rewrote migration history, when, against which reference branch, and exactly
 * which migrations were destroyed (including any that already lived on prod).
 *
 * `buildHistoryRecord` is PURE (timestamp injected) so it is unit-testable.
 * `appendHistory` does the I/O (git-root resolution + write) and never throws —
 * a logging failure must not abort a squash that already mutated the tree; it
 * returns `{ path: null, error }` so the caller can surface a warning.
 */
import path from 'node:path';
import { execFile } from 'node:child_process';
import { promisify } from 'node:util';
import { ensureDirectory, writeText } from '../../../lib/fs.js';

const execFileAsync = promisify(execFile);

export type HistoryOperation = 'squash' | 'squash-bruteforce' | 'rebase-snapshot';

export interface HistoryAssemblyEntry {
  assemblyName: string;
  /** Every migration file basename deleted in this assembly. */
  deletedMigrations: string[];
  /**
   * Reference/production migrations destroyed. Empty for a normal squash
   * (which never touches reference migrations); populated only by a brute-force
   * re-baseline — the genuinely dangerous deletions.
   */
  parentMigrationsDestroyed: string[];
  newMigrationName: string | null;
  backupDir: string | null;
}

export interface HistoryRecord {
  timestamp: string;
  operation: HistoryOperation;
  cwd: string;
  currentBranch: string;
  baseBranch: string;
  branchType: string;
  /** True for a `-BruteForce` re-baseline. */
  bruteForce: boolean;
  /** True when the human passed the explicit confirmation token. */
  confirmed: boolean;
  assemblies: HistoryAssemblyEntry[];
}

export interface BuildHistoryInput {
  /** ISO timestamp — injected by the caller so the builder stays pure/testable. */
  timestamp: string;
  bruteForce: boolean;
  /** True when invoked through the rebase-snapshot mode — labels the audit record. */
  rebaseSnapshot?: boolean;
  confirmed: boolean;
  cwd: string;
  currentBranch: string;
  baseBranch: string;
  branchType: string;
  assemblies: HistoryAssemblyEntry[];
}

/** Pure record builder — derives the operation kind from the inputs. */
export function buildHistoryRecord(input: BuildHistoryInput): HistoryRecord {
  const operation: HistoryOperation = input.bruteForce
    ? 'squash-bruteforce'
    : input.rebaseSnapshot
      ? 'rebase-snapshot'
      : 'squash';
  return {
    timestamp: input.timestamp,
    operation,
    cwd: input.cwd,
    currentBranch: input.currentBranch,
    baseBranch: input.baseBranch,
    branchType: input.branchType,
    bruteForce: input.bruteForce,
    confirmed: input.confirmed,
    assemblies: input.assemblies,
  };
}

/** Resolve the git work-tree root so the log always lands at the project's `.claude/`. Falls back to `cwd` when not in a git repo. */
async function gitTopLevel(cwd: string): Promise<string> {
  try {
    const { stdout } = await execFileAsync('git', ['rev-parse', '--show-toplevel'], {
      cwd,
      encoding: 'utf-8',
    });
    return stdout.trim() || cwd;
  } catch {
    return cwd;
  }
}

/**
 * Append `record` to `<git-root>/.claude/Efcore-history/<safe-ts>_<operation>.json`.
 * Never throws — returns the written path, or `{ path: null, error }` on failure.
 */
export async function appendHistory(
  cwd: string,
  record: HistoryRecord,
): Promise<{ path: string | null; error?: string }> {
  try {
    const root = await gitTopLevel(cwd);
    const dir = path.join(root, '.claude', 'Efcore-history');
    await ensureDirectory(dir);
    const safeStamp = record.timestamp.replace(/[:.]/g, '-');
    const file = path.join(dir, `${safeStamp}_${record.operation}.json`);
    await writeText(file, `${JSON.stringify(record, null, 2)}\n`);
    return { path: file };
  } catch (e) {
    return { path: null, error: (e as Error).message };
  }
}
