/**
 * ReviewEvidenceResolver — 从 Action Evidence Log 组装 evidence_summary。
 * 只输出证据摘要，不判定 pass / fail / needs_admin（规格 P1）。
 */

import { readAllActionEvidenceRecords } from "../logs/ActionEvidenceLogger.ts";
import type { ActionEvidenceRecord } from "../logs/actionLogTypes.ts";
import { loadCitedEvidenceFiles } from "./citedEvidenceFiles.ts";
import {
  resolveExecutionProvenance,
  type ExecutionProvenanceResult,
} from "./ExecutionProvenanceResolver.ts";

export interface ReviewEvidenceTimeWindow {
  from?: string;
  to?: string;
}

export interface ReviewEvidenceResolveInput {
  projectRoot: string;
  /** Runtime-owned SessionStore, which may live outside projectRoot. */
  session_store_dir?: string;
  task_id?: string;
  report_id?: string;
  session_id?: string;
  run_id?: string;
  root_task_id?: string;
  attempt_id?: string;
  lease_id?: string;
  evidence_refs?: string[];
  /** Explicit child REPORT references whose execution evidence supports a PM summary. */
  delegated_report_ids?: string[];
  agent_id?: string;
  role?: string;
  thread_key?: string;
  time_window?: ReviewEvidenceTimeWindow;
  /** REPORT body (frontmatter stripped) — used to load cited JSON evidence files. */
  report_body?: string;
}

export interface EvidenceSummarySession {
  found: boolean;
  session_id?: string;
  run_id?: string;
  status?: string;
  tool_calls_count?: number;
  provenance_status?: ExecutionProvenanceResult["status"];
}

export interface EvidenceSummaryCommand {
  command: string;
  cwd?: string;
  started_at?: string;
  ended_at?: string;
  exit_code?: number | null;
  stdout_ref?: string;
  stderr_ref?: string;
}

export interface EvidenceSummaryDataQuery {
  query_id?: string;
  query_summary?: string;
  row_count?: number | null;
}

/** 规格 §3.4 — ReviewGate 消费的证据摘要（不含判定结论） */
export interface EvidenceSummary {
  task_id?: string;
  report_id?: string;
  agent_id?: string;
  role?: string;
  session: EvidenceSummarySession;
  files: { read: string[]; changed: string[] };
  commands: EvidenceSummaryCommand[];
  report: { found: boolean; path?: string };
  data_queries: EvidenceSummaryDataQuery[];
  browser_actions?: Array<{
    action: string;
    url?: string;
    screenshot_ref?: string;
  }>;
  /** Relative paths of JSON/JSONL/CSV cited in REPORT and successfully read as UTF-8. */
  cited_evidence_files?: string[];
  /** Child REPORT ids whose action evidence was found and included. */
  delegated_reports?: string[];
  warnings: string[];
  execution_provenance?: ExecutionProvenanceResult;
}

function inTimeWindow(at: string, window?: ReviewEvidenceTimeWindow): boolean {
  if (!window?.from && !window?.to) return true;
  const t = Date.parse(at);
  if (Number.isNaN(t)) return true;
  if (window.from) {
    const fromMs = Date.parse(window.from);
    if (!Number.isNaN(fromMs) && t < fromMs) return false;
  }
  if (window.to) {
    const toMs = Date.parse(window.to);
    if (!Number.isNaN(toMs) && t > toMs) return false;
  }
  return true;
}

function recordMatchesFilters(
  rec: ActionEvidenceRecord,
  taskId: string,
  reportId: string,
  sessionId: string,
  timeWindow?: ReviewEvidenceTimeWindow,
): boolean {
  if (!inTimeWindow(rec.at, timeWindow)) return false;
  if (sessionId && rec.session_id !== sessionId) return false;
  if (taskId && rec.task_id && rec.task_id !== taskId) return false;
  if (reportId) {
    if (rec.event_type === "report.write") {
      return rec.report_id === reportId;
    }
    if (taskId) {
      return rec.task_id === taskId;
    }
    return false;
  }
  return true;
}

function canonicalTaskId(value: string): string {
  const raw = str(value).replace(/\.md$/i, "");
  return /^TASK-\d{8}-\d{3,}/i.exec(raw)?.[0].toUpperCase() ?? raw.toUpperCase();
}

function sameTaskId(left: string, right: string): boolean {
  return Boolean(left && right && canonicalTaskId(left) === canonicalTaskId(right));
}

/**
 * 从 fcop/logs/runtime/actions-*.jsonl 聚合 evidence_summary。
 * 不输出 pass / fail / needs_admin。
 */
