import {type LocationGeocodedAddress} from 'expo-location'

import {IS_ANDROID} from '#/env'
import {logger} from '#/geolocation/logger'
import {type Geolocation} from '#/geolocation/types'
import {device} from '#/storage'

/**
 * Maps full US region names to their short codes.
 *
 * Context: in some cases, like on Android, we get the full region name instead
 * of the short code. We may need to expand this in the future to other
 * countries, hence the prefix.
 */
export const USRegionNameToRegionCode: {
  [regionName: string]: string
} = {
  Alabama: 'AL',
  Alaska: 'AK',
  Arizona: 'AZ',
  Arkansas: 'AR',
  California: 'CA',
  Colorado: 'CO',
  Connecticut: 'CT',
  Delaware: 'DE',
  Florida: 'FL',
  Georgia: 'GA',
  Hawaii: 'HI',
  Idaho: 'ID',
  Illinois: 'IL',
  Indiana: 'IN',
  Iowa: 'IA',
  Kansas: 'KS',
  Kentucky: 'KY',
  Louisiana: 'LA',
  Maine: 'ME',
  Maryland: 'MD',
  Massachusetts: 'MA',
  Michigan: 'MI',
  Minnesota: 'MN',
  Mississippi: 'MS',
  Missouri: 'MO',
  Montana: 'MT',
  Nebraska: 'NE',
  Nevada: 'NV',
  ['New Hampshire']: 'NH',
  ['New Jersey']: 'NJ',
  ['New Mexico']: 'NM',
  ['New York']: 'NY',
  ['North Carolina']: 'NC',
  ['North Dakota']: 'ND',
  Ohio: 'OH',
  Oklahoma: 'OK',
  Oregon: 'OR',
  Pennsylvania: 'PA',
  ['Rhode Island']: 'RI',
  ['South Carolina']: 'SC',
  ['South Dakota']: 'SD',
  Tennessee: 'TN',
  Texas: 'TX',
  Utah: 'UT',
  Vermont: 'VT',
  Virginia: 'VA',
  Washington: 'WA',
  ['West Virginia']: 'WV',
  Wisconsin: 'WI',
  Wyoming: 'WY',
}

/**
 * Normalizes a `LocationGeocodedAddress` into a `Geolocation`.
 *
 * We don't want or care about the full location data, so we trim it down and
 * normalize certain fields, like region, into the format we need.
 */
export function normalizeDeviceLocation(
  location: LocationGeocodedAddress,
): Geolocation {
  let {isoCountryCode, region} = location
  let regionCode: string | undefined = region ?? undefined

  /*
   * Android doesn't give us ISO 3166-2 short codes. We need these for US
   */
  if (IS_ANDROID) {
    if (region && isoCountryCode === 'US') {
      /*
       * We need short codes for US states. If we can't remap it, just drop it
       * entirely for now.
       */
      regionCode = USRegionNameToRegionCode[region] ?? undefined
    } else {
      /*
       * Outside the US, we don't need regionCodes for now, so just drop it.
       */
      regionCode = undefined
    }
  }

  return {
    countryCode: isoCountryCode ?? undefined,
    regionCode,
  }
}

/**
 * Combines precise location data with the geolocation config fetched from the
 * IP service, with preference to the precise data.
 */
export function mergeGeolocations(
  device?: Geolocation,
  geolocationService?: Geolocation,
): Geolocation {
  let geolocation: Geolocation = {
    countryCode: geolocationService?.countryCode ?? undefined,
    regionCode: geolocationService?.regionCode ?? undefined,
  }
  // prefer GPS
  if (device?.countryCode) {
    geolocation = device
  }
  logger.debug('merged geolocation data', {
    device,
    service: geolocationService,
    merged: geolocation,
  })
  return geolocation
}

/**
 * Gets the IP-based geolocation as a string in the format of
 * "countryCode-regionCode", or just "countryCode" if regionCode is not
 * available.
 *
 * IMPORTANT: this method should only return IP-based data, not the user's GPS
 * based data. IP-based data we can already infer from requests, but for
 * consistency between frontend and backend, we sometimes want to share the
 * value we have on the frontend with the backend.
 */
export function getIPGeolocationString() {
  const geo = device.get(['geolocationServiceResponse'])
  if (!geo) return
  const {countryCode, regionCode} = geo
  if (countryCode) {
    if (regionCode) {
      return `${countryCode}-${regionCode}`
    } else {
      return countryCode
    }
  }
}
