import { mkdir, readFile, writeFile, readdir, rm, appendFile } from 'node:fs/promises';
import { existsSync } from 'node:fs';
import { join, resolve } from 'node:path';

export const RUNS_REL = '.boo/runs';
export const DAILY_REL = '.boo/runs/daily';
export const GUIDELINES_REL = '.boo/guidelines';

export interface SessionJson {
  session_id: string;
  task: string;
  start_time: string;
  end_time?: string;
  status: 'in_progress' | 'completed';
  expected_record_types: string[];
}

export interface AuditTrailEntry {
  timestamp: string;
  record_type: string;
  action_type: string;
  tool?: string;
  files?: string[];
  detail?: string;
  input?: string;
  output?: string;
}

export interface IndexEntry {
  id: string;
  task: string;
  status: string;
  record_count: number;
  start_time: string;
  max_anomaly_level?: string;
}

export interface IndexJson {
  entries: IndexEntry[];
}

export interface StartSessionResult {
  sessionId: string;
  contextSummary: {
    recentActivity: IndexEntry[];
    userCorrections: UserCorrectionRecord[];
    unfinishedSessions: SessionJson[];
  };
}

export interface EndSessionResult {
  sessionId: string;
  integrity: IntegrityCheck[];
  correctionCount: number;
  summaryPath: string;
}

export interface IntegrityCheck {
  check: string;
  passed: boolean;
  detail?: string;
}

export interface RecoverResult {
  level: number;
  sessionId?: string;
  task?: string;
  recentActivity: IndexEntry[];
  lastTrailEntries: AuditTrailEntry[];
  userCorrections: UserCorrectionRecord[];
  conclusions: string[];
  dailyAnomalies: string[];
  dailyBacklog: string[];
  fullTrail?: AuditTrailEntry[];
  anomalies?: string[];
}

export interface DailyReport {
  date: string;
  sections: {
    taskOverview: string;
    operationStats: { label: string; count: number }[];
    changes: { time: string; target: string; detail: string }[];
    userFeedback: { feedback: string; resolution: string; persistedTo: string }[];
    anomalyAlerts: string[];
    backlogTracking: string[];
    integritySummary: string;
  };
  path: string;
}

export interface UserCorrectionRecord {
  record_type: 'conversation';
  action_type: 'user_correction';
  priority: 'critical_for_recovery';
  timestamp: string;
  original_claim: string;
  correction: string;
  principle_extracted: string;
  persisted_to: string[];
}

function runsDir(basePath?: string): string {
  return resolve(basePath ?? process.cwd(), RUNS_REL);
}

function dailyDir(basePath?: string): string {
  return resolve(basePath ?? process.cwd(), DAILY_REL);
}

function sessionDir(sessionId: string, basePath?: string): string {
  return join(runsDir(basePath), sessionId);
}

function currentSessionPath(basePath?: string): string {
  return join(runsDir(basePath), '.current_session');
}

function indexJsonPath(basePath?: string): string {
  return join(runsDir(basePath), 'index.json');
}

function auditBufferPath(basePath?: string): string {
  return join(runsDir(basePath), 'audit_buffer.jsonl');
}

function auditPendingPath(basePath?: string): string {
  return join(runsDir(basePath), 'audit_pending.jsonl');
}

function trailPath(sessionId: string, basePath?: string): string {
  return join(sessionDir(sessionId, basePath), 'audit_trail.jsonl');
}

function sessionJsonPath(sessionId: string, basePath?: string): string {
  return join(sessionDir(sessionId, basePath), 'session.json');
}

function summaryPath(sessionId: string, basePath?: string): string {
  return join(sessionDir(sessionId, basePath), 'session_summary.md');
}

export function generateSessionId(): string {
  const now = new Date();
  const y = now.getFullYear();
  const m = String(now.getMonth() + 1).padStart(2, '0');
  const d = String(now.getDate()).padStart(2, '0');
  const hh = String(now.getHours()).padStart(2, '0');
  const mm = String(now.getMinutes()).padStart(2, '0');
  return `adhoc_${y}${m}${d}_${hh}${mm}`;
}

