import { describe, expect, it, vi, beforeEach } from "vitest";
import { BrainService } from "./brain.service";
import { PrismaService } from "../prisma/prisma.service";
import { TaskStatus, TaskPriority, ProjectStatus, EntityType } from "@prisma/client";

describe("BrainService", () => {
  let service: BrainService;
  let mockPrisma: {
    task: {
      findMany: ReturnType<typeof vi.fn>;
      count: ReturnType<typeof vi.fn>;
    };
    event: {
      findMany: ReturnType<typeof vi.fn>;
      count: ReturnType<typeof vi.fn>;
    };
    project: {
      findMany: ReturnType<typeof vi.fn>;
      count: ReturnType<typeof vi.fn>;
    };
    workspace: {
      findUniqueOrThrow: ReturnType<typeof vi.fn>;
    };
  };

  const mockWorkspaceId = "123e4567-e89b-12d3-a456-426614174000";

  const mockTasks = [
    {
      id: "task-1",
      title: "Test Task 1",
      description: "Description 1",
      status: TaskStatus.IN_PROGRESS,
      priority: TaskPriority.HIGH,
      dueDate: new Date("2025-02-01"),
      assignee: { id: "user-1", name: "John Doe", email: "john@example.com" },
      project: { id: "project-1", name: "Project 1", color: "#ff0000" },
    },
    {
      id: "task-2",
      title: "Test Task 2",
      description: null,
      status: TaskStatus.NOT_STARTED,
      priority: TaskPriority.MEDIUM,
      dueDate: null,
      assignee: null,
      project: null,
    },
  ];

  const mockEvents = [
    {
      id: "event-1",
      title: "Test Event 1",
      description: "Event description",
      startTime: new Date("2025-02-01T10:00:00Z"),
      endTime: new Date("2025-02-01T11:00:00Z"),
      allDay: false,
      location: "Conference Room A",
      project: { id: "project-1", name: "Project 1", color: "#ff0000" },
    },
  ];

  const mockProjects = [
    {
      id: "project-1",
      name: "Project 1",
      description: "Project description",
      status: ProjectStatus.ACTIVE,
      startDate: new Date("2025-01-01"),
      endDate: new Date("2025-06-30"),
      color: "#ff0000",
      _count: { tasks: 5, events: 3 },
    },
  ];

  beforeEach(() => {
    mockPrisma = {
      task: {
        findMany: vi.fn().mockResolvedValue(mockTasks),
        count: vi.fn().mockResolvedValue(10),
      },
      event: {
        findMany: vi.fn().mockResolvedValue(mockEvents),
        count: vi.fn().mockResolvedValue(5),
      },
      project: {
        findMany: vi.fn().mockResolvedValue(mockProjects),
        count: vi.fn().mockResolvedValue(3),
      },
      workspace: {
        findUniqueOrThrow: vi.fn().mockResolvedValue({
          id: mockWorkspaceId,
          name: "Test Workspace",
        }),
      },
    };

    service = new BrainService(mockPrisma as unknown as PrismaService);
  });

  describe("query", () => {
    it("should query all entity types by default", async () => {
      const result = await service.query({
        workspaceId: mockWorkspaceId,
      });

      expect(result.tasks).toHaveLength(2);
      expect(result.events).toHaveLength(1);
      expect(result.projects).toHaveLength(1);
      expect(result.meta.totalTasks).toBe(2);
      expect(result.meta.totalEvents).toBe(1);
      expect(result.meta.totalProjects).toBe(1);
    });

    it("should query only specified entity types", async () => {
      const result = await service.query({
        workspaceId: mockWorkspaceId,
        entities: [EntityType.TASK],
      });

      expect(result.tasks).toHaveLength(2);
      expect(result.events).toHaveLength(0);
      expect(result.projects).toHaveLength(0);
      expect(mockPrisma.task.findMany).toHaveBeenCalled();
      expect(mockPrisma.event.findMany).not.toHaveBeenCalled();
      expect(mockPrisma.project.findMany).not.toHaveBeenCalled();
    });

    it("should apply task filters", async () => {
      await service.query({
        workspaceId: mockWorkspaceId,
        tasks: {
          status: TaskStatus.IN_PROGRESS,
          priority: TaskPriority.HIGH,
        },
      });

      expect(mockPrisma.task.findMany).toHaveBeenCalledWith(
        expect.objectContaining({
          where: expect.objectContaining({
            workspaceId: mockWorkspaceId,
            status: TaskStatus.IN_PROGRESS,
            priority: TaskPriority.HIGH,
          }),
        })
      );
    });

    it("should apply task statuses filter (array)", async () => {
      await service.query({
        workspaceId: mockWorkspaceId,
        tasks: {
          statuses: [TaskStatus.NOT_STARTED, TaskStatus.IN_PROGRESS],
        },
      });

      expect(mockPrisma.task.findMany).toHaveBeenCalledWith(
        expect.objectContaining({
          where: expect.objectContaining({
            status: { in: [TaskStatus.NOT_STARTED, TaskStatus.IN_PROGRESS] },
          }),
        })
      );
    });

    it("should apply overdue filter", async () => {
      await service.query({
        workspaceId: mockWorkspaceId,
        tasks: {
          overdue: true,
        },
      });

      expect(mockPrisma.task.findMany).toHaveBeenCalledWith(
        expect.objectContaining({
          where: expect.objectContaining({
            dueDate: expect.objectContaining({ lt: expect.any(Date) }),
            status: { in: [TaskStatus.NOT_STARTED, TaskStatus.IN_PROGRESS] },
          }),
        })
      );
    });

    it("should apply unassigned filter", async () => {
      await service.query({
        workspaceId: mockWorkspaceId,
        tasks: {
          unassigned: true,
        },
      });

      expect(mockPrisma.task.findMany).toHaveBeenCalledWith(
        expect.objectContaining({
          where: expect.objectContaining({
            assigneeId: null,
          }),
        })
      );
    });

    it("should apply due date range filter", async () => {
      const dueDateFrom = new Date("2025-01-01");
      const dueDateTo = new Date("2025-01-31");

      await service.query({
        workspaceId: mockWorkspaceId,
        tasks: {
          dueDateFrom,
          dueDateTo,
        },
      });

      expect(mockPrisma.task.findMany).toHaveBeenCalledWith(
        expect.objectContaining({
          where: expect.objectContaining({
            dueDate: { gte: dueDateFrom, lte: dueDateTo },
          }),
        })
      );
    });

    it("should apply event filters", async () => {
      await service.query({
        workspaceId: mockWorkspaceId,
        events: {
          allDay: true,
          upcoming: true,
        },
      });

      expect(mockPrisma.event.findMany).toHaveBeenCalledWith(
        expect.objectContaining({
          where: expect.objectContaining({
            allDay: true,
            startTime: { gte: expect.any(Date) },
          }),
        })
      );
    });

    it("should apply event date range filter", async () => {
      const startFrom = new Date("2025-02-01");
      const startTo = new Date("2025-02-28");

      await service.query({
        workspaceId: mockWorkspaceId,
        events: {
          startFrom,
          startTo,
        },
      });

      expect(mockPrisma.event.findMany).toHaveBeenCalledWith(
        expect.objectContaining({
          where: expect.objectContaining({
            startTime: { gte: startFrom, lte: startTo },
          }),
        })
      );
    });

    it("should apply project filters", async () => {
      await service.query({
        workspaceId: mockWorkspaceId,
        projects: {
          status: ProjectStatus.ACTIVE,
        },
      });

      expect(mockPrisma.project.findMany).toHaveBeenCalledWith(
        expect.objectContaining({
          where: expect.objectContaining({
            status: ProjectStatus.ACTIVE,
          }),
        })
      );
    });

    it("should apply project statuses filter (array)", async () => {
      await service.query({
        workspaceId: mockWorkspaceId,
        projects: {
          statuses: [ProjectStatus.PLANNING, ProjectStatus.ACTIVE],
        },
      });

      expect(mockPrisma.project.findMany).toHaveBeenCalledWith(
        expect.objectContaining({
          where: expect.objectContaining({
            status: { in: [ProjectStatus.PLANNING, ProjectStatus.ACTIVE] },
          }),
        })
      );
    });

    it("should apply search term across tasks", async () => {
      await service.query({
        workspaceId: mockWorkspaceId,
        search: "test",
        entities: [EntityType.TASK],
      });

      expect(mockPrisma.task.findMany).toHaveBeenCalledWith(
        expect.objectContaining({
          where: expect.objectContaining({
            OR: [
              { title: { contains: "test", mode: "insensitive" } },
              { description: { contains: "test", mode: "insensitive" } },
            ],
          }),
        })
      );
    });

    it("should apply search term across events", async () => {
      await service.query({
        workspaceId: mockWorkspaceId,
        search: "conference",
        entities: [EntityType.EVENT],
      });

      expect(mockPrisma.event.findMany).toHaveBeenCalledWith(
        expect.objectContaining({
          where: expect.objectContaining({
            OR: [
              { title: { contains: "conference", mode: "insensitive" } },
              { description: { contains: "conference", mode: "insensitive" } },
              { location: { contains: "conference", mode: "insensitive" } },
            ],
          }),
        })
      );
    });

    it("should apply search term across projects", async () => {
      await service.query({
        workspaceId: mockWorkspaceId,
        search: "project",
        entities: [EntityType.PROJECT],
      });

      expect(mockPrisma.project.findMany).toHaveBeenCalledWith(
        expect.objectContaining({
          where: expect.objectContaining({
            OR: [
              { name: { contains: "project", mode: "insensitive" } },
              { description: { contains: "project", mode: "insensitive" } },
            ],
          }),
        })
      );
    });

    it("should respect limit parameter", async () => {
      await service.query({
        workspaceId: mockWorkspaceId,
        limit: 5,
      });

      expect(mockPrisma.task.findMany).toHaveBeenCalledWith(
        expect.objectContaining({
          take: 5,
        })
      );
    });

    it("should include query and filters in meta", async () => {
      const result = await service.query({
        workspaceId: mockWorkspaceId,
        query: "What tasks are due?",
        tasks: { status: TaskStatus.IN_PROGRESS },
      });

      expect(result.meta.query).toBe("What tasks are due?");
      expect(result.meta.filters.tasks).toEqual({ status: TaskStatus.IN_PROGRESS });
    });
  });

  describe("getContext", () => {
    it("should return context with summary", async () => {
      const result = await service.getContext({
        workspaceId: mockWorkspaceId,
      });

      expect(result.timestamp).toBeInstanceOf(Date);
      expect(result.workspace.id).toBe(mockWorkspaceId);
      expect(result.workspace.name).toBe("Test Workspace");
      expect(result.summary).toEqual({
        activeTasks: 10,
        overdueTasks: 10,
        upcomingEvents: 5,
        activeProjects: 3,
      });
    });

    it("should include tasks when requested", async () => {
      const result = await service.getContext({
        workspaceId: mockWorkspaceId,
        includeTasks: true,
      });

      expect(result.tasks).toBeDefined();
      expect(result.tasks).toHaveLength(2);
      expect(result.tasks![0].isOverdue).toBeDefined();
    });

    it("should include events when requested", async () => {
      const result = await service.getContext({
        workspaceId: mockWorkspaceId,
        includeEvents: true,
      });

      expect(result.events).toBeDefined();
      expect(result.events).toHaveLength(1);
    });

    it("should include projects when requested", async () => {
      const result = await service.getContext({
        workspaceId: mockWorkspaceId,
        includeProjects: true,
      });

      expect(result.projects).toBeDefined();
      expect(result.projects).toHaveLength(1);
      expect(result.projects![0].taskCount).toBeDefined();
    });

    it("should use custom eventDays", async () => {
      await service.getContext({
        workspaceId: mockWorkspaceId,
        eventDays: 14,
      });

      expect(mockPrisma.event.count).toHaveBeenCalled();
      expect(mockPrisma.event.findMany).toHaveBeenCalled();
    });

    it("should not include tasks when explicitly disabled", async () => {
      const result = await service.getContext({
        workspaceId: mockWorkspaceId,
        includeTasks: false,
        includeEvents: true,
        includeProjects: true,
      });

      expect(result.tasks).toBeUndefined();
      expect(result.events).toBeDefined();
      expect(result.projects).toBeDefined();
    });

    it("should not include events when explicitly disabled", async () => {
      const result = await service.getContext({
        workspaceId: mockWorkspaceId,
        includeTasks: true,
        includeEvents: false,
        includeProjects: true,
      });

      expect(result.tasks).toBeDefined();
      expect(result.events).toBeUndefined();
      expect(result.projects).toBeDefined();
    });

    it("should not include projects when explicitly disabled", async () => {
      const result = await service.getContext({
        workspaceId: mockWorkspaceId,
        includeTasks: true,
        includeEvents: true,
        includeProjects: false,
      });

      expect(result.tasks).toBeDefined();
      expect(result.events).toBeDefined();
      expect(result.projects).toBeUndefined();
    });
  });

  describe("search", () => {
    it("should search across all entities", async () => {
      const result = await service.search(mockWorkspaceId, "test");

      expect(result.tasks).toHaveLength(2);
      expect(result.events).toHaveLength(1);
      expect(result.projects).toHaveLength(1);
      expect(result.meta.query).toBe("test");
    });

    it("should respect limit parameter", async () => {
      await service.search(mockWorkspaceId, "test", 5);

      expect(mockPrisma.task.findMany).toHaveBeenCalledWith(
        expect.objectContaining({
          take: 5,
        })
      );
    });

    it("should handle empty search term", async () => {
      const result = await service.search(mockWorkspaceId, "");

      expect(result.tasks).toBeDefined();
      expect(result.events).toBeDefined();
      expect(result.projects).toBeDefined();
    });
  });
});
