import { describe, it, expect, beforeEach, afterEach } from "vitest";
import { Test, TestingModule } from "@nestjs/testing";
import { KnowledgeCacheService } from "./cache.service";

// Integration tests - require running Valkey instance
// Skip in unit test runs, enable with: INTEGRATION_TESTS=true pnpm test
describe.skipIf(!process.env.INTEGRATION_TESTS)("KnowledgeCacheService", () => {
  let service: KnowledgeCacheService;

  beforeEach(async () => {
    // Set environment variables for testing
    process.env.KNOWLEDGE_CACHE_ENABLED = "true";
    process.env.KNOWLEDGE_CACHE_TTL = "300";
    process.env.VALKEY_URL = "redis://localhost:6379";

    const module: TestingModule = await Test.createTestingModule({
      providers: [KnowledgeCacheService],
    }).compile();

    service = module.get<KnowledgeCacheService>(KnowledgeCacheService);
  });

  afterEach(async () => {
    // Clean up
    if (service && service.isEnabled()) {
      await service.onModuleDestroy();
    }
  });

  describe("Cache Enabled/Disabled", () => {
    it("should be enabled by default", () => {
      expect(service.isEnabled()).toBe(true);
    });

    it("should be disabled when KNOWLEDGE_CACHE_ENABLED=false", async () => {
      process.env.KNOWLEDGE_CACHE_ENABLED = "false";
      const module = await Test.createTestingModule({
        providers: [KnowledgeCacheService],
      }).compile();
      const disabledService = module.get<KnowledgeCacheService>(KnowledgeCacheService);

      expect(disabledService.isEnabled()).toBe(false);
    });
  });

  describe("Entry Caching", () => {
    const workspaceId = "test-workspace-id";
    const slug = "test-entry";
    const entryData = {
      id: "entry-id",
      workspaceId,
      slug,
      title: "Test Entry",
      content: "Test content",
      tags: [],
    };

    it("should return null on cache miss", async () => {
      if (!service.isEnabled()) {
        return; // Skip if cache is disabled
      }

      await service.onModuleInit();
      const result = await service.getEntry(workspaceId, slug);
      expect(result).toBeNull();
    });

    it("should cache and retrieve entry data", async () => {
      if (!service.isEnabled()) {
        return;
      }

      await service.onModuleInit();

      // Set cache
      await service.setEntry(workspaceId, slug, entryData);

      // Get from cache
      const result = await service.getEntry(workspaceId, slug);
      expect(result).toEqual(entryData);
    });

    it("should invalidate entry cache", async () => {
      if (!service.isEnabled()) {
        return;
      }

      await service.onModuleInit();

      // Set cache
      await service.setEntry(workspaceId, slug, entryData);

      // Verify it's cached
      let result = await service.getEntry(workspaceId, slug);
      expect(result).toEqual(entryData);

      // Invalidate
      await service.invalidateEntry(workspaceId, slug);

      // Verify it's gone
      result = await service.getEntry(workspaceId, slug);
      expect(result).toBeNull();
    });
  });

  describe("Search Caching", () => {
    const workspaceId = "test-workspace-id";
    const query = "test search";
    const filters = { status: "PUBLISHED", page: 1, limit: 20 };
    const searchResults = {
      data: [],
      pagination: { page: 1, limit: 20, total: 0, totalPages: 0 },
      query,
    };

    it("should cache and retrieve search results", async () => {
      if (!service.isEnabled()) {
        return;
      }

      await service.onModuleInit();

      // Set cache
      await service.setSearch(workspaceId, query, filters, searchResults);

      // Get from cache
      const result = await service.getSearch(workspaceId, query, filters);
      expect(result).toEqual(searchResults);
    });

    it("should differentiate search results by filters", async () => {
      if (!service.isEnabled()) {
        return;
      }

      await service.onModuleInit();

      const filters1 = { page: 1, limit: 20 };
      const filters2 = { page: 2, limit: 20 };

      const results1 = { ...searchResults, pagination: { ...searchResults.pagination, page: 1 } };
      const results2 = { ...searchResults, pagination: { ...searchResults.pagination, page: 2 } };

      await service.setSearch(workspaceId, query, filters1, results1);
      await service.setSearch(workspaceId, query, filters2, results2);

      const result1 = await service.getSearch(workspaceId, query, filters1);
      const result2 = await service.getSearch(workspaceId, query, filters2);

      expect(result1.pagination.page).toBe(1);
      expect(result2.pagination.page).toBe(2);
    });

    it("should invalidate all search caches for workspace", async () => {
      if (!service.isEnabled()) {
        return;
      }

      await service.onModuleInit();

      // Set multiple search caches
      await service.setSearch(workspaceId, "query1", {}, searchResults);
      await service.setSearch(workspaceId, "query2", {}, searchResults);

      // Invalidate all
      await service.invalidateSearches(workspaceId);

      // Verify both are gone
      const result1 = await service.getSearch(workspaceId, "query1", {});
      const result2 = await service.getSearch(workspaceId, "query2", {});

      expect(result1).toBeNull();
      expect(result2).toBeNull();
    });
  });

  describe("Graph Caching", () => {
    const workspaceId = "test-workspace-id";
    const entryId = "entry-id";
    const maxDepth = 2;
    const graphData = {
      centerNode: { id: entryId, slug: "test", title: "Test", tags: [], depth: 0 },
      nodes: [],
      edges: [],
      stats: { totalNodes: 1, totalEdges: 0, maxDepth },
    };

    it("should cache and retrieve graph data", async () => {
      if (!service.isEnabled()) {
        return;
      }

      await service.onModuleInit();

      // Set cache
      await service.setGraph(workspaceId, entryId, maxDepth, graphData);

      // Get from cache
      const result = await service.getGraph(workspaceId, entryId, maxDepth);
      expect(result).toEqual(graphData);
    });

    it("should differentiate graphs by maxDepth", async () => {
      if (!service.isEnabled()) {
        return;
      }

      await service.onModuleInit();

      const graph1 = { ...graphData, stats: { ...graphData.stats, maxDepth: 1 } };
      const graph2 = { ...graphData, stats: { ...graphData.stats, maxDepth: 2 } };

      await service.setGraph(workspaceId, entryId, 1, graph1);
      await service.setGraph(workspaceId, entryId, 2, graph2);

      const result1 = await service.getGraph(workspaceId, entryId, 1);
      const result2 = await service.getGraph(workspaceId, entryId, 2);

      expect(result1.stats.maxDepth).toBe(1);
      expect(result2.stats.maxDepth).toBe(2);
    });

    it("should invalidate all graph caches for workspace", async () => {
      if (!service.isEnabled()) {
        return;
      }

      await service.onModuleInit();

      // Set cache
      await service.setGraph(workspaceId, entryId, maxDepth, graphData);

      // Invalidate
      await service.invalidateGraphs(workspaceId);

      // Verify it's gone
      const result = await service.getGraph(workspaceId, entryId, maxDepth);
      expect(result).toBeNull();
    });
  });

  describe("Cache Statistics", () => {
    it("should track hits and misses", async () => {
      if (!service.isEnabled()) {
        return;
      }

      await service.onModuleInit();

      const workspaceId = "test-workspace-id";
      const slug = "test-entry";
      const entryData = { id: "1", slug, title: "Test" };

      // Reset stats
      service.resetStats();

      // Miss
      await service.getEntry(workspaceId, slug);
      let stats = service.getStats();
      expect(stats.misses).toBe(1);
      expect(stats.hits).toBe(0);

      // Set
      await service.setEntry(workspaceId, slug, entryData);
      stats = service.getStats();
      expect(stats.sets).toBe(1);

      // Hit
      await service.getEntry(workspaceId, slug);
      stats = service.getStats();
      expect(stats.hits).toBe(1);
      expect(stats.hitRate).toBeCloseTo(0.5); // 1 hit, 1 miss = 50%
    });

    it("should reset statistics", async () => {
      if (!service.isEnabled()) {
        return;
      }

      await service.onModuleInit();

      const workspaceId = "test-workspace-id";
      const slug = "test-entry";

      await service.getEntry(workspaceId, slug); // miss

      service.resetStats();
      const stats = service.getStats();

      expect(stats.hits).toBe(0);
      expect(stats.misses).toBe(0);
      expect(stats.sets).toBe(0);
      expect(stats.deletes).toBe(0);
      expect(stats.hitRate).toBe(0);
    });
  });

  describe("Clear Workspace Cache", () => {
    it("should clear all caches for a workspace", async () => {
      if (!service.isEnabled()) {
        return;
      }

      await service.onModuleInit();

      const workspaceId = "test-workspace-id";

      // Set various caches
      await service.setEntry(workspaceId, "entry1", { id: "1" });
      await service.setSearch(workspaceId, "query", {}, { data: [] });
      await service.setGraph(workspaceId, "entry-id", 1, { nodes: [] });

      // Clear all
      await service.clearWorkspaceCache(workspaceId);

      // Verify all are gone
      const entry = await service.getEntry(workspaceId, "entry1");
      const search = await service.getSearch(workspaceId, "query", {});
      const graph = await service.getGraph(workspaceId, "entry-id", 1);

      expect(entry).toBeNull();
      expect(search).toBeNull();
      expect(graph).toBeNull();
    });
  });
});
