import { describe, it, expect, vi, beforeAll } from "vitest";
import { calculateSimilarity } from "./similarity-score";

describe("calculateSimilarity - basic tests", () => {
  it.each([
    {
      name: "first text is null",
      firstText: null,
      secondText: "text2",
      expected: 0.0,
    },
    {
      name: "second text is undefined",
      firstText: "text1",
      secondText: undefined,
      expected: 0.0,
    },
    {
      name: "both texts are null",
      firstText: null,
      secondText: null,
      expected: 0.0,
    },
    {
      name: "texts are identical",
      firstText: "Hello world",
      secondText: "Hello world",
      expected: 1.0,
    },
    {
      name: "texts differ only by case",
      firstText: "Hello World",
      secondText: "hello world",
      expected: 1.0,
    },
    {
      name: "texts are completely different",
      firstText: "abc",
      secondText: "xyz",
      expected: 0.0,
    },
    {
      name: "texts differ only by surrounding whitespace",
      firstText: "  Hello world  ",
      secondText: "Hello world",
      expected: 1.0,
    },
    {
      name: "texts differ only by special character case",
      firstText: "Text with $pecial chars!",
      secondText: "text with $pecial chars!",
      expected: 1.0,
    },
    {
      name: "first text is empty after normalization",
      firstText: "",
      secondText: "text2",
      expected: 0.0,
    },
    {
      name: "second text is empty after normalization",
      firstText: "text1",
      secondText: "",
      expected: 0.0,
    },
    {
      name: "both texts are whitespace-only",
      firstText: "   ",
      secondText: "   ",
      expected: 1.0,
    },
  ])("should return $expected when $name", ({
    firstText,
    secondText,
    expected,
  }) => {
    expect(calculateSimilarity(firstText, secondText)).toBe(expected);
  });

  it("should return partial score for similar texts", () => {
    const score = calculateSimilarity(
      "This is the first sentence.",
      "This is the second sentence.",
    );
    expect(score).toBeGreaterThan(0.5);
    expect(score).toBeLessThan(1.0);
  });

  it("should handle a realistic email with minor changes", () => {
    const original = `Hi Team,

Just a quick reminder about the meeting tomorrow at 10 AM. Please come prepared to discuss the quarterly results.

Thanks,
Bob`;

    const modified = `Hi Team,

Just a quick reminder about the all-hands meeting tomorrow at 10 AM. Please come prepared to discuss the quarterly results.

Best,
Bob`;

    const score = calculateSimilarity(original, modified);

    // Should be very similar but not identical
    expect(score).toBeGreaterThan(0.9);
    expect(score).toBeLessThan(1.0);
  });

  it("should detect small word changes", () => {
    const score = calculateSimilarity(
      "I will review this tomorrow",
      "I will review this today",
    );
    // Should be similar but not identical
    expect(score).toBeGreaterThan(0.7);
    expect(score).toBeLessThan(1.0);
  });
});

/**
 * Integration tests that use the real implementation with ParsedMessage objects.
 * These test the actual Outlook HTML handling fix.
 */
