/**
 * Client-side run-log export: builds a zip with human-readable .log.txt and
 * machine-readable .json from a Tier 1 status snapshot (live or hydrated).
 */
import { zipSync, strToU8 } from 'fflate';
import { collectReportEvidence } from '../../src/tier1/reportEvidence';
import { REPORT_COUNTER_KEYS, reportScoresFromMetrics } from '../../src/tier1/statsAggregate';

export type ExportLogEvent = {
  ts: string;
  phase: string;
  featureId: string | null;
  message: string;
};

export type ExportRunLogInput = {
  id: string;
  ts: string;
  model?: string | null;
  prompt?: string | null;
  events: ExportLogEvent[];
  problems: string[];
  metrics?: Record<string, unknown> | null;
  epochs?: { ts: string; model?: string | null; prompt: string }[];
  /** Harness contract version (e.g. V1.11+abcd…). */
  contractVersion?: string | null;
  /** Provider Vault extension version. */
  vaultVersion?: string | null;
  /** Corpus / app version from the published corpus manifest. */
  corpusVersion?: string | null;
  /** SET 34 — optional precomputed evidence lines (else derived from events). */
  evidence?: string[] | null;
  // SET 89 §§10 / 11 — canonical registries and terminal certification are
  // shipped alongside metrics so downstream tools do not have to reconstruct
  // them from event logs. Historical runs simply omit these fields.
  defectRegistry?: unknown;
  actionLedger?: unknown;
  artifactCertification?: unknown;
};

function pad2(n: number): string {
  return String(n).padStart(2, '0');
}

/** Format elapsed ms as +MmSSs (or +Ss when under a minute). */
export function formatRelOffset(startTs: string, eventTs: string): string {
  const start = new Date(startTs).getTime();
  const t = new Date(eventTs).getTime();
  const sec = Math.max(0, Math.round((t - start) / 1000));
  const m = Math.floor(sec / 60);
  const s = sec % 60;
  return m > 0 ? `+${m}m${pad2(s)}s` : `+${s}s`;
}

function formatElapsed(ms: number | undefined): string {
  if (ms == null || !Number.isFinite(ms)) return '—';
  const sec = Math.max(0, Math.round(ms / 1000));
  const m = Math.floor(sec / 60);
  const s = sec % 60;
  return m > 0 ? `${m}m${pad2(s)}s` : `${s}s`;
}

export function buildRunLogTxt(input: ExportRunLogInput): string {
  const startTs = input.events[0]?.ts ?? input.ts;
  const elapsedMs =
    typeof input.metrics?.elapsedMs === 'number'
      ? input.metrics.elapsedMs
      : input.events.length
        ? new Date(input.events[input.events.length - 1]!.ts).getTime() - new Date(startTs).getTime()
        : undefined;

  const lines: string[] = [
    'RHA Wrapper — run log',
    `run id: ${input.id}`,
    `ts: ${input.ts}`,
    `model: ${input.model ?? '—'}`,
    `contract: ${input.contractVersion ?? '—'}`,
    `vault: ${input.vaultVersion ?? '—'}`,
    `corpus: ${input.corpusVersion ?? '—'}`,
    `elapsed: ${formatElapsed(elapsedMs)}`,
    '',
    '--- events ---',
  ];

  for (const e of input.events) {
    const feat = e.featureId ?? '—';
    lines.push(`[${formatRelOffset(startTs, e.ts)}] [${e.phase}] [${feat}] ${e.message}`);
  }

  lines.push('', '--- problems ---');
  if (input.problems.length === 0) {
    lines.push('(none)');
  } else {
    for (const p of input.problems) lines.push(p);
  }

  // SET 34 — claim-bound evidence (seam dumps, spec hash, efficacy, …).
  const evidence = input.evidence?.length
    ? input.evidence
    : collectReportEvidence(input.events, input.problems).lines;
  lines.push('', '--- evidence ---');
  if (evidence.length === 0) {
    lines.push('(none)');
  } else {
    for (const e of evidence) lines.push(e);
  }

  lines.push('', '--- metrics ---');
  if (input.metrics && Object.keys(input.metrics).length) {
    lines.push(JSON.stringify(exportMetricsWithScores(input.metrics), null, 2));
  } else {
    lines.push('(none)');
  }

  // SET 89 §§10.5 / 11.4 / 12.3 — canonical registries emit summaries in the
  // human-readable log; the full snapshots are always available in the JSON.
  if (input.defectRegistry) {
    lines.push('', '--- defect registry ---');
    lines.push(summarizeDefectRegistry(input.defectRegistry));
  }
  if (input.actionLedger) {
    lines.push('', '--- action ledger ---');
    lines.push(summarizeActionLedger(input.actionLedger));
  }
  if (input.artifactCertification) {
    lines.push('', '--- artifact certification ---');
    lines.push(JSON.stringify(input.artifactCertification, null, 2));
  }
  lines.push('');
  return lines.join('\n');
}

