import { LoqateAddressLookupError } from "./LoqateAddressLookupError";
import type {
  LoqateAddressDetailsItem,
  LoqateAddressDetailsResponse,
  LoqateSearchResponse,
} from "./types";

const LOQATE_BASE_URL = "https://api.addressy.com/Capture/Interactive";
const LOQATE_FIND_URL = "/Find/v1.00/json3.ws";
const LOQATE_RETRIEVE_URL = "/Retrieve/v1.2/json3.ws";
const DEFAULT_COUNTRIES = ["GB"];

export class LoqateAddressLookupService {
  /**
   * Base URL for the Loqate API
   */
  #baseUrl: string;

  /**
   * API key for the Loqate API
   */
  #apiKey?: string;

  /**
   * List of allowed country codes for the Loqate API
   * 2 or 3 character ISO country codes
   */
  #countries?: string[];

  /**
   * Optional filters for the Loqate API
   * E.g., { AdministrativeArea: "CA", PostalCode: "90210" }
   */
  #filters?: Record<string, string>;

  constructor({
    baseUrl,
    apiKey,
    countries,
    filters,
  }: {
    baseUrl?: string;
    apiKey?: string;
    countries?: string[];
    filters?: Record<string, string>;
  }) {
    this.#apiKey = apiKey;
    this.#baseUrl = baseUrl || LOQATE_BASE_URL;
    this.#countries = countries || DEFAULT_COUNTRIES;
    this.#filters = filters;
  }

  private fetchFromApi<TResponse = unknown>(url: string): Promise<TResponse> {
    return fetch(`${this.#baseUrl}${url}`)
      .then(response => response.json())
      .then(json => {
        if (json.Items?.some((item: any) => item.Error)) {
          throw new LoqateAddressLookupError(json);
        }
        return json;
      });
  }

  /**
   * Builds the Filters query parameter for Loqate API requests.
   * - Filter keys (e.g., "AdministrativeArea", "PostalCode") are predefined by Loqate API (no need to encode)
   * - Filter values (e.g., "New York", "90210") contain user input that may have special characters (need encoding)
   *
   * @returns Empty string if no filters, otherwise "&Filters=key1:value1&key2:value2" (Loqate's expected format for Filters)
   */
  private buildFiltersQuery(): string {
    if (!this.#filters || Object.keys(this.#filters).length === 0) {
      return "";
    }

    // Transform Record<string, string> to Loqate's "key:value&key:value" format
    const encodedFilters = Object.entries(this.#filters)
      .map(([key, value]) => `${key}:${encodeURIComponent(value)}`)
      .join("&");

    return `&Filters=${encodedFilters}`;
  }

  search(searchTerm: string): Promise<LoqateSearchResponse> {
    let url = `${LOQATE_FIND_URL}?Key=${this.#apiKey}&Text=${searchTerm}&Countries=${this.#countries?.join(",")}`;
    url += this.buildFiltersQuery();
    return this.fetchFromApi<LoqateSearchResponse>(url);
  }

  findById(id: string): Promise<LoqateSearchResponse> {
    let url = `${LOQATE_FIND_URL}?Key=${this.#apiKey}&Container=${id}&Countries=${this.#countries?.join(",")}`;
    url += this.buildFiltersQuery();
    return this.fetchFromApi<LoqateSearchResponse>(url);
  }

  async get(id: string): Promise<LoqateAddressDetailsItem> {
    const url = `${LOQATE_RETRIEVE_URL}?Key=${this.#apiKey}&Id=${id}`;
    const response = await this.fetchFromApi<LoqateAddressDetailsResponse>(url);
    return response.Items[0];
  }
}
