import { describe, test, expect } from "vitest";
import { Context7 } from "./client";
import { Context7Error } from "@error";

describe("Context7 Client", () => {
  const apiKey = process.env.CONTEXT7_API_KEY || process.env.API_KEY!;

  describe("constructor", () => {
    test("should create client with API key", () => {
      const client = new Context7({ apiKey });
      expect(client).toBeDefined();
    });

    test("should create client from environment variables", () => {
      const client = new Context7();
      expect(client).toBeDefined();
    });

    test("should throw error when API key is missing", () => {
      const originalEnv = process.env.CONTEXT7_API_KEY;
      const originalApiKey = process.env.API_KEY;

      delete process.env.CONTEXT7_API_KEY;
      delete process.env.API_KEY;

      expect(() => new Context7({ apiKey: "" })).toThrow(Context7Error);
      expect(() => new Context7({})).toThrow(Context7Error);
      expect(() => new Context7()).toThrow("API key is required");

      if (originalEnv) process.env.CONTEXT7_API_KEY = originalEnv;
      if (originalApiKey) process.env.API_KEY = originalApiKey;
    });

    test("should prefer config API key over environment variable", () => {
      const customApiKey = "ctx7sk-custom-key";
      const client = new Context7({ apiKey: customApiKey });
      expect(client).toBeDefined();
    });
  });

  describe("searchLibrary", () => {
    const client = new Context7({ apiKey });

    test("should search for libraries and return array directly", async () => {
      const result = await client.searchLibrary("I need to build a UI", "react");

      expect(result).toBeDefined();
      expect(Array.isArray(result)).toBe(true);
      expect(result.length).toBeGreaterThan(0);
    });

    test("should return Library objects with all fields", async () => {
      const result = await client.searchLibrary("I want to use TypeScript", "typescript");

      expect(result.length).toBeGreaterThan(0);
      const library = result[0];

      expect(library).toHaveProperty("id");
      expect(library).toHaveProperty("name");
      expect(library).toHaveProperty("description");
      expect(library).toHaveProperty("totalSnippets");
      expect(library).toHaveProperty("trustScore");
      expect(library).toHaveProperty("benchmarkScore");
    });

    test("should search with different queries", async () => {
      const queries = ["vue", "express", "next"];

      for (const query of queries) {
        const result = await client.searchLibrary(`I want to use ${query}`, query);
        expect(result.length).toBeGreaterThan(0);
      }
    }, 15000);
  });

  describe("getContext - JSON format (default)", () => {
    const client = new Context7({ apiKey });

    test("should get context as Documentation array (default)", async () => {
      const result = await client.getContext("How to use hooks", "/facebook/react");

      expect(result).toBeDefined();
      expect(Array.isArray(result)).toBe(true);
      expect(result.length).toBeGreaterThan(0);
    });

    test("should get context with explicit json type", async () => {
      const result = await client.getContext("How to use hooks", "/facebook/react", {
        type: "json",
      });

      expect(result).toBeDefined();
      expect(Array.isArray(result)).toBe(true);
      expect(result.length).toBeGreaterThan(0);
    });

    test("should have correct Documentation structure", async () => {
      const result = await client.getContext("How to use hooks", "/facebook/react", {
        type: "json",
      });

      expect(result.length).toBeGreaterThan(0);
      const doc = result[0];
      expect(doc).toHaveProperty("title");
      expect(doc).toHaveProperty("content");
      expect(doc).toHaveProperty("source");
      expect(typeof doc.title).toBe("string");
      expect(typeof doc.content).toBe("string");
      expect(typeof doc.source).toBe("string");
    });
  });

  describe("getContext - text format", () => {
    const client = new Context7({ apiKey });

    test("should get context as text string with type: txt", async () => {
      const result = await client.getContext("How to use hooks", "/facebook/react", {
        type: "txt",
      });

      expect(result).toBeDefined();
      expect(typeof result).toBe("string");
      expect(result.length).toBeGreaterThan(0);
    });
  });

  describe("getContext - different libraries", () => {
    const client = new Context7({ apiKey });

    test("should get context for Vue", async () => {
      const result = await client.getContext("How to create components", "/vuejs/core");

      expect(result).toBeDefined();
      expect(Array.isArray(result)).toBe(true);
      expect(result.length).toBeGreaterThan(0);
    });

    test("should get context for Express", async () => {
      const result = await client.getContext("How to create routes", "/expressjs/express");

      expect(result).toBeDefined();
      expect(Array.isArray(result)).toBe(true);
      expect(result.length).toBeGreaterThan(0);
    });
  });

  describe("error handling", () => {
    const client = new Context7({ apiKey });

    test("should handle invalid library ID gracefully", async () => {
      await expect(client.getContext("test query", "/nonexistent/library")).rejects.toThrow();
    });

    test("should handle invalid search query", async () => {
      await expect(client.searchLibrary("", "")).rejects.toThrow(Context7Error);
    });
  });

  describe("type inference", () => {
    const client = new Context7({ apiKey });

    test("should infer Documentation[] for default (json) format", async () => {
      const result = await client.getContext("How to use hooks", "/facebook/react");

      expect(Array.isArray(result)).toBe(true);
      expect(result[0]).toHaveProperty("title");
      expect(result[0]).toHaveProperty("content");
      expect(result[0]).toHaveProperty("source");
    });

    test("should infer string type for txt format", async () => {
      const result = await client.getContext("How to use hooks", "/facebook/react", {
        type: "txt",
      });

      expect(typeof result).toBe("string");
    });
  });
});
