import { AI_SENDER, USER_SENDER } from "@/constants";
import { MessageContext } from "@/types/message";
import { TFile } from "obsidian";
import { MessageRepository } from "./MessageRepository";

// Mock the settings module
jest.mock("@/settings/model", () => ({
  getSettings: jest.fn(() => ({ debug: false })),
}));

/**
 * This test file demonstrates the complete message lifecycle with context notes.
 * It serves as both a test and documentation of how messages flow through the system.
 */
describe("Message Lifecycle with Context Notes - Complete Example", () => {
  let messageRepository: MessageRepository;

  beforeEach(() => {
    messageRepository = new MessageRepository();
  });

  it("should demonstrate complete message lifecycle with context note", () => {
    // Step 1: User types a message and attaches a note
    const userDisplayText = "Please summarize the key points";
    const attachedNote: TFile = {
      path: "meeting-notes-2024-01-15.md",
      name: "meeting-notes-2024-01-15.md",
      basename: "meeting-notes-2024-01-15",
      extension: "md",
    } as TFile;

    const context: MessageContext = {
      notes: [attachedNote],
      urls: [],
      selectedTextContexts: [],
    };

    // Step 2: Message is stored with basic display text
    const messageId = messageRepository.addMessage(
      userDisplayText,
      userDisplayText, // Initially same as display
      USER_SENDER,
      context
    );

    // Verify initial storage
    let displayMessages = messageRepository.getDisplayMessages();
    expect(displayMessages).toHaveLength(1);
    expect(displayMessages[0]).toMatchObject({
      message: "Please summarize the key points",
      sender: USER_SENDER,
      context: {
        notes: [
          expect.objectContaining({
            basename: "meeting-notes-2024-01-15",
          }),
        ],
      },
    });

    // Step 3: Context Manager processes the note and updates processed text
    const processedTextWithContext = `Please summarize the key points

<note_context>
<title>meeting-notes-2024-01-15</title>
<path>meeting-notes-2024-01-15.md</path>
<ctime>2024-01-15T10:00:00.000Z</ctime>
<mtime>2024-01-15T14:30:00.000Z</mtime>
<content>
# Team Meeting - January 15, 2024

## Attendees
- John (Product Manager)
- Sarah (Tech Lead)
- Mike (Designer)

## Key Decisions
1. Launch date moved to Q2 2024
2. MVP features: Auth, Dashboard, Analytics
3. Tech stack: React + Node.js + PostgreSQL

## Action Items
- Sarah: Set up CI/CD pipeline by Jan 20
- Mike: Complete dashboard mockups by Jan 22
- John: Finalize user stories by Jan 18
</content>
</note_context>`;

    messageRepository.updateProcessedText(messageId, processedTextWithContext);

    // Step 4: Verify different views for UI vs LLM

    // UI View - shows only what user typed
    displayMessages = messageRepository.getDisplayMessages();
    expect(displayMessages[0].message).toBe("Please summarize the key points");

    // LLM History View - returns display text only (no context)
    // Context should come from envelope (L3 layer), not baked into history
    const llmMessages = messageRepository.getLLMMessages();
    expect(llmMessages[0].message).toBe("Please summarize the key points");
    expect(llmMessages[0].message).not.toContain("Team Meeting - January 15, 2024");

    // LLM Current Turn - use getLLMMessage(id) for message with context
    const llmCurrentMessage = messageRepository.getLLMMessage(messageId);
    expect(llmCurrentMessage?.message).toBe(processedTextWithContext);
    expect(llmCurrentMessage?.message).toContain("Team Meeting - January 15, 2024");
    expect(llmCurrentMessage?.message).toContain("Launch date moved to Q2 2024");

    // Step 5: AI responds based on the context
    const aiResponse = `Based on the meeting notes, here are the key points:

**Main Decisions:**
• Project launch postponed to Q2 2024
• MVP will include authentication, dashboard, and analytics features
• Technology choices: React frontend, Node.js backend, PostgreSQL database

**Team Responsibilities:**
• Sarah (Tech Lead): CI/CD pipeline setup - Due Jan 20
• Mike (Designer): Dashboard mockup designs - Due Jan 22
• John (Product Manager): User story finalization - Due Jan 18

The team appears to be taking a pragmatic approach with a focused MVP scope and clear task delegation.`;

    messageRepository.addMessage(
      aiResponse,
      aiResponse, // AI messages have same display and processed text
      AI_SENDER
    );

    // Step 6: Verify complete conversation
    const finalDisplayMessages = messageRepository.getDisplayMessages();
    expect(finalDisplayMessages).toHaveLength(2);

    // User message with context badge
    expect(finalDisplayMessages[0]).toMatchObject({
      message: "Please summarize the key points",
      sender: USER_SENDER,
      context: {
        notes: expect.arrayContaining([
          expect.objectContaining({ basename: "meeting-notes-2024-01-15" }),
        ]),
      },
    });

    // AI response
    expect(finalDisplayMessages[1]).toMatchObject({
      message: expect.stringContaining("Based on the meeting notes"),
      sender: AI_SENDER,
    });

    // Step 7: Verify what LLM history contains (for chat memory)
    const llmView = messageRepository.getLLMMessages();
    expect(llmView).toHaveLength(2);

    // LLM history contains raw messages only (no context)
    expect(llmView[0].message).toBe("Please summarize the key points");
    expect(llmView[0].message).not.toContain("Team Meeting - January 15, 2024");

    // LLM history contains AI response
    expect(llmView[1].message).toContain("Based on the meeting notes");

    // Context metadata is preserved (envelope may or may not be built depending on flow)
    expect(llmView[0].context).toBeDefined();
  });

  it("should handle message edit with context reprocessing", () => {
    // Initial message with context
    const initialText = "List the attendees";
    const note: TFile = {
      path: "meeting.md",
      name: "meeting.md",
      basename: "meeting",
      extension: "md",
    } as TFile;

    const context: MessageContext = {
      notes: [note],
      urls: [],
      selectedTextContexts: [],
    };

    // Add initial message with properly formatted context
    const messageId = messageRepository.addMessage(
      initialText,
      `${initialText}

<note_context>
<title>meeting</title>
<path>meeting.md</path>
<ctime>2024-01-10T09:00:00.000Z</ctime>
<mtime>2024-01-10T10:00:00.000Z</mtime>
<content>
Attendees: Alice, Bob, Charlie
</content>
</note_context>`,
      USER_SENDER,
      context
    );

    // User edits the message
    const editedText = "List the attendees and their roles";
    messageRepository.editMessage(messageId, editedText);

    // Context is reprocessed (simulated)
    const reprocessedText = `${editedText}

<note_context>
<title>meeting</title>
<path>meeting.md</path>
<ctime>2024-01-10T09:00:00.000Z</ctime>
<mtime>2024-01-10T10:30:00.000Z</mtime>
<content>
Attendees: Alice (PM), Bob (Dev), Charlie (QA)
</content>
</note_context>`;
    messageRepository.updateProcessedText(messageId, reprocessedText);

    // Verify the edit
    const displayMessages = messageRepository.getDisplayMessages();
    expect(displayMessages[0].message).toBe("List the attendees and their roles");

    // LLM history view contains display text only
    const llmMessages = messageRepository.getLLMMessages();
    expect(llmMessages[0].message).toBe("List the attendees and their roles");
    expect(llmMessages[0].message).not.toContain("Alice (PM), Bob (Dev), Charlie (QA)");

    // Full context available via getLLMMessage(id)
    const llmMessage = messageRepository.getLLMMessage(messageId);
    expect(llmMessage?.message).toContain("List the attendees and their roles");
    expect(llmMessage?.message).toContain("Alice (PM), Bob (Dev), Charlie (QA)");
  });

  it("should maintain context through conversation", () => {
    // User asks initial question with context
    const context: MessageContext = {
      notes: [
        {
          path: "budget.md",
          name: "budget.md",
          basename: "budget",
          extension: "md",
        } as TFile,
      ],
      urls: [],
      selectedTextContexts: [],
    };

    messageRepository.addMessage(
      "What is our total budget?",
      `What is our total budget?

<note_context>
<title>budget</title>
<path>budget.md</path>
<ctime>2024-01-01T08:00:00.000Z</ctime>
<mtime>2024-01-05T16:00:00.000Z</mtime>
<content>
Q1: $100k
Q2: $150k
Q3: $200k
Q4: $250k
</content>
</note_context>`,
      USER_SENDER,
      context
    );

    // AI responds
    messageRepository.addMessage(
      "Based on the budget document, your total budget for the year is $700k ($100k + $150k + $200k + $250k).",
      "Based on the budget document, your total budget for the year is $700k ($100k + $150k + $200k + $250k).",
      AI_SENDER
    );

    // User asks follow-up (no new context needed)
    messageRepository.addMessage(
      "What percentage increase is Q4 over Q1?",
      "What percentage increase is Q4 over Q1?",
      USER_SENDER
    );

    // Verify conversation flow
    const messages = messageRepository.getDisplayMessages();
    expect(messages).toHaveLength(3);

    // First message has context
    expect(messages[0].context?.notes).toHaveLength(1);

    // Follow-up messages don't need context repeated
    expect(messages[1].context).toBeUndefined();
    expect(messages[2].context).toBeUndefined();

    // LLM history contains raw messages only (no context in history)
    const llmMessages = messageRepository.getLLMMessages();
    expect(llmMessages[0].message).not.toContain("<note_context>");
    expect(llmMessages[0].message).not.toContain("<title>budget</title>");

    // Context available via getLLMMessage(id) for current turn processing
    const firstMessage = messageRepository.getLLMMessage(llmMessages[0].id!);
    expect(firstMessage?.message).toContain("<note_context>");
    expect(firstMessage?.message).toContain("<title>budget</title>");
  });
});