function isoNow(): string {
  return new Date().toISOString();
}

function isoDate(d?: Date): string {
  const dt = d ?? new Date();
  return `${dt.getFullYear()}${String(dt.getMonth() + 1).padStart(2, '0')}${String(dt.getDate()).padStart(2, '0')}`;
}


function tryParseJson<T>(raw: string): T | null {
  try {
    return JSON.parse(raw) as T;
  } catch {
    return null;
  }
}

async function ensureDir(p: string): Promise<void> {
  if (!existsSync(p)) {
    await mkdir(p, { recursive: true });
  }
}

async function readLines(p: string): Promise<string[]> {
  try {
    const content = await readFile(p, 'utf-8');
    return content.split('\n').filter(Boolean);
  } catch {
    return [];
  }
}

async function readJsonFile<T>(p: string): Promise<T | null> {
  try {
    const raw = await readFile(p, 'utf-8');
    return tryParseJson<T>(raw);
  } catch {
    return null;
  }
}

function appendLine(p: string, line: string): Promise<void> {
  return appendFile(p, line + '\n', 'utf-8');
}

async function clearFile(p: string): Promise<void> {
  try {
    await writeFile(p, '', 'utf-8');
  } catch {
    // File may not exist
  }
}

export async function getCurrentSession(basePath?: string): Promise<string | null> {
  try {
    const raw = await readFile(currentSessionPath(basePath), 'utf-8');
    return raw.trim();
  } catch {
    return null;
  }
}

export async function getSessionJson(sessionId: string, basePath?: string): Promise<SessionJson | null> {
  return readJsonFile<SessionJson>(sessionJsonPath(sessionId, basePath));
}

export async function getIndex(basePath?: string): Promise<IndexJson | null> {
  return readJsonFile<IndexJson>(indexJsonPath(basePath));
}

async function writeIndex(entries: IndexEntry[], basePath?: string): Promise<void> {
  await ensureDir(runsDir(basePath));
  await writeFile(indexJsonPath(basePath), JSON.stringify({ entries }, null, 2), 'utf-8');
}

async function appendIndex(sessionId: string, task: string, basePath?: string): Promise<void> {
  const existing = await getIndex(basePath);
  const entry: IndexEntry = {
    id: sessionId,
    task,
    status: 'in_progress',
    record_count: 0,
    start_time: isoNow(),
  };
  const entries = [entry, ...(existing?.entries ?? [])].slice(0, 100);
  await writeIndex(entries, basePath);
}

async function updateIndexStatus(sessionId: string, status: string, basePath?: string): Promise<void> {
  const idx = await getIndex(basePath);
  if (!idx) return;
  for (const e of idx.entries) {
    if (e.id === sessionId) {
      e.status = status;
    }
  }
  await writeIndex(idx.entries, basePath);
}

export async function startSession(task: string, basePath?: string): Promise<StartSessionResult> {
  const sessionId = generateSessionId();
  const sDir = sessionDir(sessionId, basePath);
  await ensureDir(sDir);

  const session: SessionJson = {
    session_id: sessionId,
    task,
    start_time: isoNow(),
    status: 'in_progress',
    expected_record_types: ['data', 'change', 'conversation'],
  };

  await writeFile(sessionJsonPath(sessionId, basePath), JSON.stringify(session, null, 2), 'utf-8');
  await writeFile(currentSessionPath(basePath), sessionId, 'utf-8');
  await appendIndex(sessionId, task, basePath);

  // L0 context recovery
  const idx = await getIndex(basePath);
  const recentActivity = idx?.entries.slice(0, 5) ?? [];

  // L2 user correction scan
  const allCorrections = await scanAllTrailsForCorrections(basePath);

  const unfinishedSessions = await findUnfinishedSessions(basePath);

  return {
    sessionId,
    contextSummary: {
      recentActivity,
      userCorrections: allCorrections,
      unfinishedSessions,
    },
  };
}

