import { SystemRole } from "@capakraken/shared";
import { describe, expect, it, vi } from "vitest";
import { commentRouter } from "../router/comment.js";
import { createCallerFactory } from "../trpc.js";

vi.mock("../lib/comment-entity-registry.js", async (importOriginal) => {
  const actual = await importOriginal<typeof import("../lib/comment-entity-registry.js")>();
  return {
    ...actual,
    assertCommentEntityAccess: vi.fn().mockResolvedValue({
      listMentionCandidates: vi.fn(),
      buildLink: vi.fn().mockReturnValue("/estimates/est_1"),
    }),
  };
});

vi.mock("../lib/audit.js", () => ({
  createAuditEntry: vi.fn().mockResolvedValue(undefined),
}));

vi.mock("../lib/create-notification.js", () => ({
  createNotification: vi.fn().mockResolvedValue(undefined),
}));

const createCaller = createCallerFactory(commentRouter);

function createContext(db: Record<string, unknown>, role = SystemRole.MANAGER) {
  return {
    session: {
      user: { email: "mgr@example.com", name: "Manager", image: null },
      expires: "2099-01-01T00:00:00.000Z",
    },
    db: db as never,
    dbUser: {
      id: "user_mgr",
      systemRole: role,
      permissionOverrides: null,
    },
  };
}

function makeDb(commentCreate: ReturnType<typeof vi.fn>) {
  return {
    estimate: {
      findUnique: vi.fn().mockResolvedValue({ id: "est_1" }),
    },
    comment: {
      create: commentCreate,
    },
    notification: {
      create: vi.fn(),
    },
    auditLog: {
      create: vi.fn(),
    },
  };
}

describe("comment router — HTML sanitization before DB write", () => {
  it("strips script tags from comment body before writing to the database", async () => {
    // stripHtml removes the <script> and </script> tags; the inner text "alert(1)"
    // is plain text and is therefore preserved — the HTML injection vector (the tags
    // themselves) is eliminated.
    const sanitizedBody = "alert(1)Hello";
    const commentCreate = vi.fn().mockResolvedValue({
      id: "comment_1",
      body: sanitizedBody,
      author: { id: "user_mgr", name: "Manager", email: "mgr@example.com", image: null },
    });
    const caller = createCaller(createContext(makeDb(commentCreate)));

    await caller.create({
      entityType: "estimate",
      entityId: "est_1",
      body: "<script>alert(1)</script>Hello",
    });

    expect(commentCreate).toHaveBeenCalledOnce();
    const callArg = commentCreate.mock.calls[0]![0] as { data: { body: string } };
    // Tags are stripped — no angle brackets remain in what the DB receives
    expect(callArg.data.body).toBe(sanitizedBody);
    expect(callArg.data.body).not.toContain("<script>");
    expect(callArg.data.body).not.toContain("</script>");
  });

  it("strips bold and italic tags but preserves the text content", async () => {
    const commentCreate = vi.fn().mockResolvedValue({
      id: "comment_2",
      body: "This is bold and italic",
      author: { id: "user_mgr", name: "Manager", email: "mgr@example.com", image: null },
    });
    const caller = createCaller(createContext(makeDb(commentCreate)));

    await caller.create({
      entityType: "estimate",
      entityId: "est_1",
      body: "This is <b>bold</b> and <i>italic</i>",
    });

    expect(commentCreate).toHaveBeenCalledOnce();
    const callArg = commentCreate.mock.calls[0]![0] as { data: { body: string } };
    expect(callArg.data.body).toBe("This is bold and italic");
  });

  it("passes plain text through unchanged", async () => {
    const commentCreate = vi.fn().mockResolvedValue({
      id: "comment_3",
      body: "Just a plain comment",
      author: { id: "user_mgr", name: "Manager", email: "mgr@example.com", image: null },
    });
    const caller = createCaller(createContext(makeDb(commentCreate)));

    await caller.create({
      entityType: "estimate",
      entityId: "est_1",
      body: "Just a plain comment",
    });

    expect(commentCreate).toHaveBeenCalledOnce();
    const callArg = commentCreate.mock.calls[0]![0] as { data: { body: string } };
    expect(callArg.data.body).toBe("Just a plain comment");
  });

  it("strips HTML but preserves mention syntax and correctly populates the mentions array", async () => {
    const commentCreate = vi.fn().mockResolvedValue({
      id: "comment_4",
      body: "Hi @[Alice](user_1) — please review",
      author: { id: "user_mgr", name: "Manager", email: "mgr@example.com", image: null },
    });
    const caller = createCaller(createContext(makeDb(commentCreate)));

    await caller.create({
      entityType: "estimate",
      entityId: "est_1",
      body: "Hi @[Alice](user_1) — <b>please review</b>",
    });

    expect(commentCreate).toHaveBeenCalledOnce();
    const callArg = commentCreate.mock.calls[0]![0] as {
      data: { body: string; mentions: string[] };
    };
    expect(callArg.data.body).toBe("Hi @[Alice](user_1) — please review");
    expect(callArg.data.mentions).toContain("user_1");
  });
});
