import { describe, expect, it } from "bun:test";

import { buildConflictCandidateRecord } from "../conflict-engine.js";
import { buildConflictAuditSummary, clusterConflicts, summarizeConflictAdvice } from "../conflict-advisor.js";

/** Returns an ISO timestamp N days before now (defaults to 4 days = firmly "stale"). */
function daysAgo(n: number): string {
  return new Date(Date.now() - n * 24 * 60 * 60 * 1000).toISOString();
}

function createConflictRecord(overrides: Record<string, unknown> = {}) {
  const defaultTs = daysAgo(4); // 4 days ago → "stale" (3-6 days range)
  return {
    ...buildConflictCandidateRecord({
      canonicalKey: "user-reply-style",
      category: "preferences",
      fingerprint: "user-reply-style--durable-1--source-1--new-text",
      reason: "promotion_conflicts_with_existing_durable",
      existing: {
        memoryId: "durable-1",
        text: "User prefers concise, direct, technical replies.",
        category: "preferences",
        scope: "memory:agent",
        importance: 0.84,
        metadata: "{\"source\":\"agent\"}",
        canonicalKey: "user-reply-style",
      },
      incoming: {
        text: "User prefers concise direct technical replies.",
        category: "preferences",
        scope: "memory:agent",
        importance: 0.78,
        metadata: "{\"source\":\"agent\"}",
        source: "agent",
        sourceMemoryId: "source-1",
        sourceCategory: "events",
      },
      createdAt: defaultTs,
      updatedAt: defaultTs,
    }),
    ...overrides,
  };
}

describe("summarizeConflictAdvice", () => {
  it("suggests keep_existing for text-equivalent conflicts", () => {
    const advice = summarizeConflictAdvice(createConflictRecord() as any);

    expect(advice.suggestedResolution).toBe("keep_existing");
    expect(advice.confidence).toBe("high");
    expect(advice.similarity).toBe(1);
  });

  it("suggests keep_existing for cross-category canonical key conflicts", () => {
    const advice = summarizeConflictAdvice(createConflictRecord({
      reason: "canonical_key_conflicts_with_existing_durable",
      category: "events",
      incoming: {
        text: "Reply-style observation imported as an event.",
        category: "events",
        scope: "memory:agent",
        importance: 0.78,
        metadata: "{\"source\":\"agent\"}",
        source: "agent",
        sourceMemoryId: "source-2",
        sourceCategory: "events",
      },
    }) as any);

    expect(advice.suggestedResolution).toBe("keep_existing");
    expect(["medium", "high"]).toContain(advice.confidence);
    expect(advice.reasons.some((reason) => reason.includes("canonical key"))).toBe(true);
  });

  it("suggests accept_incoming when the incoming text is a tighter rewrite of the same memory", () => {
    const advice = summarizeConflictAdvice(createConflictRecord({
      existing: {
        memoryId: "durable-1",
        text: "User prefers concise grounded technical replies and avoids sales language in the final copy.",
        category: "preferences",
        scope: "memory:agent",
        importance: 0.84,
        metadata: "{\"source\":\"agent\"}",
        canonicalKey: "user-reply-style",
      },
      incoming: {
        text: "User prefers concise grounded technical replies and avoids sales language.",
        category: "preferences",
        scope: "memory:agent",
        importance: 0.78,
        metadata: "{\"source\":\"agent\"}",
        source: "agent",
        sourceMemoryId: "source-1",
        sourceCategory: "events",
      },
    }) as any);

    expect(advice.suggestedResolution).toBe("accept_incoming");
    expect(advice.confidence).toBe("medium");
    expect(advice.similarity).toBeGreaterThanOrEqual(0.72);
    expect(advice.mergeSuggestion).toBeUndefined();
  });

  it("offers a merged wording suggestion for same-category manual_review conflicts", () => {
    const advice = summarizeConflictAdvice(createConflictRecord({
      existing: {
        memoryId: "durable-1",
        text: "User prefers concise grounded technical replies; avoid sales language.",
        category: "preferences",
        scope: "memory:agent",
        importance: 0.84,
        metadata: "{\"source\":\"agent\"}",
        canonicalKey: "user-reply-style",
      },
      incoming: {
        text: "User prefers concise grounded technical replies; keep the tone colloquial.",
        category: "preferences",
        scope: "memory:agent",
        importance: 0.78,
        metadata: "{\"source\":\"agent\"}",
        source: "agent",
        sourceMemoryId: "source-1",
        sourceCategory: "events",
      },
    }) as any);

    expect(advice.suggestedResolution).toBe("manual_review");
    expect(advice.mergeSuggestion).toContain("User prefers concise grounded technical replies");
    expect(advice.mergeSuggestion).toContain("avoid sales language");
    expect(advice.mergeSuggestion).toContain("keep the tone colloquial");
    expect(advice.reasons.some((reason) => reason.includes("merged durable wording"))).toBe(true);
  });
});

