import { appendFileSync, mkdirSync, writeFileSync } from "node:fs";
import { join } from "node:path";
import type { ThreadEvidenceKind, ThreadEvidenceRecord, ThreadId, TurnId } from "@best-agent/protocol";
import { readJsonLines } from "./json-lines.js";
import type { ThreadStore } from "./thread-store.js";

export interface ListThreadEvidenceInput {
  evidenceIds?: string[];
  filePath?: string;
  kind?: ThreadEvidenceKind;
  runId?: string;
  threadId: ThreadId;
  turnId?: TurnId;
}

type EvidenceIndexFile = {
  byKind: Partial<Record<ThreadEvidenceKind, number>>;
  latestCreatedAt?: string;
  totalCount: number;
  updatedAt: string;
};

export class LocalEvidenceStore {
  readonly #threads: ThreadStore;

  constructor(threads: ThreadStore) {
    this.#threads = threads;
  }

  async append(threadId: ThreadId, evidence: ThreadEvidenceRecord): Promise<boolean> {
    this.#ensureEvidenceDirectory(threadId);
    const existing = readJsonLines<ThreadEvidenceRecord>(this.#cardsPath(threadId));
    if (existing.some((candidate) => candidate.evidenceId === evidence.evidenceId)) {
      return false;
    }
    appendFileSync(this.#cardsPath(threadId), `${JSON.stringify(evidence)}\n`);
    this.#writeIndex(threadId, [...existing, evidence]);
    return true;
  }

  async list(input: ListThreadEvidenceInput): Promise<ThreadEvidenceRecord[]> {
    const evidence = readJsonLines<ThreadEvidenceRecord>(this.#cardsPath(input.threadId));
    const evidenceIds = input.evidenceIds ? new Set(input.evidenceIds) : undefined;
    return evidence.filter((candidate) => {
      if (input.kind && candidate.kind !== input.kind) {
        return false;
      }
      if (input.runId && candidate.runId !== input.runId) {
        return false;
      }
      if (input.turnId && candidate.turnId !== input.turnId) {
        return false;
      }
      if (input.filePath && !candidate.relatedFilePaths?.includes(input.filePath)) {
        return false;
      }
      if (evidenceIds && !evidenceIds.has(candidate.evidenceId)) {
        return false;
      }
      return true;
    });
  }

  #ensureEvidenceDirectory(threadId: ThreadId): void {
    mkdirSync(this.#evidenceDir(threadId), { recursive: true });
  }

  #writeIndex(threadId: ThreadId, evidence: ThreadEvidenceRecord[]): void {
    const byKind = evidence.reduce<Partial<Record<ThreadEvidenceKind, number>>>((counts, candidate) => {
      counts[candidate.kind] = (counts[candidate.kind] ?? 0) + 1;
      return counts;
    }, {});
    const index: EvidenceIndexFile = {
      byKind,
      latestCreatedAt: evidence.at(-1)?.createdAt,
      totalCount: evidence.length,
      updatedAt: new Date().toISOString(),
    };
    writeFileSync(this.#indexPath(threadId), JSON.stringify(index, null, 2));
  }

  #evidenceDir(threadId: ThreadId): string {
    return join(this.#threads.getThreadPaths(threadId).threadDir, "evidence");
  }

  #cardsPath(threadId: ThreadId): string {
    return join(this.#evidenceDir(threadId), "cards.jsonl");
  }

  #indexPath(threadId: ThreadId): string {
    return join(this.#evidenceDir(threadId), "index.json");
  }
}
