/**
 * Conversation Tracker Service Tests
 *
 * Tests the conversation tracking service with focus on:
 * - Conversation creation
 * - Message appending
 * - Daily statistics calculation
 * - Top customer identification
 * - Status updates
 * - Filtering and pagination
 */

import {
  ConversationTrackerService,
  ConversationMessage,
  CreateConversationRequest,
} from "@/lib/services/conversation-tracker.service";
import { prisma } from "@/lib/db";

// Mock dependencies
jest.mock("@paralleldrive/cuid2", () => ({
  createId: jest.fn(
    () => "mock-cuid-" + Math.random().toString(36).substr(2, 9),
  ),
}));

jest.mock("@/lib/dual-database", () => ({
  saveToBothDatabases: jest.fn(),
  saveUserToBoth: jest.fn(),
  saveAuditLogToBoth: jest.fn(),
}));

jest.mock("@/lib/logger", () => ({
  logger: {
    scope: jest.fn().mockReturnValue({
      info: jest.fn(),
      warn: jest.fn(),
      error: jest.fn(),
    }),
  },
}));

const { createId } = require("@paralleldrive/cuid2");

// Test fixtures
const mockUserId = "test-user-123";
const mockConversationId = createId();

const mockMessages: ConversationMessage[] = [
  {
    role: "user",
    content: "Hello, I need help",
    timestamp: new Date("2025-01-15T10:00:00Z"),
  },
  {
    role: "agent",
    content: "Hi! How can I assist you?",
    timestamp: new Date("2025-01-15T10:01:00Z"),
  },
];

const mockConversation = {
  id: mockConversationId,
  userId: mockUserId,
  platform: "whatsapp",
  customerPhone: "+974-5555-1234",
  customerName: "John Doe",
  messages: JSON.stringify(mockMessages),
  metadata: null,
  status: "open",
  createdAt: new Date("2025-01-15T10:00:00Z"),
  updatedAt: new Date("2025-01-15T10:00:00Z"),
};