function summarizeDefectRegistry(snap: unknown): string {
  if (!snap || typeof snap !== 'object') return '(none)';
  const s = snap as { identities?: unknown[]; occurrences?: unknown[]; resolutions?: unknown[] };
  return JSON.stringify({
    identities: s.identities?.length ?? 0,
    occurrences: s.occurrences?.length ?? 0,
    resolutions: s.resolutions?.length ?? 0,
  }, null, 2);
}

function summarizeActionLedger(snap: unknown): string {
  if (!snap || typeof snap !== 'object') return '(none)';
  const s = snap as { starts?: unknown[]; terminals?: unknown[]; decisions?: unknown[] };
  return JSON.stringify({
    starts: s.starts?.length ?? 0,
    terminals: s.terminals?.length ?? 0,
    decisions: s.decisions?.length ?? 0,
  }, null, 2);
}

/** SET 57e — export carries the three function ratings next to the counters
 * so the UI and the exported log can never disagree (same formula source).
 * Records predating the SET 57 counters export unchanged. */
export function exportMetricsWithScores(
  metrics: Record<string, unknown>,
): Record<string, unknown> {
  if (!REPORT_COUNTER_KEYS.some(k => metrics[k] != null)) return metrics;
  return { ...metrics, ...reportScoresFromMetrics(metrics) };
}

export function buildRunLogJson(input: ExportRunLogInput): string {
  const evidence = input.evidence?.length
    ? input.evidence
    : collectReportEvidence(input.events, input.problems).lines;
  return JSON.stringify(
    {
      id: input.id,
      ts: input.ts,
      model: input.model ?? null,
      prompt: input.prompt ?? null,
      events: input.events,
      problems: input.problems,
      evidence,
      metrics: input.metrics ? exportMetricsWithScores(input.metrics) : null,
      epochs: input.epochs ?? [],
      // SET 89 — canonical registries + terminal certification are
      // included verbatim so downstream analyzers do not have to walk
      // events. Missing on older records.
      defectRegistry: input.defectRegistry ?? null,
      actionLedger: input.actionLedger ?? null,
      artifactCertification: input.artifactCertification ?? null,
    },
    null,
    2,
  );
}

/** Build and trigger download of `run-<id>-log.zip` containing txt + json. */
export function downloadRunLogZip(input: ExportRunLogInput): void {
  const id = input.id || 'unknown';
  const txt = buildRunLogTxt(input);
  const json = buildRunLogJson(input);
  const zipped = zipSync({
    [`run-${id}.log.txt`]: strToU8(txt),
    [`run-${id}.json`]: strToU8(json),
  });
  const blob = new Blob([zipped], { type: 'application/zip' });
  const url = URL.createObjectURL(blob);
  const a = document.createElement('a');
  a.href = url;
  a.download = `run-${id}-log.zip`;
  document.body.appendChild(a);
  a.click();
  a.remove();
  URL.revokeObjectURL(url);
}