async function findUnfinishedSessions(basePath?: string): Promise<SessionJson[]> {
  const rDir = runsDir(basePath);
  if (!existsSync(rDir)) return [];

  const entries = await readdir(rDir, { withFileTypes: true });
  const unfinished: SessionJson[] = [];

  for (const entry of entries) {
    if (!entry.isDirectory()) continue;
    const sess = await getSessionJson(entry.name, basePath);
    if (sess && sess.status === 'in_progress') {
      unfinished.push(sess);
    }
  }

  return unfinished;
}

async function scanAllTrailsForCorrections(basePath?: string): Promise<UserCorrectionRecord[]> {
  const rDir = runsDir(basePath);
  if (!existsSync(rDir)) return [];

  const entries = await readdir(rDir, { withFileTypes: true });
  const corrections: UserCorrectionRecord[] = [];

  for (const entry of entries) {
    if (!entry.isDirectory()) continue;
    const lines = await readLines(trailPath(entry.name, basePath));
    for (const line of lines) {
      const record = tryParseJson<UserCorrectionRecord>(line);
      if (record?.action_type === 'user_correction') {
        corrections.push(record);
      }
    }
  }

  // Also scan audit_pending.jsonl
  const pendingLines = await readLines(auditPendingPath(basePath));
  for (const line of pendingLines) {
    const record = tryParseJson<UserCorrectionRecord>(line);
    if (record?.action_type === 'user_correction') {
      corrections.push(record);
    }
  }

  return corrections;
}

export async function endSession(basePath?: string): Promise<EndSessionResult | null> {
  const sessionId = await getCurrentSession(basePath);
  if (!sessionId) return null;

  const sDir = sessionDir(sessionId, basePath);
  await ensureDir(sDir);

  // Collect remaining buffer data
  const bufferLines = await readLines(auditBufferPath(basePath));
  const pendingLines = await readLines(auditPendingPath(basePath));
  const allRemaining = [...bufferLines, ...pendingLines];

  // Append to audit_trail.jsonl
  const trail = trailPath(sessionId, basePath);
  if (allRemaining.length > 0) {
    await appendFile(trail, allRemaining.join('\n') + '\n', 'utf-8');
  }

  // Clear buffer files
  await clearFile(auditBufferPath(basePath));
  await clearFile(auditPendingPath(basePath));

  // Read current trail for stats
  const trailLines = await readLines(trail);

  const corrections: UserCorrectionRecord[] = [];
  for (const line of trailLines) {
    const record = tryParseJson<UserCorrectionRecord>(line);
    if (record?.action_type === 'user_correction') {
      corrections.push(record);
    }
  }

  // Integrity checks
  const integrity: IntegrityCheck[] = [
    {
      check: 'Audit records exist',
      passed: trailLines.length > 0,
      detail: trailLines.length > 0 ? `${trailLines.length} records` : 'No audit records found',
    },
    {
      check: 'File modifications tracked',
      passed: trailLines.some((l) => {
        const r = tryParseJson<AuditTrailEntry>(l);
        return r && (r.tool === 'Write' || r.tool === 'Edit');
      }),
      detail: 'Checking for Write/Edit tool entries',
    },
    {
      check: 'User corrections persisted',
      passed: corrections.every((c) => (c.persisted_to?.length ?? 0) > 0),
      detail: corrections.length > 0
        ? `${corrections.length} corrections found, ${corrections.filter((c) => (c.persisted_to?.length ?? 0) > 0).length} persisted`
        : 'No corrections to persist',
    },
  ];

  // Generate session summary
  const summaryContent = generateSessionSummary(sessionId, trailLines, corrections);
  const summaryFile = summaryPath(sessionId, basePath);
  await writeFile(summaryFile, summaryContent, 'utf-8');

  const session = await getSessionJson(sessionId, basePath);
  if (session) {
    session.status = 'completed';
    session.end_time = isoNow();
    await writeFile(sessionJsonPath(sessionId, basePath), JSON.stringify(session, null, 2), 'utf-8');
    await updateIndexStatus(sessionId, 'completed', basePath);
  }

  const idx = await getIndex(basePath);
  if (idx) {
    for (const e of idx.entries) {
      if (e.id === sessionId) {
        e.record_count = trailLines.length;
        e.status = 'completed';
      }
    }
    await writeIndex(idx.entries, basePath);
  }

  // Clear .current_session
  try {
    await rm(currentSessionPath(basePath));
  } catch {
    // Ok if already gone
  }

  return {
    sessionId,
    integrity,
    correctionCount: corrections.length,
    summaryPath: summaryFile,
  };
}