describe("ConversationTrackerService", () => {
  let service: ConversationTrackerService;

  beforeEach(() => {
    service = new ConversationTrackerService();
    jest.clearAllMocks();
  });

  afterAll(async () => {
    await prisma.$disconnect();
  });

  describe("createConversation", () => {
    it("should create a conversation with open status and messages", async () => {
      // Mock Prisma
      const mockCreate = jest
        .spyOn(prisma.conversationLog, "create")
        .mockResolvedValue(mockConversation as any);

      const data: CreateConversationRequest = {
        platform: "whatsapp",
        customerPhone: "+974-5555-1234",
        customerName: "John Doe",
        messages: mockMessages,
        metadata: { source: "widget" },
      };

      const result = await service.createConversation(mockUserId, data);

      expect(result).toBeDefined();
      expect(result.userId).toBe(mockUserId);
      expect(result.platform).toBe("whatsapp");
      expect(result.status).toBe("open");
      expect(mockCreate).toHaveBeenCalledWith(
        expect.objectContaining({
          data: expect.objectContaining({
            userId: mockUserId,
            platform: "whatsapp",
            customerPhone: "+974-5555-1234",
            customerName: "John Doe",
            status: "open",
          }),
        }),
      );
    });

    it("should throw error for invalid platform", async () => {
      const data: any = {
        platform: "invalid-platform",
        messages: mockMessages,
      };

      await expect(
        service.createConversation(mockUserId, data),
      ).rejects.toThrow("Invalid platform: invalid-platform");
    });

    it("should throw error when no messages provided", async () => {
      const data: CreateConversationRequest = {
        platform: "whatsapp",
        messages: [],
      };

      await expect(
        service.createConversation(mockUserId, data),
      ).rejects.toThrow("At least one message is required");
    });

    it("should create conversation without optional fields", async () => {
      const mockCreate = jest
        .spyOn(prisma.conversationLog, "create")
        .mockResolvedValue({
          ...mockConversation,
          customerPhone: null,
          customerName: null,
        } as any);

      const data: CreateConversationRequest = {
        platform: "voice",
        messages: mockMessages,
      };

      const result = await service.createConversation(mockUserId, data);

      expect(result.customerPhone).toBeNull();
      expect(result.customerName).toBeNull();
    });
  });

  describe("appendMessages", () => {
    it("should add messages to existing conversation", async () => {
      const mockFindUnique = jest
        .spyOn(prisma.conversationLog, "findUnique")
        .mockResolvedValue(mockConversation as any);

      const newMessages: ConversationMessage[] = [
        {
          role: "user",
          content: "Thank you!",
          timestamp: new Date("2025-01-15T10:05:00Z"),
        },
      ];

      const updatedConversation = {
        ...mockConversation,
        messages: JSON.stringify([...mockMessages, ...newMessages]),
      };

      const mockUpdate = jest
        .spyOn(prisma.conversationLog, "update")
        .mockResolvedValue(updatedConversation as any);

      const result = await service.appendMessages(
        mockConversationId,
        mockUserId,
        newMessages,
      );

      expect(mockFindUnique).toHaveBeenCalledWith({
        where: { id: mockConversationId },
      });

      expect(mockUpdate).toHaveBeenCalledWith(
        expect.objectContaining({
          where: { id: mockConversationId },
          data: expect.objectContaining({
            messages: expect.any(String),
          }),
        }),
      );

      const resultMessages = JSON.parse(result.messages);
      expect(resultMessages).toHaveLength(3);
      expect(resultMessages[2].content).toBe("Thank you!");
    });

    it("should throw error for unauthorized access", async () => {
      const wrongUserId = "wrong-user-456";
      jest
        .spyOn(prisma.conversationLog, "findUnique")
        .mockResolvedValue(mockConversation as any);

      const newMessages: ConversationMessage[] = [
        {
          role: "user",
          content: "Hello",
          timestamp: new Date(),
        },
      ];

      await expect(
        service.appendMessages(mockConversationId, wrongUserId, newMessages),
      ).rejects.toThrow("Unauthorized: Conversation does not belong to user");
    });

    it("should throw error when conversation not found", async () => {
      jest.spyOn(prisma.conversationLog, "findUnique").mockResolvedValue(null);

      const newMessages: ConversationMessage[] = [
        {
          role: "user",
          content: "Hello",
          timestamp: new Date(),
        },
      ];

      await expect(
        service.appendMessages("non-existent-id", mockUserId, newMessages),
      ).rejects.toThrow("Conversation not found");
    });

    it("should throw error when no messages provided", async () => {
      jest
        .spyOn(prisma.conversationLog, "findUnique")
        .mockResolvedValue(mockConversation as any);

      await expect(
        service.appendMessages(mockConversationId, mockUserId, []),
      ).rejects.toThrow("At least one message is required");
    });
  });

  describe("getDailyStats", () => {
    it("should calculate accurate statistics", async () => {
      const testDate = new Date("2025-01-15");

      const conversation1 = {
        ...mockConversation,
        id: createId(),
        platform: "whatsapp",
        messages: JSON.stringify([
          { role: "user", content: "Hi", timestamp: new Date() },
          { role: "agent", content: "Hello", timestamp: new Date() },
        ]),
      };

      const conversation2 = {
        ...mockConversation,
        id: createId(),
        platform: "voice",
        messages: JSON.stringify([
          { role: "user", content: "Help", timestamp: new Date() },
          { role: "agent", content: "Sure", timestamp: new Date() },
          { role: "user", content: "Thanks", timestamp: new Date() },
        ]),
      };

      const conversation3 = {
        ...mockConversation,
        id: createId(),
        platform: "social",
        messages: JSON.stringify([
          { role: "user", content: "Question", timestamp: new Date() },
        ]),
      };

      jest
        .spyOn(prisma.conversationLog, "findMany")
        .mockResolvedValue([
          conversation1,
          conversation2,
          conversation3,
        ] as any);

      const stats = await service.getDailyStats(mockUserId, testDate);

      expect(stats.totalConversations).toBe(3);
      expect(stats.platforms.whatsapp).toBe(1);
      expect(stats.platforms.voice).toBe(1);
      expect(stats.platforms.social).toBe(1);
      expect(stats.messagesReceived).toBe(4); // user messages
      expect(stats.messagesSent).toBe(2); // agent messages
    });

    it("should identify top customers correctly", async () => {
      const testDate = new Date("2025-01-15");

      const conversations = [
        {
          ...mockConversation,
          id: createId(),
          customerPhone: "+974-1111-1111",
          customerName: "Customer A",
          messages: JSON.stringify([
            { role: "user", content: "Msg1", timestamp: new Date() },
            { role: "user", content: "Msg2", timestamp: new Date() },
            { role: "user", content: "Msg3", timestamp: new Date() },
          ]),
        },
        {
          ...mockConversation,
          id: createId(),
          customerPhone: "+974-2222-2222",
          customerName: "Customer B",
          messages: JSON.stringify([
            { role: "user", content: "Msg1", timestamp: new Date() },
            { role: "user", content: "Msg2", timestamp: new Date() },
          ]),
        },
        {
          ...mockConversation,
          id: createId(),
          customerPhone: "+974-1111-1111", // Same as first
          customerName: "Customer A",
          messages: JSON.stringify([
            { role: "user", content: "Msg4", timestamp: new Date() },
          ]),
        },
      ];

      jest
        .spyOn(prisma.conversationLog, "findMany")
        .mockResolvedValue(conversations as any);

      const stats = await service.getDailyStats(mockUserId, testDate);

      expect(stats.topCustomers).toHaveLength(2);
      expect(stats.topCustomers[0].phone).toBe("+974-1111-1111");
      expect(stats.topCustomers[0].messageCount).toBe(4); // 3 + 1
      expect(stats.topCustomers[1].phone).toBe("+974-2222-2222");
      expect(stats.topCustomers[1].messageCount).toBe(2);
    });

    it("should handle empty conversations", async () => {
      const testDate = new Date("2025-01-15");
      jest.spyOn(prisma.conversationLog, "findMany").mockResolvedValue([]);

      const stats = await service.getDailyStats(mockUserId, testDate);

      expect(stats.totalConversations).toBe(0);
      expect(stats.messagesReceived).toBe(0);
      expect(stats.messagesSent).toBe(0);
      expect(stats.topCustomers).toHaveLength(0);
    });
  });

  describe("updateConversationStatus", () => {
    it("should change conversation status", async () => {
      jest
        .spyOn(prisma.conversationLog, "findUnique")
        .mockResolvedValue(mockConversation as any);

      const updatedConversation = {
        ...mockConversation,
        status: "resolved",
      };

      const mockUpdate = jest
        .spyOn(prisma.conversationLog, "update")
        .mockResolvedValue(updatedConversation as any);

      const result = await service.updateConversationStatus(
        mockConversationId,
        mockUserId,
        "resolved",
      );

      expect(mockUpdate).toHaveBeenCalledWith(
        expect.objectContaining({
          where: { id: mockConversationId },
          data: expect.objectContaining({
            status: "resolved",
          }),
        }),
      );

      expect(result.status).toBe("resolved");
    });

    it("should throw error for unauthorized status update", async () => {
      const wrongUserId = "wrong-user-456";
      jest
        .spyOn(prisma.conversationLog, "findUnique")
        .mockResolvedValue(mockConversation as any);

      await expect(
        service.updateConversationStatus(
          mockConversationId,
          wrongUserId,
          "resolved",
        ),
      ).rejects.toThrow("Unauthorized: Conversation does not belong to user");
    });
  });

  describe("getUserConversations", () => {
    it("should return filtered results with pagination", async () => {
      const conversations = [
        { ...mockConversation, id: createId(), platform: "whatsapp" },
        { ...mockConversation, id: createId(), platform: "voice" },
        { ...mockConversation, id: createId(), platform: "whatsapp" },
      ];

      const mockFindMany = jest
        .spyOn(prisma.conversationLog, "findMany")
        .mockResolvedValue(conversations as any);

      const result = await service.getUserConversations(mockUserId, {
        platform: "whatsapp",
        limit: 10,
        offset: 0,
      });

      expect(mockFindMany).toHaveBeenCalledWith(
        expect.objectContaining({
          where: expect.objectContaining({
            userId: mockUserId,
            platform: "whatsapp",
          }),
          take: 10,
          skip: 0,
        }),
      );

      expect(result).toHaveLength(3);
    });

    it("should filter by status", async () => {
      const mockFindMany = jest
        .spyOn(prisma.conversationLog, "findMany")
        .mockResolvedValue([mockConversation] as any);

      await service.getUserConversations(mockUserId, {
        status: "open",
      });

      expect(mockFindMany).toHaveBeenCalledWith(
        expect.objectContaining({
          where: expect.objectContaining({
            userId: mockUserId,
            status: "open",
          }),
        }),
      );
    });

    it("should use default pagination values", async () => {
      const mockFindMany = jest
        .spyOn(prisma.conversationLog, "findMany")
        .mockResolvedValue([]);

      await service.getUserConversations(mockUserId, {});

      expect(mockFindMany).toHaveBeenCalledWith(
        expect.objectContaining({
          take: 50,
          skip: 0,
        }),
      );
    });
  });
});