export function resolveReviewEvidence(
  input: ReviewEvidenceResolveInput,
): EvidenceSummary {
  const warnings: string[] = [];
  const all = readAllActionEvidenceRecords(input.projectRoot);

  let taskId = str(input.task_id);
  let reportId = str(input.report_id);
  const sessionId = str(input.session_id);
  const runId = str(input.run_id);
  const evidenceRefs = (input.evidence_refs ?? []).map(str).filter(Boolean);
  const explicitSession =
    sessionId || evidenceRefs.find((ref) => /^session-/i.test(ref)) || "";
  const explicitRun = runId || evidenceRefs.find((ref) => /^run-/i.test(ref)) || "";

  if (reportId && !taskId) {
    const reportRec = all.find(
      (r) => r.event_type === "report.write" && r.report_id === reportId,
    );
    if (reportRec?.task_id) {
      taskId = reportRec.task_id;
    } else {
      warnings.push(`report_id ${reportId} 未在 action log 中找到 report.write`);
    }
  }

  const inWindow = all.filter((rec) => inTimeWindow(rec.at, input.time_window));
  let filtered: ActionEvidenceRecord[] = [];
  if (explicitSession || explicitRun) {
    filtered = inWindow.filter(
      (rec) =>
        (!explicitSession || rec.session_id === explicitSession) &&
        (!explicitRun || rec.run_id === explicitRun) &&
        (!taskId || sameTaskId(rec.task_id, taskId)) &&
        (rec.event_type !== "report.write" || !reportId || rec.report_id === reportId),
    );
    if (filtered.length === 0) {
      warnings.push(
        `bound evidence unavailable for session=${explicitSession || "(none)"} run=${explicitRun || "(none)"}; cross-session fallback is disabled`,
      );
    }
  }
  if (filtered.length === 0 && !explicitSession && !explicitRun && taskId) {
    filtered = inWindow.filter((rec) => sameTaskId(rec.task_id, taskId));
  }
  if (filtered.length === 0 && !explicitSession && !explicitRun && reportId) {
    const reportWrite = inWindow.find(
      (rec) => rec.event_type === "report.write" && rec.report_id === reportId,
    );
    if (reportWrite?.session_id) {
      filtered = inWindow.filter((rec) => rec.session_id === reportWrite.session_id);
    }
  }
  if (
    filtered.length === 0 &&
    !explicitSession &&
    !explicitRun &&
    (input.agent_id || input.role || input.thread_key)
  ) {
    filtered = inWindow.filter(
      (rec) =>
        (!input.agent_id || rec.agent_id === input.agent_id) &&
        (!input.role || rec.role.toUpperCase() === input.role.toUpperCase()) &&
        (!input.thread_key || rec.thread_key === input.thread_key),
    );
    if (filtered.length > 0) {
      warnings.push("使用 agent/thread/time_window 受控回退关联动作证据");
    }
  }

  const primaryFiltered = filtered;
  const delegatedReports: string[] = [];
  const delegatedRecords: ActionEvidenceRecord[] = [];
  for (const delegatedReportId of (input.delegated_report_ids ?? []).map(str).filter(Boolean)) {
    const reportWrite = inWindow.find(
      (rec) => rec.event_type === "report.write" && rec.report_id === delegatedReportId,
    );
    if (!reportWrite?.task_id) {
      warnings.push(`delegated_report ${delegatedReportId}: report.write evidence unavailable`);
      continue;
    }
    const records = inWindow.filter(
      (rec) =>
        rec.event_type !== "report.write" &&
        sameTaskId(rec.task_id, reportWrite.task_id) &&
        (!reportWrite.session_id || rec.session_id === reportWrite.session_id),
    );
    if (records.length === 0) {
      warnings.push(`delegated_report ${delegatedReportId}: execution evidence unavailable`);
      continue;
    }
    delegatedReports.push(delegatedReportId);
    delegatedRecords.push(...records);
  }
  const evidenceRecords = [...primaryFiltered, ...delegatedRecords];

  const filesRead = new Set<string>();
  const filesChanged = new Set<string>();
  const commands: EvidenceSummaryCommand[] = [];
  const dataQueries: EvidenceSummaryDataQuery[] = [];
  const browserActions: EvidenceSummary["browser_actions"] = [];
  const callIds = new Set(
    primaryFiltered.map((rec) => rec.call_id).filter((value): value is string => Boolean(value)),
  );

  const primaryReportWrite = primaryFiltered.find(
    (rec) => rec.event_type === "report.write" && (!reportId || rec.report_id === reportId),
  );
  let reportFound = Boolean(primaryReportWrite);
  let reportPath = primaryReportWrite?.event_type === "report.write"
    ? primaryReportWrite.path
    : undefined;
  let agentId = primaryFiltered.find((rec) => rec.agent_id)?.agent_id ?? "";
  let role = primaryFiltered.find((rec) => rec.role)?.role ?? "";

  for (const rec of evidenceRecords) {
    switch (rec.event_type) {
      case "file.read":
        if (rec.path && rec.path !== "(unknown)") filesRead.add(rec.path);
        break;
      case "file.edit":
      case "file.write":
        if (rec.path && rec.path !== "(unknown)") filesChanged.add(rec.path);
        break;
      case "command.run":
        commands.push({
          command: rec.command,
          cwd: rec.cwd,
          started_at: rec.at,
          ...(rec.duration_ms != null
            ? { ended_at: new Date(Date.parse(rec.at) + rec.duration_ms).toISOString() }
            : {}),
          exit_code: rec.exit_code,
          stdout_ref: rec.stdout_ref,
          stderr_ref: rec.stderr_ref,
        });
        break;
      case "data.query":
        dataQueries.push({
          query_id: rec.query_id,
          query_summary: rec.query_summary,
          row_count: rec.row_count,
        });
        break;
      case "report.write":
        if (primaryFiltered.includes(rec)) {
          reportFound = true;
          reportPath = rec.path;
          if (rec.report_id) reportId = rec.report_id;
          if (rec.task_id) taskId = rec.task_id;
        }
        break;
      case "browser.action":
        browserActions.push({
          action: rec.action,
          url: rec.url,
          screenshot_ref: rec.screenshot_ref,
        });
        break;
      default:
        break;
    }
  }

  const resolvedSessionId =
    explicitSession || filtered.find((r) => r.session_id)?.session_id || "";
  const resolvedRunId = explicitRun || filtered.find((r) => r.run_id)?.run_id || "";

  const provenance = resolveExecutionProvenance({
    projectRoot: input.projectRoot,
    sessionStoreDir: input.session_store_dir,
    task_id: taskId || undefined,
    root_task_id: input.root_task_id,
    thread_key: input.thread_key,
    session_id: resolvedSessionId || sessionId || undefined,
    run_id: resolvedRunId || runId || undefined,
    attempt_id: input.attempt_id,
    lease_id: input.lease_id,
    agent_id: input.agent_id,
    role: input.role,
    panelRecords: primaryFiltered,
  });
  const provenanceVerified =
    provenance.status === "verified" || provenance.status === "observability_gap";
  if (provenanceVerified) {
    for (let index = warnings.length - 1; index >= 0; index--) {
      if (warnings[index]?.startsWith("bound evidence unavailable")) warnings.splice(index, 1);
    }
  }
  if (provenance.status === "conflict") {
    warnings.push(
      ...provenance.conflicts.map(
        (conflict) =>
          `binding_conflict: ${conflict.field} ${conflict.left_source}=${conflict.left_value} ${conflict.right_source}=${conflict.right_value}`,
      ),
    );
  } else if (
    provenance.status === "observability_gap" &&
    provenance.observability_gaps.includes("panel_action_log_absent")
  ) {
    warnings.push("observability_gap: panel_action_log_absent");
  }

  // report.write 只证明落盘，不算 session 执行证据（否则 fact gate 的 session_evidence_gap 永远触发不了）
  const sessionToolEvidence = primaryFiltered.filter(
    (r) =>
      r.session_id === resolvedSessionId &&
      r.event_type !== "report.write",
  );

  const citedLoaded = input.report_body
    ? loadCitedEvidenceFiles(input.projectRoot, input.report_body)
    : [];
  for (const cited of citedLoaded) {
    if (cited.ok) {
      filesRead.add(cited.path);
    } else if (cited.warning) {
      warnings.push(`cited_evidence ${cited.path}: ${cited.warning}`);
    }
  }
  const citedOk = citedLoaded.filter((c) => c.ok).map((c) => c.path);

  return {
    task_id: taskId || undefined,
    report_id: reportId || undefined,
    agent_id: agentId || undefined,
    role: role || undefined,
    session: {
      found: Boolean(
        (resolvedSessionId && sessionToolEvidence.length > 0) || provenanceVerified,
      ),
      session_id: resolvedSessionId || provenance.binding.session_id || undefined,
      run_id: resolvedRunId || provenance.binding.run_id || undefined,
      tool_calls_count: callIds.size > 0 ? callIds.size : primaryFiltered.length || undefined,
      provenance_status: provenance.status,
    },
    files: {
      read: [...filesRead].sort(),
      changed: [...filesChanged].sort(),
    },
    commands,
    report: { found: reportFound, path: reportPath },
    data_queries: dataQueries,
    browser_actions: browserActions,
    cited_evidence_files: citedOk.length > 0 ? citedOk : undefined,
    delegated_reports: delegatedReports.length > 0 ? delegatedReports : undefined,
    warnings,
    execution_provenance: provenance,
  };
}

function str(v: unknown): string {
  return typeof v === "string" ? v.trim() : v != null ? String(v).trim() : "";
}