describe("clusterConflicts", () => {
  it("groups conflicts by canonical key, reason, and category", () => {
    const clusters = clusterConflicts([
      createConflictRecord({
        conflictId: "00000000-0000-0000-0000-000000000001",
        createdAt: daysAgo(4),
        updatedAt: daysAgo(4),
      }) as any,
      createConflictRecord({
        conflictId: "00000000-0000-0000-0000-000000000002",
        createdAt: daysAgo(4),
        updatedAt: daysAgo(3),
      }) as any,
      createConflictRecord({
        canonicalKey: "project-entity-owner",
        conflictId: "00000000-0000-0000-0000-000000000003",
        fingerprint: "project-entity-owner--durable-1--source-1--new-text",
      }) as any,
    ]);

    expect(clusters).toHaveLength(2);
    expect(clusters[0]?.canonicalKey).toBe("user-reply-style");
    expect(clusters[0]?.totalCount).toBe(2);
    expect(clusters[0]?.latestConflictId).toBe("00000000-0000-0000-0000-000000000002");
    expect(clusters[0]?.attention).toBe("stale");
  });
});

describe("buildConflictAuditSummary", () => {
  it("highlights escalated or stale open clusters first", () => {
    const summary = buildConflictAuditSummary([
      createConflictRecord({
        conflictId: "00000000-0000-0000-0000-000000000001",
        createdAt: daysAgo(14),
        updatedAt: daysAgo(14),
      }) as any,
      createConflictRecord({
        canonicalKey: "project-entity-owner",
        conflictId: "00000000-0000-0000-0000-000000000002",
        fingerprint: "project-entity-owner--durable-1--source-1--new-text",
        createdAt: daysAgo(1),
        updatedAt: daysAgo(1),
      }) as any,
      createConflictRecord({
        conflictId: "00000000-0000-0000-0000-000000000003",
        status: "kept-existing",
        resolvedAt: daysAgo(0),
        updatedAt: daysAgo(0),
      }) as any,
    ], 3);

    expect(summary.totalConflicts).toBe(3);
    expect(summary.openConflicts).toBe(2);
    expect(summary.attentionCounts.resolved).toBe(1);
    expect(summary.priorityClusters[0]?.attention).toBe("escalated");
    expect(summary.suggestedActions.some((action) => action.includes("escalated"))).toBe(true);
  });

  it("returns a no-open-conflicts message when nothing is pending", () => {
    const summary = buildConflictAuditSummary([
      createConflictRecord({
        conflictId: "00000000-0000-0000-0000-000000000001",
        status: "kept-existing",
        resolvedAt: "2026-03-16T00:00:00.000Z",
      }) as any,
    ], 3);

    expect(summary.openConflicts).toBe(0);
    expect(summary.priorityClusters).toHaveLength(0);
    expect(summary.suggestedActions).toEqual(["No open conflicts need review."]);
  });
});