describe("calculateSimilarity - integration tests with ParsedMessage", () => {
  // Import real implementation without mocks
  let realCalculateSimilarity: typeof calculateSimilarity;

  beforeAll(async () => {
    // Clear the module cache and re-import without mocks
    vi.resetModules();
    vi.doUnmock("@/utils/mail");
    const module = await import("./similarity-score");
    realCalculateSimilarity = module.calculateSimilarity;
  });

  const createParsedMessage = (
    textPlain: string,
    bodyContentType?: "html" | "text",
  ) => ({
    id: "msg-123",
    threadId: "thread-456",
    textPlain,
    textHtml: undefined,
    subject: "Test Subject",
    date: new Date().toISOString(),
    snippet: "snippet",
    historyId: "12345",
    internalDate: "1234567890",
    headers: {
      from: "test@example.com",
      to: "recipient@example.com",
      subject: "Test",
      date: "Mon, 1 Jan 2024 12:00:00 +0000",
    },
    labelIds: [] as string[],
    inline: [] as never[],
    bodyContentType,
  });

  describe("Outlook HTML content handling", () => {
    it("should return 1.0 when comparing stored plain text with Outlook HTML response", () => {
      const storedContent = "Hello, this is a test draft";
      const outlookMessage = createParsedMessage(
        '<html><body><div dir="ltr">Hello, this is a test draft</div></body></html>',
        "html",
      );

      const score = realCalculateSimilarity(storedContent, outlookMessage);
      expect(score).toBe(1.0);
    });

    it("should return 1.0 when comparing stored content with signature to Outlook HTML", () => {
      const storedContent =
        'Hello, this is a test draft\n\nDrafted by <a href="http://localhost:3000/?ref=ABC">Inbox Zero</a>.';
      const outlookMessage = createParsedMessage(
        '<html><body><div dir="ltr">Hello, this is a test draft<br><br>Drafted by <a href="http://localhost:3000/?ref=ABC">Inbox Zero</a>.</div></body></html>',
        "html",
      );

      const score = realCalculateSimilarity(storedContent, outlookMessage);
      expect(score).toBe(1.0);
    });

    it("should return 1.0 for Outlook response with quoted content", () => {
      const storedContent = "Thanks for the update!";
      const outlookMessage = createParsedMessage(
        `<html><head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"></head><body><div dir="ltr">Thanks for the update!</div><br><div class="gmail_quote gmail_quote_container"><div dir="ltr" class="gmail_attr">On Tue, 11 Nov 2025 at 2:18, John wrote:<br></div><blockquote class="gmail_quote" style="margin:0px 0px 0px 0.8ex; border-left:1px solid rgb(204,204,204); padding-left:1ex"><div dir="ltr">Previous message</div></blockquote></div></body></html>`,
        "html",
      );

      const score = realCalculateSimilarity(storedContent, outlookMessage);
      expect(score).toBe(1.0);
    });

    it("should ignore Outlook signature containers in sent message HTML", () => {
      const storedContent = "Short reply body.";
      const outlookMessage = createParsedMessage(
        `<html><body><div>Short reply body.</div><br><div id="Signature-abcd"><div>Company award line</div><div>Role and credential line</div><div>Office | 555-0100</div><div>Long confidentiality disclaimer.</div></div></body></html>`,
        "html",
      );

      const score = realCalculateSimilarity(storedContent, outlookMessage);
      expect(score).toBe(1.0);
    });
  });

  describe("Gmail content handling (with ParsedMessage)", () => {
    it("should return 1.0 when comparing stored content with Gmail response with quotes", () => {
      const storedContent = "Thanks for reaching out! I'll get back to you.";
      const gmailMessage = createParsedMessage(
        `Thanks for reaching out! I'll get back to you.

On Mon, Jan 1, 2024 at 10:00 AM Sender <sender@example.com> wrote:
> Original message content here`,
      );

      const score = realCalculateSimilarity(storedContent, gmailMessage);
      expect(score).toBe(1.0);
    });

    it("should return 1.0 for identical content with different newline styles", () => {
      const storedContent = "Line 1\nLine 2\nLine 3";
      const gmailMessage = createParsedMessage("Line 1\r\nLine 2\r\nLine 3");

      const score = realCalculateSimilarity(storedContent, gmailMessage);
      expect(score).toBe(1.0);
    });

    it("should strip converted Gmail HTML quote blocks before comparing drafts", () => {
      const storedContent = `Checking on this now.

Drafted by <a href="https://www.getinboxzero.com/?ref=ABC123">Inbox Zero</a>.`;
      const gmailMessage = {
        ...createParsedMessage(""),
        textPlain: undefined,
        textHtml: `<div dir="ltr">Checking on this now.<br><br>Drafted by <a href="https://www.getinboxzero.com/?ref=ABC123">Inbox Zero</a>.</div><br><div class="gmail_quote gmail_quote_container"><div dir="ltr" class="gmail_attr">Le lun. 27 avr. 2026, Sender &lt;<a href="mailto:sender@example.com">sender@example.com</a>&gt; a écrit:<br></div><blockquote class="gmail_quote"><div dir="ltr">Previous message</div></blockquote></div>`,
      };

      const score = realCalculateSimilarity(storedContent, gmailMessage);
      expect(score).toBe(1.0);
    });

    it("should ignore Gmail signature containers in sent message HTML", () => {
      const storedContent = "Thanks, I handled this now.";
      const gmailMessage = {
        ...createParsedMessage(""),
        textPlain: undefined,
        textHtml: `<div dir="ltr">Thanks, I handled this now.</div><br><span class="gmail_signature_prefix">-- </span><br><div class="gmail_signature"><div>Sender Name</div><div>Company</div><div>555-0100</div><div>Street address</div></div>`,
      };

      const score = realCalculateSimilarity(storedContent, gmailMessage);
      expect(score).toBe(1.0);
    });

    it("should ignore standard plain text signature delimiters", () => {
      const storedContent = "Thanks, I handled this now.";
      const gmailMessage = createParsedMessage(`Thanks, I handled this now.

-- 
Sender Name
Company
555-0100`);

      const score = realCalculateSimilarity(storedContent, gmailMessage);
      expect(score).toBe(1.0);
    });

    it("should decode named HTML entities before comparing plain text signatures", () => {
      const storedContent = "Thanks for the update.\n\n&lt;3 Team";
      const gmailMessage = createParsedMessage(
        "Thanks for the update.\n\n<3 Team",
      );

      const score = realCalculateSimilarity(storedContent, gmailMessage);
      expect(score).toBe(1.0);
    });

    it.each([
      { emoji: "👋", hex: "&#x1F44B;", name: "waving hand" },
      { emoji: "😀", hex: "&#x1F600;", name: "grinning face" },
      { emoji: "🎉", hex: "&#x1F389;", name: "party popper" },
      { emoji: "❤", hex: "&#x2764;", name: "red heart" },
      { emoji: "🚀", hex: "&#x1F680;", name: "rocket" },
      { emoji: "✅", hex: "&#x2705;", name: "check mark" },
      { emoji: "🔥", hex: "&#x1F525;", name: "fire" },
      { emoji: "👍", hex: "&#x1F44D;", name: "thumbs up" },
      { emoji: "💡", hex: "&#x1F4A1;", name: "light bulb" },
      { emoji: "📧", hex: "&#x1F4E7;", name: "email" },
    ])("should return 1.0 when stored content has HTML entity $name ($hex) and Gmail has actual emoji", ({
      emoji,
      hex,
    }) => {
      const storedContent = `hey, 10am works for me! see you then ${hex}`;
      const gmailMessage = createParsedMessage(
        `hey, 10am works for me! see you then ${emoji}

On Tue, 27 Jan 2026 at 2:59, Test User <test@example.com> wrote:
> Previous message`,
      );

      const score = realCalculateSimilarity(storedContent, gmailMessage);
      expect(score).toBe(1.0);
    });

    it("should return 1.0 when stored content has an HTML referral footer and Gmail has plain text", () => {
      const storedContent = `Checking on the usage numbers now (scanned vs drafted). Should have those soon.

Took a look at the doc. Option 2 seems like a good middle ground for balancing the unit economics. Happy to jam on the specifics once I pull the data.

Drafted by <a href="https://www.getinboxzero.com/?ref=ABC123">Inbox Zero</a>.`;
      const gmailMessage = createParsedMessage(
        `Checking on the usage numbers now (scanned vs drafted). Should have those soon.

Took a look at the doc. Option 2 seems like a good middle ground for balancing the unit economics. Happy to jam on the specifics once I pull the data.

Drafted by Inbox Zero.

On Tue, 1 Apr 2026 at 10:00, Sender <sender@example.com> wrote:
> Previous message`,
      );

      const score = realCalculateSimilarity(storedContent, gmailMessage);
      expect(score).toBe(1.0);
    });

    it("should return 0.0 when the sent message has no comparable body", () => {
      const storedContent = `Thanks, I handled this now.

--
Sender Name
Company
555-0100`;
      const gmailMessage = createParsedMessage("");

      const score = realCalculateSimilarity(storedContent, gmailMessage);
      expect(score).toBe(0.0);
    });

    it("should preserve plain text angle brackets that are not real HTML", () => {
      const storedContent = "Daily Updates <updates@example.com>";
      const gmailMessage = createParsedMessage("Daily Updates <attachment>");

      const score = realCalculateSimilarity(storedContent, gmailMessage);
      expect(score).toBeLessThan(1.0);
    });

    it("should not strip email addresses whose local part starts with an HTML tag letter", () => {
      const storedContent = "Please contact <alice@example.com>";
      const gmailMessage = createParsedMessage(
        "Please contact <alex@example.com>",
      );

      const score = realCalculateSimilarity(storedContent, gmailMessage);
      expect(score).toBeLessThan(1.0);
    });

    it("should not misclassify plain text starting with <a... as HTML", () => {
      const storedContent = "The <apple> is red";
      const gmailMessage = createParsedMessage("The <banana> is yellow");

      const score = realCalculateSimilarity(storedContent, gmailMessage);
      expect(score).toBeLessThan(1.0);
    });

    it.each([
      { emoji: "👋", decimal: "128075", name: "waving hand" },
      { emoji: "😀", decimal: "128512", name: "grinning face" },
      { emoji: "🎉", decimal: "127881", name: "party popper" },
      { emoji: "❤", decimal: "10084", name: "red heart" },
      { emoji: "🚀", decimal: "128640", name: "rocket" },
      { emoji: "✅", decimal: "9989", name: "check mark" },
      { emoji: "🔥", decimal: "128293", name: "fire" },
      { emoji: "👍", decimal: "128077", name: "thumbs up" },
      { emoji: "💡", decimal: "128161", name: "light bulb" },
      { emoji: "📧", decimal: "128231", name: "email" },
    ])("should decode decimal HTML entity for $name (&#$decimal;) to $emoji", ({
      emoji,
      decimal,
    }) => {
      const storedContent = `Hello &#${decimal}; world`;
      const gmailMessage = createParsedMessage(`Hello ${emoji} world`);

      const score = realCalculateSimilarity(storedContent, gmailMessage);
      expect(score).toBe(1.0);
    });

    it("should not throw on invalid code points and leave them unchanged", () => {
      const storedContent = "Hello &#1114112; and &#xFFFFFFFF; world";
      const gmailMessage = createParsedMessage(
        "Hello &#1114112; and &#xFFFFFFFF; world",
      );

      const score = realCalculateSimilarity(storedContent, gmailMessage);
      expect(score).toBe(1.0);
    });
  });

  describe("Sent email tracking scenarios", () => {
    it("should return 1.0 when user sends AI draft unmodified", () => {
      const originalDraft = `Hi there,

Thanks for your email. I'll review this and get back to you shortly.

Best regards`;

      const sentMessage = createParsedMessage(
        `Hi there,

Thanks for your email. I'll review this and get back to you shortly.

Best regards

On Mon, Jan 1, 2024 at 9:00 AM <someone@example.com> wrote:
> Their original question`,
      );

      const score = realCalculateSimilarity(originalDraft, sentMessage);
      expect(score).toBe(1.0);
    });

    it("should exclude the account signature from the comparison score", () => {
      const signature = Array.from(
        { length: 40 },
        (_value, index) =>
          `Generic signature policy line ${index + 1}: this footer is not authored reply content.`,
      ).join("\n");
      const originalDraft = `There are three files total.

${signature}`;
      const sentMessage = createParsedMessage(`There will not be another file.

The three runs are grouped into one export.

${signature}

------------------------------------------------------------

Repeated gateway footer. This repeated footer is added outside the user's authored reply.

------------------------------------------------------------

Repeated gateway footer. This repeated footer is added outside the user's authored reply.`);

      const score = realCalculateSimilarity(originalDraft, sentMessage, {
        excludedSignatures: [signature],
      });

      expect(score).toBeLessThan(0.5);
    });

    it("should ignore forwarded payloads below the authored reply", () => {
      const originalDraft = "Can you take a look at this?";
      const sentMessage = createParsedMessage(`Can you take a look at this?

---------- Forwarded message ----------
From: sender@example.com
Subject: Original request

The original request has a lot of unrelated detail.`);

      const score = realCalculateSimilarity(originalDraft, sentMessage);
      expect(score).toBe(1.0);
    });
  });

  describe("Backwards compatibility with plain strings", () => {
    it("should handle plain string as second argument for backwards compatibility", () => {
      const storedContent = "Hello world";
      const plainString = "Hello world";

      const score = realCalculateSimilarity(storedContent, plainString);
      expect(score).toBe(1.0);
    });

    it("should still strip quotes when using plain string", () => {
      const storedContent = "My reply";
      const plainString = `My reply

On Mon, Jan 1, 2024 wrote:
> Quote content`;

      const score = realCalculateSimilarity(storedContent, plainString);
      expect(score).toBe(1.0);
    });
  });
});
