import { vi } from "vitest";
import {
  withErrorHandling,
  withValidation,
  validators,
  errorHandler,
  formatSuccessResponse,
  formatErrorResponse,
  toolWrapper,
} from "@/utils/toolWrapper.js";

describe("toolWrapper utilities", () => {
  describe("withErrorHandling", () => {
    it("should wrap function with error handling", async () => {
      const mockFn = vi.fn().mockResolvedValue("success");
      const wrappedFn = withErrorHandling("test_operation", mockFn);

      const result = await wrappedFn("arg1", "arg2");

      expect(mockFn).toHaveBeenCalledWith("arg1", "arg2");
      expect(result).toBe("success");
    });

    it("should handle errors with operation prefix", async () => {
      const mockFn = vi.fn().mockRejectedValue(new Error("Original error"));
      const wrappedFn = withErrorHandling("test_operation", mockFn);

      await expect(wrappedFn()).rejects.toThrow("test_operation: Original error");
    });

    it("should handle synchronous functions", async () => {
      const mockFn = vi.fn().mockReturnValue("sync result");
      const wrappedFn = withErrorHandling("test_operation", mockFn);

      const result = await wrappedFn();
      expect(result).toBe("sync result");
    });

    it("should handle non-Error objects", async () => {
      const mockFn = vi.fn().mockRejectedValue("string error");
      const wrappedFn = withErrorHandling("test_operation", mockFn);

      await expect(wrappedFn()).rejects.toThrow("test_operation: string error");
    });
  });

  describe("withValidation", () => {
    it("should validate parameters before execution", async () => {
      const mockValidator = vi.fn();
      const mockFn = vi.fn().mockResolvedValue("success");
      const wrappedFn = withValidation("test_operation", mockValidator, mockFn);

      const result = await wrappedFn("arg1", "arg2");

      expect(mockValidator).toHaveBeenCalledWith("arg1", "arg2");
      expect(mockFn).toHaveBeenCalledWith("arg1", "arg2");
      expect(result).toBe("success");
    });

    it("should handle validation errors", async () => {
      const mockValidator = vi.fn().mockImplementation(() => {
        throw new Error("Validation failed");
      });
      const mockFn = vi.fn();
      const wrappedFn = withValidation("test_operation", mockValidator, mockFn);

      await expect(wrappedFn()).rejects.toThrow("test_operation: Validation failed");
      expect(mockFn).not.toHaveBeenCalled();
    });

    it("should handle execution errors after validation", async () => {
      const mockValidator = vi.fn();
      const mockFn = vi.fn().mockRejectedValue(new Error("Execution failed"));
      const wrappedFn = withValidation("test_operation", mockValidator, mockFn);

      await expect(wrappedFn()).rejects.toThrow("test_operation: Execution failed");
    });
  });

  describe("validators", () => {
    describe("requireSite", () => {
      it("should pass when client is provided", () => {
        const mockClient = {};
        expect(() => validators.requireSite(mockClient, {})).not.toThrow();
      });

      it("should throw when client is missing", () => {
        expect(() => validators.requireSite(null, {})).toThrow("WordPress client is required");
        expect(() => validators.requireSite(undefined, {})).toThrow("WordPress client is required");
      });
    });

    describe("requireId", () => {
      it("should pass when id is provided", () => {
        expect(() => validators.requireId({ id: "123" })).not.toThrow();
        expect(() => validators.requireId({ id: 123 })).not.toThrow();
      });

      it("should throw when id is missing", () => {
        expect(() => validators.requireId({})).toThrow("ID parameter is required");
        expect(() => validators.requireId({ id: null })).toThrow("ID parameter is required");
        expect(() => validators.requireId({ id: undefined })).toThrow("ID parameter is required");
      });
    });

    describe("requireNonEmpty", () => {
      it("should pass when value is not empty", () => {
        expect(() => validators.requireNonEmpty("test", "field")).not.toThrow();
        expect(() => validators.requireNonEmpty(123, "field")).not.toThrow();
        expect(() => validators.requireNonEmpty([], "field")).not.toThrow();
      });

      it("should throw when value is empty", () => {
        expect(() => validators.requireNonEmpty("", "field")).toThrow("field cannot be empty");
        expect(() => validators.requireNonEmpty("   ", "field")).toThrow("field cannot be empty");
        expect(() => validators.requireNonEmpty(null, "field")).toThrow("field cannot be empty");
        expect(() => validators.requireNonEmpty(undefined, "field")).toThrow("field cannot be empty");
      });
    });

    describe("requireFields", () => {
      it("should pass when all fields are provided", () => {
        const params = { name: "test", age: 25, active: true };
        expect(() => validators.requireFields(params, ["name", "age"])).not.toThrow();
      });

      it("should throw when required field is missing", () => {
        const params = { name: "test" };
        expect(() => validators.requireFields(params, ["name", "age"])).toThrow("age is required");
      });

      it("should throw when required field is null or undefined", () => {
        const params = { name: "test", age: null };
        expect(() => validators.requireFields(params, ["name", "age"])).toThrow("age is required");
      });
    });
  });

  describe("errorHandler decorator", () => {
    it("should be available as a function", () => {
      expect(typeof errorHandler).toBe("function");
    });
  });

  describe("formatSuccessResponse", () => {
    it("should return content as-is", () => {
      const content = "test content";
      const result = formatSuccessResponse(content);
      expect(result).toBe(content);
    });

    it("should handle objects", () => {
      const content = { message: "success" };
      const result = formatSuccessResponse(content);
      expect(result).toBe(content);
    });
  });

  describe("formatErrorResponse", () => {
    it("should throw formatted error", () => {
      const error = new Error("Test error");
      expect(() => formatErrorResponse("test_operation", error)).toThrow("test_operation: Test error");
    });

    it("should handle non-Error objects", () => {
      expect(() => formatErrorResponse("test_operation", "string error")).toThrow("test_operation: string error");
    });
  });

  describe("toolWrapper", () => {
    it("should execute function successfully", async () => {
      const mockFn = vi.fn().mockResolvedValue("success");
      const result = await toolWrapper(mockFn);

      expect(mockFn).toHaveBeenCalled();
      expect(result).toBe("success");
    });

    it("should handle function errors", async () => {
      const mockFn = vi.fn().mockRejectedValue(new Error("Function error"));

      await expect(toolWrapper(mockFn)).rejects.toThrow("Function error");
    });

    it("should handle synchronous functions", async () => {
      const mockFn = vi.fn().mockReturnValue("sync result");
      const result = await toolWrapper(mockFn);

      expect(result).toBe("sync result");
    });

    it("should handle non-Error objects", async () => {
      const mockFn = vi.fn().mockRejectedValue("string error");

      await expect(toolWrapper(mockFn)).rejects.toThrow("string error");
    });
  });

  describe("integration scenarios", () => {
    it("should combine withErrorHandling and validators", async () => {
      const mockFn = vi.fn().mockResolvedValue("success");
      const wrappedFn = withErrorHandling("test_operation", mockFn);

      // Test with validator
      const validatedFn = withValidation("test_operation", validators.requireSite, wrappedFn);

      const result = await validatedFn({}, {});
      expect(result).toBe("success");
    });

    it("should handle complex error scenarios", async () => {
      const mockFn = vi.fn().mockImplementation((params) => {
        if (params.trigger === "validation") {
          throw new Error("Validation failed");
        } else if (params.trigger === "execution") {
          throw new Error("Execution failed");
        }
        return "success";
      });

      const wrappedFn = withErrorHandling("test_operation", mockFn);

      await expect(wrappedFn({ trigger: "validation" })).rejects.toThrow("test_operation: Validation failed");
      await expect(wrappedFn({ trigger: "execution" })).rejects.toThrow("test_operation: Execution failed");

      const result = await wrappedFn({ trigger: "success" });
      expect(result).toBe("success");
    });
  });
});
