import { captureException } from '@sentry/react'
import {
  FluidSlugType,
  FluidState,
  FluidType,
  KonnectorUpdate,
  Season,
} from 'enums'
import get from 'lodash/get'
import { DateTime, Interval } from 'luxon'
import { FluidStatus, Relation } from 'models'
import challengeData from '../db/challengeEntity.json'

/** Array of elec, water & gas */
export const allFluids = [FluidType.ELECTRICITY, FluidType.WATER, FluidType.GAS]

export function getKonnectorSlug(
  fluidType: Exclude<FluidType, FluidType.MULTIFLUID>
) {
  switch (fluidType) {
    case FluidType.ELECTRICITY:
      return FluidSlugType.ELECTRICITY
    case FluidType.WATER:
      return FluidSlugType.WATER
    case FluidType.GAS:
      return FluidSlugType.GAS
    default:
      throw new Error('unknown fluidtype')
  }
}
/**
 * Return lowercase fluidtype
 * @example FluidType.ELECTRICITY => 'electricity'
 */
export function getFluidName(fluidType: FluidType) {
  return FluidType[fluidType].toLowerCase()
}

export const getFluidTypeTranslation = (
  fluidType: Exclude<FluidType, FluidType.MULTIFLUID>
) => {
  switch (fluidType) {
    case FluidType.GAS:
      return 'de gaz'
    case FluidType.ELECTRICITY:
      return "d'électricité"
    case FluidType.WATER:
      return "d'eau"
    default:
      throw new Error('unexpected fluidtype')
  }
}

export const getPartnerKey = (fluidType: FluidType) => {
  switch (fluidType) {
    case FluidType.ELECTRICITY:
      return 'enedis'
    case FluidType.WATER:
      return 'egl'
    case FluidType.GAS:
      return 'grdf'
    default:
      throw new Error('unknown fluidtype')
  }
}

export function getKonnectorUpdateError(type: string) {
  switch (type.toUpperCase()) {
    case 'USER_ACTION_NEEDED.OAUTH_OUTDATED':
    case 'USER_ACTION_NEEDED.SCA_REQUIRED':
      return KonnectorUpdate.ERROR_UPDATE_OAUTH
    case 'LOGIN_FAILED':
      return KonnectorUpdate.LOGIN_FAILED
    default:
      return KonnectorUpdate.ERROR_UPDATE
  }
}
export function isKonnectorActive(
  fluidStatus: FluidStatus[],
  fluidType: FluidType
): boolean {
  if (fluidType === FluidType.MULTIFLUID) {
    if (
      fluidStatus.filter(
        fluid =>
          fluid.status === FluidState.NOT_CONNECTED ||
          fluid.status === FluidState.KONNECTOR_NOT_FOUND
      ).length === 3
    ) {
      return false
    } else {
      return true
    }
  }
  if (
    fluidStatus[fluidType].status === FluidState.NOT_CONNECTED ||
    fluidStatus[fluidType].status === FluidState.KONNECTOR_NOT_FOUND
  ) {
    return false
  } else return true
}
export function formatNumberValues(
  value: number | null,
  fluidStyle?: string,
  toBeCompared = false
) {
  if (value || value === 0) {
    const localeValue = value.toLocaleString('fr-FR', {
      minimumFractionDigits: 2,
      maximumFractionDigits: 2,
    })
    const noSpaceValue = parseInt(localeValue.replace(/\s/g, ''))
    if (toBeCompared) return noSpaceValue
    if (fluidStyle && noSpaceValue >= 1000) {
      const convertedValue = (noSpaceValue / 1000).toFixed(2).replace('.', ',')
      return convertedValue
    } else return localeValue
  } else {
    return '--,--'
  }
}

/**
 * Get one relation in doc
 * @param {object} doc - DocumentEntity
 * @param {string} relName - Name of the relation
 */
export function getRelationship<D>(doc: D, relName: string): Relation {
  return get(doc, `relationships.${relName}.data`, [])
}

/**
 * Get array of items in one relation in doc
 * @param {object} doc - DocumentEntity
 * @param {string} relName - Name of the relation
 */
export function getRelationshipHasMany<D>(doc: D, relName: string): Relation[] {
  return get(doc, `relationships.${relName}.data`, [])
}

/**
 * Import a svg file with format : id.svg
 */
