import { Client, AddressType } from '@googlemaps/google-maps-services-js';
import config from '@/shared/config/config';
import ApiError from '@/shared/utils/errors/ApiError';
import httpStatus from 'http-status';

const googleMapsClient = new Client({});

export interface IGoogleAddressComponent {
  long_name: string;
  short_name: string;
  types: AddressType[];
}

export interface IExtractedAddressComponents {
  country?: string;
  state?: string;
  city?: string;
  area?: string;
  postal_code?: string;
  coordinates?: {
    lat: number;
    lng: number;
  };
}

export interface ITextSearchResult {
  place_id?: string;
  formatted_address?: string;
  geometry?: {
    location: {
      lat: number;
      lng: number;
    };
  };
  name?: string;
  types?: AddressType[];
}

export const fetchPlaceDetails = async (placeId: string) => {
  try {
    const response = await googleMapsClient.placeDetails({
      params: {
        place_id: placeId,
        fields: [
          'address_components',
          'formatted_address',
          'geometry',
          'name',
          'place_id',
          'types',
        ],
        key: config.google.apiKey || process.env.GOOGLE_PLACES_API_KEY,
      },
    });

    if (response.data.status !== 'OK') {
      throw new ApiError(
        httpStatus.BAD_REQUEST,
        `Google Places API error: ${response.data.status} - ${response.data.error_message || 'Unknown error'}`,
      );
    }

    return response.data.result;
  } catch (error: any) {
    if (error instanceof ApiError) {
      throw error;
    }
    throw new ApiError(
      httpStatus.INTERNAL_SERVER_ERROR,
      `Failed to fetch place details: ${error.message}`,
    );
  }
};

export const searchAreaCoordinates = async (
  areaName: string,
  cityName: string,
  postalCode?: string,
): Promise<ITextSearchResult> => {
  try {
    const query = postalCode
      ? `${areaName} ${cityName} ${postalCode}`
      : `${areaName} ${cityName}`;

    const response = await googleMapsClient.textSearch({
      params: {
        query: query,
        key: config.google.apiKey || process.env.GOOGLE_PLACES_API_KEY,
      },
    });

    if (response.data.status !== 'OK') {
      throw new ApiError(
        httpStatus.BAD_REQUEST,
        `Google TextSearch API error: ${response.data.status} - ${response.data.error_message || 'Unknown error'}`,
      );
    }

    const areaResult = response.data.results.find(
      (result) =>
        result.types.includes(AddressType.sublocality) ||
        result.types.includes(AddressType.sublocality_level_1) ||
        result.types.includes(AddressType.sublocality_level_2) ||
        result.types.includes(AddressType.neighborhood),
    );

    if (!areaResult) {
      return response.data.results[0];
    }

    return areaResult;
  } catch (error: any) {
    if (error instanceof ApiError) {
      throw error;
    }
    throw new ApiError(
      httpStatus.INTERNAL_SERVER_ERROR,
      `Failed to search area coordinates: ${error.message}`,
    );
  }
};

const normalizeString = (str: string): string => {
  return str.toLowerCase().trim().replace(/[.,]/g, '').replace(/\s+/g, ' ');
};

const COMPONENT_TYPE_MAPPING = {
  country: [AddressType.country],
  state: [AddressType.administrative_area_level_1],
  city: [AddressType.locality],
  area: [
    AddressType.sublocality_level_1,
    AddressType.sublocality,
    AddressType.neighborhood,
  ],
  postal_code: [AddressType.postal_code],
};

export const extractAddressComponents = (
  placeDetails: any,
): IExtractedAddressComponents => {
  const components: IExtractedAddressComponents = {};

  if (!placeDetails.address_components) {
    return components;
  }

  if (placeDetails.geometry?.location) {
    components.coordinates = {
      lat: placeDetails.geometry.location.lat,
      lng: placeDetails.geometry.location.lng,
    };
  }

  placeDetails.address_components.forEach(
    (component: IGoogleAddressComponent) => {
      const types = component.types;

      for (const [ourType, googleTypes] of Object.entries(
        COMPONENT_TYPE_MAPPING,
      )) {
        if (googleTypes.some((googleType) => types.includes(googleType))) {
          (components as any)[ourType] = component.long_name;
          break;
        }
      }
    },
  );

  return components;
};

export const processPlaceId = async (placeId: string) => {
  const placeDetails = await fetchPlaceDetails(placeId);

  const extractedComponents = extractAddressComponents(placeDetails);

  const normalized: IExtractedAddressComponents = {};

  if (extractedComponents.country) {
    normalized.country = normalizeString(extractedComponents.country);
  }
  if (extractedComponents.state) {
    normalized.state = normalizeString(extractedComponents.state);
  }
  if (extractedComponents.city) {
    normalized.city = normalizeString(extractedComponents.city);
  }
  if (extractedComponents.area) {
    normalized.area = normalizeString(extractedComponents.area);
  }
  if (extractedComponents.postal_code) {
    normalized.postal_code = extractedComponents.postal_code;
  }

  normalized.coordinates = extractedComponents.coordinates;

  return {
    normalized,
    original: extractedComponents,
    placeDetails,
  };
};

export const getAreaCoordinates = async (
  areaName: string,
  cityName: string,
  postalCode?: string,
) => {
  if (!areaName || !cityName) {
    throw new ApiError(
      httpStatus.BAD_REQUEST,
      'Area name and city name are required for area coordinate lookup',
    );
  }

  const areaResult = await searchAreaCoordinates(
    areaName,
    cityName,
    postalCode,
  );

  return {
    coordinates: {
      lat: areaResult.geometry.location.lat,
      lng: areaResult.geometry.location.lng,
    },
    place_id: areaResult.place_id,
    formatted_address: areaResult.formatted_address,
  };
};