function generateSessionSummary(
  sessionId: string,
  trailLines: string[],
  corrections: UserCorrectionRecord[],
): string {
  const actions: string[] = [];
  const outputs: string[] = [];

  for (const line of trailLines) {
    const record = tryParseJson<AuditTrailEntry>(line);
    if (record) {
      if (record.action_type) actions.push(record.action_type);
      if (record.output) outputs.push(record.output);
    }
  }

  return [
    `# Session Summary | ${sessionId}`,
    '',
    `## Time: ${isoNow()}`,
    `## Status: completed`,
    '',
    '## Completed work',
    ...actions.map((a) => `- ${a}`),
    '',
    '## Key conclusions',
    ...outputs.map((o) => `- ${o}`),
    '',
    '## User corrections',
    ...(corrections.length > 0
      ? corrections.map((c) => `- ${c.original_claim} → ${c.correction} (${c.principle_extracted})`)
      : ['- None']),
    '',
  ].join('\n');
}

export async function recoverSession(
  level: number,
  specificSessionId?: string,
  basePath?: string,
): Promise<RecoverResult> {
  const result: RecoverResult = { level, recentActivity: [], lastTrailEntries: [], userCorrections: [], conclusions: [], dailyAnomalies: [], dailyBacklog: [] };

  // L0: index summary
  const idx = await getIndex(basePath);
  result.recentActivity = idx?.entries.slice(0, 5) ?? [];

  if (level === 0) return result;

  // L1: current session + last 3 trail entries
  let activeSessionId = specificSessionId ?? await getCurrentSession(basePath);
  if (activeSessionId) {
    result.sessionId = activeSessionId;
    const session = await getSessionJson(activeSessionId, basePath);
    if (session) {
      result.task = session.task;
    }

    const trailLines = await readLines(trailPath(activeSessionId, basePath));
    result.lastTrailEntries = trailLines.slice(-3).map((l) => {
      const r = tryParseJson<AuditTrailEntry>(l);
      return r ?? { timestamp: '', record_type: '', action_type: '', input: l, output: l };
    });
  }

  if (level === 1) return result;

  // L2: user corrections + conclusions + daily anomalies
  result.userCorrections = await scanAllTrailsForCorrections(basePath);

  const allTrailLines = await readLines(trailPath(activeSessionId ?? '', basePath));
  for (const line of allTrailLines) {
    const record = tryParseJson<AuditTrailEntry>(line);
    if (record?.output) {
      result.conclusions.push(record.output);
    }
  }

  // Read daily reports for anomalies + backlog
  const dDir = dailyDir(basePath);
  if (existsSync(dDir)) {
    const dailyFiles = (await readdir(dDir)).filter((f) => f.endsWith('_daily.md')).sort().reverse();
    if (dailyFiles.length > 0) {
      const latest = await readFile(join(dDir, dailyFiles[0]!), 'utf-8');
      const anomalies = latest.match(/## (?:四|4).*?[\s\S]*?(?=##|$)/);
      if (anomalies) result.dailyAnomalies.push(anomalies[0]);
      const backlog = latest.match(/## (?:六|6).*?[\s\S]*?(?=##|$)/);
      if (backlog) result.dailyBacklog.push(backlog[0]);
    }
  }

  if (level === 2) return result;

  // L3: full trail + pending
  if (level >= 3) {
    if (activeSessionId) {
      const fullLines = await readLines(trailPath(activeSessionId, basePath));
      result.fullTrail = fullLines.map((l) => {
        const r = tryParseJson<AuditTrailEntry>(l);
        return r ?? { timestamp: '', record_type: '', action_type: '', input: l, output: l };
      });
    }
  }

  return result;
}

export async function generateDailyReport(
  targetDate?: string,
  review?: boolean,
  basePath?: string,
): Promise<DailyReport> {
  const date = targetDate ?? isoDate();
  const idx = await getIndex(basePath);
  const rDir = runsDir(basePath);

  const todayEntries = (idx?.entries ?? []).filter((e) => e.start_time.startsWith(date.slice(0, 4) + '-' + date.slice(4, 6) + '-' + date.slice(6, 8)));

  let totalWriteEdit = 0;
  let totalBash = 0;
  let totalAuditBlocks = 0;
  const changes: { time: string; target: string; detail: string }[] = [];
  const feedback: { feedback: string; resolution: string; persistedTo: string }[] = [];
  const anomalies: string[] = [];

  for (const entry of todayEntries) {
    const lines = await readLines(trailPath(entry.id, basePath));
    for (const line of lines) {
      const record = tryParseJson<AuditTrailEntry>(line);
      if (!record) continue;
      if (record.tool === 'Write' || record.tool === 'Edit') totalWriteEdit++;
      if (record.tool === 'Bash') totalBash++;
      if (record.action_type === 'audit_block') totalAuditBlocks++;
      if (record.tool && (record.tool === 'Write' || record.tool === 'Edit') && record.files) {
        changes.push({ time: record.timestamp, target: record.files.join(', '), detail: record.detail ?? '' });
      }
      if (record.action_type === 'user_correction') {
        const uc = record as unknown as UserCorrectionRecord;
        feedback.push({ feedback: uc.original_claim, resolution: uc.correction, persistedTo: (uc.persisted_to ?? []).join(', ') });
      }
    }
  }

  if (existsSync(rDir)) {
    const sessionDirs = await readdir(rDir, { withFileTypes: true });
    for (const d of sessionDirs) {
      if (!d.isDirectory()) continue;
      const anomPath = join(rDir, d.name, 'anomalies.json');
      if (existsSync(anomPath)) {
        const anomContent = await readFile(anomPath, 'utf-8');
        anomalies.push(`[${d.name}] ${anomContent.slice(0, 200)}`);
      }
    }
  }

  // Read previous day backlog
  const prevDate = isoDate(new Date(Date.now() - 86400000));
  let backlog: string[] = [];
  const prevDailyPath = join(dailyDir(basePath), `${prevDate}_daily.md`);
  if (existsSync(prevDailyPath)) {
    const prevContent = await readFile(prevDailyPath, 'utf-8');
    const m = prevContent.match(/## (?:六|6|明日待办)[\s\S]*?(?=##|$)/);
    if (m) backlog = m[0].split('\n').filter((l) => l.trim().startsWith('-')).map((l) => l.replace(/^-\s*/, ''));
  }

  const reportPath = join(dailyDir(basePath), `${date}_daily.md`);
  await ensureDir(dailyDir(basePath));

  const sections = {
    taskOverview: todayEntries.length > 0
      ? todayEntries.map((e) => `| ${e.id} | ${e.task} | ${e.status} | ${e.record_count} |`).join('\n')
      : 'No activity',
    operationStats: [
      { label: 'Write/Edit operations', count: totalWriteEdit },
      { label: 'Bash executions', count: totalBash },
      { label: 'Audit blocks', count: totalAuditBlocks },
    ],
    changes,
    userFeedback: feedback,
    anomalyAlerts: anomalies,
    backlogTracking: backlog,
    integritySummary: [
      `| All sessions have audit records | ${todayEntries.every((e) => e.record_count > 0) ? '✅' : '⚠️'} |`,
      `| Audit blocks persisted | ${totalAuditBlocks > 0 ? '✅' : '⚠️'} |`,
      `| User corrections persisted | ${feedback.every((f) => f.persistedTo.length > 0) ? '✅' : '⚠️'} |`,
    ].join('\n'),
  };

  const reportContent = generateDailyReportContent(date, sections);
  await writeFile(reportPath, reportContent, 'utf-8');

  // If review mode, also generate morning review
  if (review) {
    const reviewPath = join(dailyDir(basePath), `${date}_morning_review.md`);
    const reviewContent = generateMorningReview(sections, date);
    await writeFile(reviewPath, reviewContent, 'utf-8');
  }

  return { date, sections, path: reportPath };
}

function generateDailyReportContent(date: string, sections: DailyReport['sections']): string {
  return [
    `# Work Report | ${date}`,
    '',
    `> Auto-generated: ${isoNow()}`,
    `> Data source: .boo/runs/index.json + session audit_trail`,
    `> Coverage: ${date.slice(0, 4)}-${date.slice(4, 6)}-${date.slice(6, 8)} 00:00 — 23:59`,
    '',
    '---',
    '',
    '## I. Task Overview',
    '',
    '| Session ID | Task | Status | Records |',
    '|-----------|------|--------|---------|',
    sections.taskOverview,
    '',
    '---',
    '',
    '## II. Operation Stats',
    '',
    '| Metric | Count |',
    '|--------|-------|',
    ...sections.operationStats.map((s) => `| ${s.label} | ${s.count} |`),
    '',
    '---',
    '',
    '## III. Change Records',
    '',
    ...(sections.changes.length > 0
      ? ['| Time | Target | Detail |', '|------|--------|--------|', ...sections.changes.map((c) => `| ${c.time} | ${c.target} | ${c.detail} |`)]
      : ['No changes recorded today.']),
    '',
    '---',
    '',
    '## IV. User Feedback & Corrections',
    '',
    ...(sections.userFeedback.length > 0
      ? ['| Feedback | Resolution | Persisted To |', '|---------|------------|--------------|', ...sections.userFeedback.map((f) => `| ${f.feedback} | ${f.resolution} | ${f.persistedTo} |`)]
      : ['None.']),
    '',
    '---',
    '',
    '## V. Anomaly Alerts',
    '',
    ...(sections.anomalyAlerts.length > 0 ? sections.anomalyAlerts.map((a) => `- ${a}`) : ['None.']),
    '',
    '---',
    '',
    '## VI. Backlog Tracking',
    '',
    ...(sections.backlogTracking.length > 0 ? sections.backlogTracking.map((b) => `- ${b}`) : ['None.']),
    '',
    '---',
    '',
    '## VII. Integrity Summary',
    '',
    '| Check | Result |',
    '|-------|--------|',
    sections.integritySummary,
    '',
  ].join('\n');
}

function generateMorningReview(sections: DailyReport['sections'], date: string): string {
  const anomalies = sections.anomalyAlerts;
  const hasUnhandledAnomalies = anomalies.some((a) => !a.includes('resolved'));
  const hasUnpersistedFeedback = sections.userFeedback.some((f) => !f.persistedTo);
  const hasIncompleteBacklog = sections.backlogTracking.length > 0;

  return [
    `# Morning Self-Review | ${date}`,
    '',
    `> Generated: ${isoNow()}`,
    '',
    '## Self-Correction Check',
    '',
    `- Unresolved anomalies: ${hasUnhandledAnomalies ? '⚠️ Yes — needs attention' : '✅ None'}`,
    `- Unpersisted user feedback: ${hasUnpersistedFeedback ? '⚠️ Needs documentation' : '✅ All persisted'}`,
    `- Outstanding backlog: ${hasIncompleteBacklog ? '⚠️ Carry-over items' : '✅ Clean slate'}`,
    '',
    '## Today\'s Recommended Priorities',
    '',
    ...(sections.backlogTracking.length > 0
      ? sections.backlogTracking.map((b) => `- [ ] ${b} (carry-over)`)
      : []),
    '- [ ] Review yesterday\'s user feedback and persist any remaining corrections',
    '- [ ] Continue highest-priority task from session overview',
    '',
  ].join('\n');
}

export async function ensureBooDirs(basePath?: string): Promise<void> {
  await ensureDir(runsDir(basePath));
  await ensureDir(dailyDir(basePath));
}

export async function writeAuditBuffer(entry: AuditTrailEntry, basePath?: string): Promise<void> {
  await ensureDir(runsDir(basePath));
  await appendLine(auditBufferPath(basePath), JSON.stringify(entry));
}

export async function writeAuditPending(entry: AuditTrailEntry, basePath?: string): Promise<void> {
  await ensureDir(runsDir(basePath));
  await appendLine(auditPendingPath(basePath), JSON.stringify(entry));
}
