import type { Mock } from "vitest";
import { LoqateAddressLookupService } from "./LoqateAddressLookupService";

describe("LoqateAddressLookupService", () => {
  let service: LoqateAddressLookupService;
  const mockApiKey = "test-api-key";
  const mockBaseUrl = "https://test-api.com";
  const mockCountries = ["GB", "USA"];

  // Helper functions for mock responses
  const createEmptyMockResponse = () => ({ items: [] });

  const createAddressDetailsMockResponse = (overrides = {}) => ({
    Items: [
      {
        Id: "address123",
        Type: "Address",
        City: "Test City",
        Line1: "Test Line 1",
        Line2: "Test Line 2",
        PostalCode: "12345",
        Label: "Test Label",
        ...overrides,
      },
    ],
  });

  const createErrorMockResponse = (overrides = {}) => ({
    Items: [
      {
        Error: "Test error code",
        Description: "Test error",
        Cause: "Test cause",
        Resolution: "Test resolution",
        ...overrides,
      },
    ],
  });

  // Helper function for creating service with filters
  const createServiceWithFilters = (filters: Record<string, string>) => {
    return new LoqateAddressLookupService({
      baseUrl: mockBaseUrl,
      apiKey: mockApiKey,
      countries: mockCountries,
      filters,
    });
  };

  // Helper function for mocking fetch responses
  const mockFetchResponse = (response: unknown) => {
    (global.fetch as Mock).mockResolvedValueOnce({
      json: () => Promise.resolve(response),
    });
  };

  beforeEach(() => {
    global.fetch = vi.fn();
    service = new LoqateAddressLookupService({
      baseUrl: mockBaseUrl,
      apiKey: mockApiKey,
      countries: mockCountries,
    });
  });

  afterEach(() => {
    vi.resetAllMocks();
  });

  it("uses default base URL and countries when none is provided", async () => {
    const serviceWithDefaultUrl = new LoqateAddressLookupService({
      apiKey: mockApiKey,
    });
    const mockResponse = createEmptyMockResponse();
    mockFetchResponse(mockResponse);

    await serviceWithDefaultUrl.search("test");

    expect(global.fetch).toHaveBeenCalledWith(
      "https://api.addressy.com/Capture/Interactive/Find/v1.00/json3.ws?Key=test-api-key&Text=test&Countries=GB",
    );
  });

  describe("find", () => {
    it("calls the correct URL with search term", async () => {
      const mockResponse = createEmptyMockResponse();
      mockFetchResponse(mockResponse);

      await service.search("123 Main Street");

      expect(global.fetch).toHaveBeenCalledWith(
        `${mockBaseUrl}/Find/v1.00/json3.ws?Key=${mockApiKey}&Text=123 Main Street&Countries=GB,USA`,
      );
    });
  });

  describe("findById", () => {
    it("calls the correct URL with container ID", async () => {
      const mockResponse = createEmptyMockResponse();
      mockFetchResponse(mockResponse);

      await service.findById("container123");

      expect(global.fetch).toHaveBeenCalledWith(
        `${mockBaseUrl}/Find/v1.00/json3.ws?Key=${mockApiKey}&Container=container123&Countries=GB,USA`,
      );
    });
  });

  describe("get", () => {
    it("calls the correct URL with ID", async () => {
      const mockResponse = createAddressDetailsMockResponse();
      mockFetchResponse(mockResponse);

      await service.get("address123");

      expect(global.fetch).toHaveBeenCalledWith(
        `${mockBaseUrl}/Retrieve/v1.2/json3.ws?Key=${mockApiKey}&Id=address123`,
      );
    });
  });

  describe("filters functionality", () => {
    it("includes no filters when filters are not provided", async () => {
      const mockResponse = createEmptyMockResponse();
      mockFetchResponse(mockResponse);

      await service.search("456 Oak Avenue");

      expect(global.fetch).toHaveBeenCalledWith(
        `${mockBaseUrl}/Find/v1.00/json3.ws?Key=${mockApiKey}&Text=456 Oak Avenue&Countries=GB,USA`,
      );
    });

    it("includes single filter in search URL", async () => {
      const serviceWithFilters = createServiceWithFilters({
        AdministrativeArea: "NY",
      });

      const mockResponse = createEmptyMockResponse();
      mockFetchResponse(mockResponse);

      await serviceWithFilters.search("789 Broadway");

      expect(global.fetch).toHaveBeenCalledWith(
        `${mockBaseUrl}/Find/v1.00/json3.ws?Key=${mockApiKey}&Text=789 Broadway&Countries=GB,USA&Filters=AdministrativeArea:NY`,
      );
    });

    it("includes multiple filters in search URL", async () => {
      const serviceWithFilters = createServiceWithFilters({
        AdministrativeArea: "CA",
        PostalCode: "90210",
      });

      const mockResponse = createEmptyMockResponse();
      mockFetchResponse(mockResponse);

      await serviceWithFilters.search("321 Sunset Boulevard");

      expect(global.fetch).toHaveBeenCalledWith(
        `${mockBaseUrl}/Find/v1.00/json3.ws?Key=${mockApiKey}&Text=321 Sunset Boulevard&Countries=GB,USA&Filters=AdministrativeArea:CA&PostalCode:90210`,
      );
    });

    it("encodes filter values with special characters", async () => {
      const serviceWithFilters = createServiceWithFilters({
        AdministrativeArea: "New York",
        CustomFilter: "100% Match & More",
      });

      const mockResponse = createEmptyMockResponse();
      mockFetchResponse(mockResponse);

      await serviceWithFilters.search("654 Fifth Avenue");

      expect(global.fetch).toHaveBeenCalledWith(
        `${mockBaseUrl}/Find/v1.00/json3.ws?Key=${mockApiKey}&Text=654 Fifth Avenue&Countries=GB,USA&Filters=AdministrativeArea:New%20York&CustomFilter:100%25%20Match%20%26%20More`,
      );
    });

    it("does not encode filter keys", async () => {
      const serviceWithFilters = createServiceWithFilters({
        AdministrativeArea: "NY",
      });

      const mockResponse = createEmptyMockResponse();
      mockFetchResponse(mockResponse);

      await serviceWithFilters.search("987 Park Avenue");

      const calledUrl = (global.fetch as Mock).mock.calls[0][0];
      expect(calledUrl).toContain("AdministrativeArea:NY");
      expect(calledUrl).not.toContain("AdministrativeArea%3ANY");
    });

    it("includes filters in findById URL", async () => {
      const serviceWithFilters = createServiceWithFilters({
        AdministrativeArea: "TX",
      });

      const mockResponse = createEmptyMockResponse();
      mockFetchResponse(mockResponse);

      await serviceWithFilters.findById("container123");

      expect(global.fetch).toHaveBeenCalledWith(
        `${mockBaseUrl}/Find/v1.00/json3.ws?Key=${mockApiKey}&Container=container123&Countries=GB,USA&Filters=AdministrativeArea:TX`,
      );
    });

    it("handles empty filters object", async () => {
      const serviceWithEmptyFilters = createServiceWithFilters({});

      const mockResponse = createEmptyMockResponse();
      mockFetchResponse(mockResponse);

      await serviceWithEmptyFilters.search("147 Wall Street");

      expect(global.fetch).toHaveBeenCalledWith(
        `${mockBaseUrl}/Find/v1.00/json3.ws?Key=${mockApiKey}&Text=147 Wall Street&Countries=GB,USA`,
      );
    });

    it("does not include filters in get URL (retrieve endpoint)", async () => {
      const serviceWithFilters = createServiceWithFilters({
        AdministrativeArea: "FL",
      });

      const mockResponse = createAddressDetailsMockResponse();
      mockFetchResponse(mockResponse);

      await serviceWithFilters.get("address123");

      expect(global.fetch).toHaveBeenCalledWith(
        `${mockBaseUrl}/Retrieve/v1.2/json3.ws?Key=${mockApiKey}&Id=address123`,
      );

      const calledUrl = (global.fetch as Mock).mock.calls[0][0];
      expect(calledUrl).not.toContain("Filters");
    });
  });

  describe("error handling", () => {
    it("propagates API errors", async () => {
      const errorResponse = createErrorMockResponse();
      mockFetchResponse(errorResponse);

      await expect(service.search("test")).rejects.toThrow();
    });

    it("propagates rejected promises", async () => {
      const networkError = new Error("Network error");
      (global.fetch as Mock).mockRejectedValueOnce(networkError);

      await expect(service.search("test")).rejects.toThrow(networkError);
    });
  });
});
