import { describe, it, expect, beforeEach, vi } from "vitest";
import { Test, TestingModule } from "@nestjs/testing";
import { NotFoundException } from "@nestjs/common";
import { GraphService } from "./graph.service";
import { PrismaService } from "../../prisma/prisma.service";
import { KnowledgeCacheService } from "./cache.service";

describe("GraphService", () => {
  let service: GraphService;
  let prisma: PrismaService;

  const mockEntry = {
    id: "entry-1",
    workspaceId: "workspace-1",
    slug: "test-entry",
    title: "Test Entry",
    content: "Test content",
    contentHtml: "<p>Test content</p>",
    summary: "Test summary",
    status: "PUBLISHED",
    visibility: "WORKSPACE",
    createdAt: new Date(),
    updatedAt: new Date(),
    createdBy: "user-1",
    updatedBy: "user-1",
    tags: [],
    outgoingLinks: [],
    incomingLinks: [],
  };

  const mockPrismaService = {
    knowledgeEntry: {
      findUnique: vi.fn(),
    },
  };

  const mockCacheService = {
    isEnabled: vi.fn().mockReturnValue(false),
    getEntry: vi.fn().mockResolvedValue(null),
    setEntry: vi.fn(),
    invalidateEntry: vi.fn(),
    getGraph: vi.fn().mockResolvedValue(null),
    setGraph: vi.fn(),
    invalidateGraph: vi.fn(),
  };

  beforeEach(async () => {
    const module: TestingModule = await Test.createTestingModule({
      providers: [
        GraphService,
        {
          provide: PrismaService,
          useValue: mockPrismaService,
        },
        {
          provide: KnowledgeCacheService,
          useValue: mockCacheService,
        },
      ],
    }).compile();

    service = module.get<GraphService>(GraphService);
    prisma = module.get<PrismaService>(PrismaService);

    vi.clearAllMocks();
  });

  it("should be defined", () => {
    expect(service).toBeDefined();
  });

  describe("getEntryGraph", () => {
    it("should throw NotFoundException if entry does not exist", async () => {
      mockPrismaService.knowledgeEntry.findUnique.mockResolvedValue(null);

      await expect(service.getEntryGraph("workspace-1", "non-existent", 1)).rejects.toThrow(
        NotFoundException
      );
    });

    it("should throw NotFoundException if entry belongs to different workspace", async () => {
      mockPrismaService.knowledgeEntry.findUnique.mockResolvedValue({
        ...mockEntry,
        workspaceId: "different-workspace",
      });

      await expect(service.getEntryGraph("workspace-1", "entry-1", 1)).rejects.toThrow(
        NotFoundException
      );
    });

    it("should return graph with center node when depth is 0", async () => {
      mockPrismaService.knowledgeEntry.findUnique.mockResolvedValue(mockEntry);

      const result = await service.getEntryGraph("workspace-1", "entry-1", 0);

      expect(result.centerNode.id).toBe("entry-1");
      expect(result.nodes).toHaveLength(1);
      expect(result.edges).toHaveLength(0);
      expect(result.stats.totalNodes).toBe(1);
      expect(result.stats.totalEdges).toBe(0);
    });

    it("should build graph with connected nodes at depth 1", async () => {
      const linkedEntry = {
        id: "entry-2",
        workspaceId: "workspace-1",
        slug: "linked-entry",
        title: "Linked Entry",
        content: "Linked content",
        contentHtml: "<p>Linked content</p>",
        summary: null,
        status: "PUBLISHED",
        visibility: "WORKSPACE",
        createdAt: new Date(),
        updatedAt: new Date(),
        createdBy: "user-1",
        updatedBy: "user-1",
        tags: [],
        outgoingLinks: [],
        incomingLinks: [],
      };

      mockPrismaService.knowledgeEntry.findUnique
        // First call: initial validation (with tags only)
        .mockResolvedValueOnce(mockEntry)
        // Second call: BFS for center entry (with tags and links)
        .mockResolvedValueOnce({
          ...mockEntry,
          outgoingLinks: [
            {
              id: "link-1",
              sourceId: "entry-1",
              targetId: "entry-2",
              linkText: "link to entry 2",
              resolved: true,
              target: linkedEntry,
            },
          ],
          incomingLinks: [],
        })
        // Third call: BFS for linked entry
        .mockResolvedValueOnce(linkedEntry);

      const result = await service.getEntryGraph("workspace-1", "entry-1", 1);

      expect(result.nodes).toHaveLength(2);
      expect(result.edges).toHaveLength(1);
      expect(result.stats.totalNodes).toBe(2);
      expect(result.stats.totalEdges).toBe(1);
    });
  });
});