export const importIconById = async (
  id: string,
  pathType: string
): Promise<string | undefined> => {
  let importedChallengeIcon
  try {
    importedChallengeIcon = await import(
      /* webpackMode: "eager" */ `assets/icons/visu/${pathType}/${id}.svg`
    )
    if (importedChallengeIcon) {
      return importedChallengeIcon.default
    }
  } catch (e) {
    console.error(`Could not import icon ${pathType}/${id}`)
    captureException(e)
  }
}

export const getMonthFullName = (month: number) => {
  const monthNames = [
    'Janvier',
    'Février',
    'Mars',
    'Avril',
    'Mai',
    'Juin',
    'Juillet',
    'Août',
    'Septembre',
    'Octobre',
    'Novembre',
    'Décembre',
  ] as const
  if (month < 1 || month > 12) throw new Error('Invalid month')
  return monthNames[month - 1]
}

/**
 * Return month string according to month index
 * @variation Equivalent to date.monthLong
 * @param date - DateTime
 * @returns month in french
 */
export const getMonthName = (date: DateTime) => {
  const monthNames = [
    'janvier',
    'février',
    'mars',
    'avril',
    'mai',
    'juin',
    'juillet',
    'août',
    'septembre',
    'octobre',
    'novembre',
    'décembre',
  ] as const
  return monthNames[date.month - 1]
}

/**
 * Return month string according to month index
 * @param date - DateTime
 * @returns "de" month in french
 */
export const getMonthNameWithPrep = (date: DateTime) => {
  const monthNames = [
    'de janvier',
    'de février',
    'de mars',
    `d’avril`,
    'de mai',
    'de juin',
    'de juillet',
    `d’août`,
    'de septembre',
    `d’octobre`,
    'de novembre',
    'de décembre',
  ] as const
  return monthNames[date.month - 1]
}

/**
 * Return season according to following rules
 * - Winter is : 1/11/XXXX => 1/3/XXXX
 * - Summer is : 1/6/XXXX => 1/9/XXXX
 * @returns Season
 */
export const getSeason = (): Season => {
  const currentDate: DateTime = DateTime.local().setZone('utc', {
    keepLocalTime: true,
  })
  const currentYear: number = currentDate.year
  const winterStart: DateTime = DateTime.local(currentYear, 11, 1).setZone(
    'utc',
    {
      keepLocalTime: true,
    }
  )
  const winterEnd: DateTime = DateTime.local(currentYear + 1, 3, 1).setZone(
    'utc',
    {
      keepLocalTime: true,
    }
  )

  const summerStart: DateTime = DateTime.local(currentYear, 6, 1).setZone(
    'utc',
    {
      keepLocalTime: true,
    }
  )
  const summerEnd: DateTime = DateTime.local(currentYear, 9, 1).setZone('utc', {
    keepLocalTime: true,
  })
  const summerInterval: Interval = Interval.fromDateTimes(
    summerStart,
    summerEnd
  )
  const winterInterval: Interval = Interval.fromDateTimes(
    winterStart,
    winterEnd
  )

  if (summerInterval.contains(currentDate)) {
    return Season.SUMMER
  } else if (winterInterval.contains(currentDate)) {
    return Season.WINTER
  } else {
    return Season.NONE
  }
}

/**
 *  Returns the challenge title with line return ( \n ). The result is coming from challengeEntity.json
 * @param userChallengeId EXPLORATION001
 * @returns Simone\nVEILLE
 */
export const getChallengeTitleWithLineReturn = (userChallengeId: string) => {
  for (const challenge of challengeData) {
    if (challenge._id === userChallengeId) {
      return challenge.title_line_return
    }
  }
}

/**
 * Returns today's date, example: 2022-09-28T00:00:00.000Z
 * @returns DateTime
 */
export const getTodayDate = () =>
  DateTime.local()
    .setZone('utc', {
      keepLocalTime: true,
    })
    .startOf('day')

/**
 * Formats an array of strings into a list with commas and an "et" (and) before the last element.
 * @param {string[]} array - The array of strings to be formatted.
 * @returns {string} The formatted list string.
 *
 * If the array is empty, an empty string is returned.
 * If the array has only one element, that element is returned as is.
 * If the array has two elements, they are joined with " et " (and).
 * If the array has more than two elements, all but the last element are joined with commas,
 * and " et " (and) is placed before the last element.
 * @example
 * // Returns "pomme, banane et cerise"
 * formatListWithAnd(['pomme', 'banane', 'cerise']);
 */
export const formatListWithAnd = (array: string[]) => {
  if (array.length === 0) {
    return ''
  } else if (array.length === 1) {
    return array[0]
  } else if (array.length === 2) {
    return array.join(' et ')
  } else {
    const lastElement = array.pop()
    return array.join(', ') + ' et ' + lastElement
  }
}

