import { describe, expect, it, vi, beforeEach } from "vitest";
import { validate } from "class-validator";
import { plainToInstance } from "class-transformer";
import { BadRequestException } from "@nestjs/common";
import { BrainSearchDto, BrainQueryDto } from "./dto";
import { BrainService } from "./brain.service";
import { PrismaService } from "../prisma/prisma.service";

describe("Brain Search Validation", () => {
  describe("BrainSearchDto", () => {
    it("should accept a valid search query", async () => {
      const dto = plainToInstance(BrainSearchDto, { q: "meeting notes", limit: 10 });
      const errors = await validate(dto);
      expect(errors).toHaveLength(0);
    });

    it("should accept empty query params", async () => {
      const dto = plainToInstance(BrainSearchDto, {});
      const errors = await validate(dto);
      expect(errors).toHaveLength(0);
    });

    it("should reject search query exceeding 500 characters", async () => {
      const longQuery = "a".repeat(501);
      const dto = plainToInstance(BrainSearchDto, { q: longQuery });
      const errors = await validate(dto);
      expect(errors.length).toBeGreaterThan(0);
      const qError = errors.find((e) => e.property === "q");
      expect(qError).toBeDefined();
      expect(qError?.constraints?.maxLength).toContain("500");
    });

    it("should accept search query at exactly 500 characters", async () => {
      const maxQuery = "a".repeat(500);
      const dto = plainToInstance(BrainSearchDto, { q: maxQuery });
      const errors = await validate(dto);
      expect(errors).toHaveLength(0);
    });

    it("should reject negative limit", async () => {
      const dto = plainToInstance(BrainSearchDto, { q: "test", limit: -1 });
      const errors = await validate(dto);
      expect(errors.length).toBeGreaterThan(0);
      const limitError = errors.find((e) => e.property === "limit");
      expect(limitError).toBeDefined();
      expect(limitError?.constraints?.min).toContain("1");
    });

    it("should reject zero limit", async () => {
      const dto = plainToInstance(BrainSearchDto, { q: "test", limit: 0 });
      const errors = await validate(dto);
      expect(errors.length).toBeGreaterThan(0);
      const limitError = errors.find((e) => e.property === "limit");
      expect(limitError).toBeDefined();
    });

    it("should reject limit exceeding 100", async () => {
      const dto = plainToInstance(BrainSearchDto, { q: "test", limit: 101 });
      const errors = await validate(dto);
      expect(errors.length).toBeGreaterThan(0);
      const limitError = errors.find((e) => e.property === "limit");
      expect(limitError).toBeDefined();
      expect(limitError?.constraints?.max).toContain("100");
    });

    it("should accept limit at boundaries (1 and 100)", async () => {
      const dto1 = plainToInstance(BrainSearchDto, { limit: 1 });
      const errors1 = await validate(dto1);
      expect(errors1).toHaveLength(0);

      const dto100 = plainToInstance(BrainSearchDto, { limit: 100 });
      const errors100 = await validate(dto100);
      expect(errors100).toHaveLength(0);
    });

    it("should reject non-integer limit", async () => {
      const dto = plainToInstance(BrainSearchDto, { limit: 10.5 });
      const errors = await validate(dto);
      expect(errors.length).toBeGreaterThan(0);
      const limitError = errors.find((e) => e.property === "limit");
      expect(limitError).toBeDefined();
    });
  });

  describe("BrainQueryDto search and query length validation", () => {
    it("should reject query exceeding 500 characters", async () => {
      const longQuery = "a".repeat(501);
      const dto = plainToInstance(BrainQueryDto, {
        workspaceId: "550e8400-e29b-41d4-a716-446655440000",
        query: longQuery,
      });
      const errors = await validate(dto);
      expect(errors.length).toBeGreaterThan(0);
      const queryError = errors.find((e) => e.property === "query");
      expect(queryError).toBeDefined();
      expect(queryError?.constraints?.maxLength).toContain("500");
    });

    it("should reject search exceeding 500 characters", async () => {
      const longSearch = "b".repeat(501);
      const dto = plainToInstance(BrainQueryDto, {
        workspaceId: "550e8400-e29b-41d4-a716-446655440000",
        search: longSearch,
      });
      const errors = await validate(dto);
      expect(errors.length).toBeGreaterThan(0);
      const searchError = errors.find((e) => e.property === "search");
      expect(searchError).toBeDefined();
      expect(searchError?.constraints?.maxLength).toContain("500");
    });

    it("should accept query at exactly 500 characters", async () => {
      const maxQuery = "a".repeat(500);
      const dto = plainToInstance(BrainQueryDto, {
        workspaceId: "550e8400-e29b-41d4-a716-446655440000",
        query: maxQuery,
      });
      const errors = await validate(dto);
      expect(errors).toHaveLength(0);
    });

    it("should accept search at exactly 500 characters", async () => {
      const maxSearch = "b".repeat(500);
      const dto = plainToInstance(BrainQueryDto, {
        workspaceId: "550e8400-e29b-41d4-a716-446655440000",
        search: maxSearch,
      });
      const errors = await validate(dto);
      expect(errors).toHaveLength(0);
    });
  });

  describe("BrainService.search defensive validation", () => {
    let service: BrainService;
    let prisma: {
      task: { findMany: ReturnType<typeof vi.fn> };
      event: { findMany: ReturnType<typeof vi.fn> };
      project: { findMany: ReturnType<typeof vi.fn> };
    };

    beforeEach(() => {
      prisma = {
        task: { findMany: vi.fn().mockResolvedValue([]) },
        event: { findMany: vi.fn().mockResolvedValue([]) },
        project: { findMany: vi.fn().mockResolvedValue([]) },
      };
      service = new BrainService(prisma as unknown as PrismaService);
    });

    it("should throw BadRequestException for search term exceeding 500 characters", async () => {
      const longTerm = "x".repeat(501);
      await expect(service.search("workspace-id", longTerm)).rejects.toThrow(BadRequestException);
      await expect(service.search("workspace-id", longTerm)).rejects.toThrow("500");
    });

    it("should accept search term at exactly 500 characters", async () => {
      const maxTerm = "x".repeat(500);
      await expect(service.search("workspace-id", maxTerm)).resolves.toBeDefined();
    });

    it("should clamp limit to max 100 when higher value provided", async () => {
      await service.search("workspace-id", "test", 200);
      expect(prisma.task.findMany).toHaveBeenCalledWith(expect.objectContaining({ take: 100 }));
    });

    it("should clamp limit to min 1 when negative value provided", async () => {
      await service.search("workspace-id", "test", -5);
      expect(prisma.task.findMany).toHaveBeenCalledWith(expect.objectContaining({ take: 1 }));
    });

    it("should clamp limit to min 1 when zero provided", async () => {
      await service.search("workspace-id", "test", 0);
      expect(prisma.task.findMany).toHaveBeenCalledWith(expect.objectContaining({ take: 1 }));
    });

    it("should pass through valid limit values unchanged", async () => {
      await service.search("workspace-id", "test", 50);
      expect(prisma.task.findMany).toHaveBeenCalledWith(expect.objectContaining({ take: 50 }));
    });
  });

  describe("BrainService.query defensive validation", () => {
    let service: BrainService;
    let prisma: {
      task: { findMany: ReturnType<typeof vi.fn> };
      event: { findMany: ReturnType<typeof vi.fn> };
      project: { findMany: ReturnType<typeof vi.fn> };
    };

    beforeEach(() => {
      prisma = {
        task: { findMany: vi.fn().mockResolvedValue([]) },
        event: { findMany: vi.fn().mockResolvedValue([]) },
        project: { findMany: vi.fn().mockResolvedValue([]) },
      };
      service = new BrainService(prisma as unknown as PrismaService);
    });

    it("should throw BadRequestException for search field exceeding 500 characters", async () => {
      const longSearch = "y".repeat(501);
      await expect(
        service.query({ workspaceId: "workspace-id", search: longSearch })
      ).rejects.toThrow(BadRequestException);
    });

    it("should throw BadRequestException for query field exceeding 500 characters", async () => {
      const longQuery = "z".repeat(501);
      await expect(
        service.query({ workspaceId: "workspace-id", query: longQuery })
      ).rejects.toThrow(BadRequestException);
    });

    it("should clamp limit to max 100 in query method", async () => {
      await service.query({ workspaceId: "workspace-id", limit: 200 });
      expect(prisma.task.findMany).toHaveBeenCalledWith(expect.objectContaining({ take: 100 }));
    });

    it("should clamp limit to min 1 in query method when negative", async () => {
      await service.query({ workspaceId: "workspace-id", limit: -10 });
      expect(prisma.task.findMany).toHaveBeenCalledWith(expect.objectContaining({ take: 1 }));
    });

    it("should accept valid query and search within limits", async () => {
      await expect(
        service.query({
          workspaceId: "workspace-id",
          query: "test query",
          search: "test search",
          limit: 50,
        })
      ).resolves.toBeDefined();
    });
  });
});