export type OffPeakHours = {
  start: { hour: number; minute: number }
  end: { hour: number; minute: number }
}

/**
 * Check if a string is a valid off-peak hour format
 * @example
 * isValidOffPeakHours("6H15-14H15") => true
 * isValidOffPeakHours("68H78_12Hab") => false
 */
export const isValidOffPeakHours = (range: string) => {
  const offPeakHoursRegex =
    /^(0?\d|1\d|2[0-3])H[0-5]?\d-(0?\d|1\d|2[0-3])H[0-5]?\d$/
  return offPeakHoursRegex.test(range)
}

/**
 * Parse the string representation of off-peak hours from Enedis to an array of time ranges object
 */
export const parseOffPeakHours = (timeString: string): OffPeakHours[] => {
  const timeRanges = timeString.split(';')
  if (!timeRanges.every(range => isValidOffPeakHours(range))) {
    console.error(`Error parsing time range "${timeString}"`)
    return []
  }

  const intervals: OffPeakHours[] = []

  for (const range of timeRanges) {
    const [startStr, endStr] = range.split('-')

    const startTime = DateTime.fromFormat(startStr, "H'H'mm")
    const endTime = DateTime.fromFormat(endStr, "H'H'mm")

    intervals.push({
      start: { hour: startTime.hour, minute: startTime.minute },
      end: { hour: endTime.hour, minute: endTime.minute },
    })
  }

  return intervals
}

/**
 * Format a number into a 2-digits string, padded with 0
 * @example formatTwoDigits(5) returns "05"
 */
export const formatTwoDigits = (num: number): string => {
  return num.toString().padStart(2, '0')
}

/**
 * Format off-peak hours object into a human-readable string
 * @example formatOffPeakHours({ start: { hour: 2, minute: 0 }, end: { hour: 10, minute: 0 }}) returns "02H00-10H00"
 */
export const formatOffPeakHours = (offPeakHours: OffPeakHours): string => {
  const { start, end } = offPeakHours
  const startTime = `${formatTwoDigits(start.hour)}H${formatTwoDigits(
    start.minute
  )}`
  const endTime = `${formatTwoDigits(end.hour)}H${formatTwoDigits(end.minute)}`

  return `${startTime}-${endTime}`
}

/**
 * Split off-peak hours that cross midnight
 * @example The range "22H00-6H00" becomes "22H00-23H59" and "0H00-6H00"
 */
export const splitOffPeakHours = (
  offPeakHours: OffPeakHours[]
): OffPeakHours[] => {
  return offPeakHours.reduce((acc: OffPeakHours[], offPeakHour) => {
    if (offPeakHour.start.hour > offPeakHour.end.hour) {
      acc.push({
        start: {
          hour: offPeakHour.start.hour,
          minute: offPeakHour.start.minute,
        },
        end: {
          hour: 23,
          minute: 59,
        },
      })
      acc.push({
        start: {
          hour: 0,
          minute: 0,
        },
        end: {
          hour: offPeakHour.end.hour,
          minute: offPeakHour.end.minute,
        },
      })
    } else {
      acc.push(offPeakHour)
    }
    return acc
  }, [])
}

export const roundToNearestHalfHour = (
  hour: number,
  minute: number,
  isEnd: boolean
): { hour: number; minute: number } => {
  let roundedMinute = Math.round(minute / 30) * 30 // Round to the nearest half-hour
  let roundedHour = hour

  // If rounding to the next hour (except for midnight), adjust the hour and reset the minute
  if (roundedMinute === 60 && roundedHour !== 23) {
    roundedHour += 1
    roundedMinute = 0
  }

  // Don't round to midnight for the off-peak hours end, instead round to 23:59
  if (
    (roundedMinute === 60 && roundedHour === 23) ||
    (roundedMinute === 0 && roundedHour === 0)
  ) {
    if (isEnd) {
      roundedHour = 23
      roundedMinute = 59
    } else {
      roundedHour = 0
      roundedMinute = 0
    }
  }

  return { hour: roundedHour, minute: roundedMinute }
}

/**
 * Round off-peak hours to the nearest half-hour
 * @example "6H50-14H50" becomes "7H00-15H00"
 */
export const roundOffPeakHours = (
  offPeakHours: OffPeakHours[]
): OffPeakHours[] => {
  return offPeakHours.map(({ start, end }) => ({
    start: roundToNearestHalfHour(start.hour, start.minute, false),
    end: roundToNearestHalfHour(end.hour, end.minute, true),
  }))
}