import {
  FluidSlugType,
  FluidState,
  FluidType,
  KonnectorUpdate,
  Season,
} from 'enums'
import { DateTime } from 'luxon'
import { FluidStatus } from 'models'
import {
  formatListWithAnd,
  formatNumberValues,
  formatOffPeakHours,
  formatTwoDigits,
  getChallengeTitleWithLineReturn,
  getFluidLabel,
  getFluidName,
  getFluidTypeTranslation,
  getFluidUnit,
  getKonnectorSlug,
  getKonnectorUpdateError,
  getMonthFullName,
  getMonthName,
  getMonthNameWithPrep,
  getPartnerKey,
  getSeason,
  isKonnectorActive,
  isValidOffPeakHours,
  parseOffPeakHours,
  roundOffPeakHours,
  splitOffPeakHours,
} from './utils'

describe('utils test', () => {
  describe('getKonnectorSlug', () => {
    it('should return correct slug for elec', () => {
      const slug = getKonnectorSlug(FluidType.ELECTRICITY)
      expect(slug).toBe(FluidSlugType.ELECTRICITY)
    })
    it('should return correct slug for water', () => {
      const slug = getKonnectorSlug(FluidType.WATER)
      expect(slug).toBe(FluidSlugType.WATER)
    })
    it('should return correct slug for gas', () => {
      const slug = getKonnectorSlug(FluidType.GAS)
      expect(slug).toBe(FluidSlugType.GAS)
    })
    it('should throw error for invalid fluid type', () => {
      expect(() => getKonnectorSlug(99 as FluidType.GAS)).toThrow(
        'unknown fluidtype'
      )
    })
  })
  describe('getKonnectorUpdateError', () => {
    it('should return KonnectorUpdate.ERROR_UPDATE_OAUTH for USER_ACTION_NEEDED.OAUTH_OUTDATED', () => {
      const result = getKonnectorUpdateError(
        'USER_ACTION_NEEDED.OAUTH_OUTDATED'
      )
      expect(result).toBe(KonnectorUpdate.ERROR_UPDATE_OAUTH)
    })
    it('should return KonnectorUpdate.LOGIN_FAILED for LOGIN_FAILED', () => {
      const result = getKonnectorUpdateError('LOGIN_FAILED')
      expect(result).toBe(KonnectorUpdate.LOGIN_FAILED)
    })
    it('should return KonnectorUpdate.ERROR_UPDATE for CHALLENGE_ASKED', () => {
      const result = getKonnectorUpdateError('CHALLENGE_ASKED')
      expect(result).toBe(KonnectorUpdate.ERROR_UPDATE)
    })
    it('should return KonnectorUpdate.ERROR_UPDATE for an unknown type', () => {
      const result = getKonnectorUpdateError('UNKNOWN_TYPE')
      expect(result).toBe(KonnectorUpdate.ERROR_UPDATE)
    })
  })

  describe('isKonnectorActive', () => {
    describe('for MULTIFLUID', () => {
      it('should return false when all fluids are not connected', () => {
        const fluidStatus = [
          { status: FluidState.NOT_CONNECTED },
          { status: FluidState.KONNECTOR_NOT_FOUND },
          { status: FluidState.NOT_CONNECTED },
        ] as FluidStatus[]
        const result = isKonnectorActive(fluidStatus, FluidType.MULTIFLUID)
        expect(result).toBe(false)
      })
      it('should return true when some fluids are connected', () => {
        const fluidStatus = [
          { status: FluidState.DONE },
          { status: FluidState.NOT_CONNECTED },
          { status: FluidState.DONE },
        ] as FluidStatus[]
        const result = isKonnectorActive(fluidStatus, FluidType.MULTIFLUID)
        expect(result).toBe(true)
      })
    })

    describe('for SINGLE fluids', () => {
      it('should return false for a single fluid not connected', () => {
        const fluidStatus = [
          {},
          {
            status: FluidState.NOT_CONNECTED,
            fluidType: FluidType.GAS,
          },
          {
            status: FluidState.KONNECTOR_NOT_FOUND,
            fluidType: FluidType.WATER,
          },
        ] as FluidStatus[]
        expect(isKonnectorActive(fluidStatus, FluidType.GAS)).toBe(false)
        expect(isKonnectorActive(fluidStatus, FluidType.WATER)).toBe(false)
      })
      it('should return true for a single fluid connected', () => {
        const fluidStatus = [
          {
            status: FluidState.DONE,
            fluidType: FluidType.ELECTRICITY,
          },
          { status: FluidState.ERROR, fluidType: FluidType.WATER },
          { status: FluidState.LOGIN_FAILED, fluidType: FluidType.GAS },
        ] as FluidStatus[]
        expect(isKonnectorActive(fluidStatus, FluidType.ELECTRICITY)).toBe(true)
        expect(isKonnectorActive(fluidStatus, FluidType.GAS)).toBe(true)
        expect(isKonnectorActive(fluidStatus, FluidType.WATER)).toBe(true)
      })
    })
  })

  describe('formatNumberValues test', () => {
    it('should return --,-- if there is not value', () => {
      const result = formatNumberValues(null)
      expect(result).toBe('--,--')
    })

    it('should return a value at english number format two digits decimal', () => {
      const result = formatNumberValues(2000.555)
      expect(result).toEqual('2 000,56')
    })

    it('should return a float value', () => {
      const result = formatNumberValues(2000.55, '', true)
      expect(result).toEqual(2000)
    })
  })

  describe('getSeason test', () => {
    it('should return summer', () => {
      const now = DateTime.local().setZone('utc', {
        keepLocalTime: true,
      })
      jest
        .spyOn(DateTime, 'local')
        .mockReturnValueOnce(now.set({ day: 12, month: 6, year: 2021 }))
      const result = getSeason()
      expect(result).toBe(Season.SUMMER)
    })

    it('should return winter', () => {
      const now = DateTime.local().setZone('utc', {
        keepLocalTime: true,
      })
      jest
        .spyOn(DateTime, 'local')
        .mockReturnValueOnce(now.set({ day: 1, month: 12, year: 2021 }))
      const result = getSeason()
      expect(result).toBe(Season.WINTER)
    })

    it('should return none', () => {
      const now = DateTime.local().setZone('utc', {
        keepLocalTime: true,
      })
      jest
        .spyOn(DateTime, 'local')
        .mockReturnValueOnce(now.set({ day: 1, month: 10, year: 2021 }))
      const result = getSeason()
      expect(result).toBe(Season.NONE)
    })
  })

  describe('getChallengeTitleWithLineReturn test', () => {
    it('should return Simone\\nVEILLE', () => {
      expect(getChallengeTitleWithLineReturn('CHALLENGE0001')).toBe(
        'Simone\nVEILLE'
      )
    })
    it('should return undefined', () => {
      expect(getChallengeTitleWithLineReturn('CHALLENGE0000')).toBe(undefined)
    })
  })

  describe('getMonthFullName', () => {
    it('should return the name of the month', () => {
      expect(getMonthFullName(3)).toBe('Mars')
    })
  })

  describe('getMonthName', () => {
    it('should return the name of the month', () => {
      expect(getMonthName(DateTime.local(2023, 6, 1))).toBe('juin')
    })
  })
  describe('getMonthNameWithPrep', () => {
    it('should return the name of the month with " de "', () => {
      const date = DateTime.fromISO('2020-11-29T23:59:59.999Z')
      expect(getMonthNameWithPrep(date)).toBe('de novembre')
    })

    it('should return the name of the month with " d\'"', () => {
      const date = DateTime.fromISO('2020-10-29T23:59:59.999Z')
      expect(getMonthNameWithPrep(date)).toBe('d’octobre')
    })
  })

  describe('getFluidName', () => {
    it('should return electricity', () => {
      expect(getFluidName(FluidType.ELECTRICITY)).toBe('electricity')
    })
    it('should return water', () => {
      expect(getFluidName(FluidType.WATER)).toBe('water')
    })
    it('should return gas', () => {
      expect(getFluidName(FluidType.GAS)).toBe('gas')
    })
    it('should return multifluid', () => {
      expect(getFluidName(FluidType.MULTIFLUID)).toBe('multifluid')
    })
  })

  describe('getFluidTypeTranslation', () => {
    it('should return electricity', () => {
      expect(getFluidTypeTranslation(FluidType.ELECTRICITY)).toBe(
        "d'électricité"
      )
    })
    it('should return water', () => {
      expect(getFluidTypeTranslation(FluidType.WATER)).toBe("d'eau")
    })
    it('should return gas', () => {
      expect(getFluidTypeTranslation(FluidType.GAS)).toBe('de gaz')
    })
    it('should throw error for invalid fluid type', () => {
      expect(() => getFluidTypeTranslation(99 as FluidType.GAS)).toThrow(
        'unexpected fluidtype'
      )
    })
  })

  describe('getPartnerKey', () => {
    it('should return enedis', () => {
      expect(getPartnerKey(FluidType.ELECTRICITY)).toBe('enedis')
    })
    it('should return egl', () => {
      expect(getPartnerKey(FluidType.WATER)).toBe('egl')
    })
    it('should return grdf', () => {
      expect(getPartnerKey(FluidType.GAS)).toBe('grdf')
    })
    it('should throw error for invalid fluid type', () => {
      expect(() => getPartnerKey(99 as FluidType.GAS)).toThrow(
        'unknown fluidtype'
      )
    })
  })

  describe('getFluidLabel', () => {
    it('should return elec', () => {
      expect(getFluidLabel(FluidType.ELECTRICITY)).toBe('elec')
    })
    it('should return gas', () => {
      expect(getFluidLabel(FluidType.GAS)).toBe('gaz')
    })
    it('should return water', () => {
      expect(getFluidLabel(FluidType.WATER)).toBe('water')
    })
    it('should return multi', () => {
      expect(getFluidLabel(FluidType.MULTIFLUID)).toBe('multi')
    })
  })

  describe('formatListWithAnd', () => {
    it('should return empty string', () => {
      expect(formatListWithAnd([])).toBe('')
    })
    it('should return single element', () => {
      expect(formatListWithAnd(['pomme'])).toBe('pomme')
    })
    it('should return two elements joined by "et"', () => {
      expect(formatListWithAnd(['pomme', 'banane'])).toBe('pomme et banane')
    })
    it('should return elements joined by commas except the last one with "et"', () => {
      expect(formatListWithAnd(['pomme', 'banane', 'cerise'])).toBe(
        'pomme, banane et cerise'
      )
    })
  })

  describe('isValidOffPeakHours', () => {
    it('should return true for valid off-peak hours format', () => {
      expect(isValidOffPeakHours('4H00-6H00')).toBe(true)
      expect(isValidOffPeakHours('14H26-18H20')).toBe(true)
      expect(isValidOffPeakHours('0H00-23H59')).toBe(true)
    })

    it('should return false for invalid off-peak hours format', () => {
      expect(isValidOffPeakHours('invalid')).toBe(false) // unexpected string
      expect(isValidOffPeakHours('2H30')).toBe(false) // Missing end of range
      expect(isValidOffPeakHours('2H60-6H00')).toBe(false) // Minutes out of range
      expect(isValidOffPeakHours('24H00-6H00')).toBe(false) // Hour out of range
    })
  })

  describe('parseOffPeakHours', () => {
    it('should return empty array', () => {
      expect(parseOffPeakHours('')).toStrictEqual([])
    })
    it('should return single interval', () => {
      expect(parseOffPeakHours('22H00-6H00')).toStrictEqual([
        { start: { hour: 22, minute: 0 }, end: { hour: 6, minute: 0 } },
      ])
    })
    it('should return two intervals', () => {
      expect(parseOffPeakHours('3H00-8H00;13H30-16H30')).toStrictEqual([
        { start: { hour: 3, minute: 0 }, end: { hour: 8, minute: 0 } },
        { start: { hour: 13, minute: 30 }, end: { hour: 16, minute: 30 } },
      ])
    })
    it('should return empty array because range is incomplete', () => {
      expect(parseOffPeakHours('3H00-')).toStrictEqual([])
    })
    it('should return empty array because time is not valid', () => {
      expect(parseOffPeakHours('51H00-98H99')).toStrictEqual([])
    })
  })
  describe('formatTwoDigits', () => {
    it('should return number with padding', () => {
      expect(formatTwoDigits(5)).toBe('05')
    })
    it('should return number without padding', () => {
      expect(formatTwoDigits(42)).toBe('42')
    })
  })
  describe('formatOffPeakHours', () => {
    it('should format correctly', () => {
      expect(
        formatOffPeakHours({
          start: { hour: 2, minute: 0 },
          end: { hour: 10, minute: 30 },
        })
      ).toBe('02H00-10H30')
    })
  })
  describe('splitOffPeakHours', () => {
    it('should split off-peak hours that cross midnight', () => {
      const offPeakHours = [
        { start: { hour: 22, minute: 0 }, end: { hour: 6, minute: 0 } },
      ]
      const expectedSplitOffPeakHours = [
        { start: { hour: 22, minute: 0 }, end: { hour: 23, minute: 59 } },
        { start: { hour: 0, minute: 0 }, end: { hour: 6, minute: 0 } },
      ]
      const result = splitOffPeakHours(offPeakHours)
      expect(result).toEqual(expectedSplitOffPeakHours)
    })
    it('should not split off-peak hours that do not cross midnight', () => {
      const offPeakHours = [
        { start: { hour: 8, minute: 0 }, end: { hour: 12, minute: 0 } },
      ]
      const result = splitOffPeakHours(offPeakHours)
      expect(result).toEqual(offPeakHours)
    })
  })
  describe('roundOffPeakHours', () => {
    it('rounds off-peak hours to the nearest half-hour', () => {
      const offPeakHours = [
        { start: { hour: 2, minute: 2 }, end: { hour: 10, minute: 58 } },
        { start: { hour: 1, minute: 58 }, end: { hour: 11, minute: 2 } },
        { start: { hour: 7, minute: 15 }, end: { hour: 14, minute: 45 } },
        { start: { hour: 1, minute: 30 }, end: { hour: 3, minute: 0 } },
      ]
      const roundedOffPeakHours = roundOffPeakHours(offPeakHours)
      expect(roundedOffPeakHours).toEqual([
        { start: { hour: 2, minute: 0 }, end: { hour: 11, minute: 0 } },
        { start: { hour: 2, minute: 0 }, end: { hour: 11, minute: 0 } },
        { start: { hour: 7, minute: 30 }, end: { hour: 15, minute: 0 } },
        { start: { hour: 1, minute: 30 }, end: { hour: 3, minute: 0 } },
      ])
    })
    it('rounds off-peak hours to midnight', () => {
      const offPeakHours = [
        { start: { hour: 0, minute: 5 }, end: { hour: 4, minute: 0 } },
        { start: { hour: 23, minute: 55 }, end: { hour: 4, minute: 0 } },
        { start: { hour: 16, minute: 0 }, end: { hour: 23, minute: 55 } },
        { start: { hour: 16, minute: 0 }, end: { hour: 0, minute: 5 } },
      ]
      const roundedOffPeakHours = roundOffPeakHours(offPeakHours)
      expect(roundedOffPeakHours).toEqual([
        { start: { hour: 0, minute: 0 }, end: { hour: 4, minute: 0 } },
        { start: { hour: 0, minute: 0 }, end: { hour: 4, minute: 0 } },
        { start: { hour: 16, minute: 0 }, end: { hour: 23, minute: 59 } },
        { start: { hour: 16, minute: 0 }, end: { hour: 23, minute: 59 } },
      ])
    })
  })

  describe('getFluidUnit', () => {
    it('should return kWh for ELECTRICITY', () => {
      expect(getFluidUnit(FluidType.ELECTRICITY)).toBe('kWh')
    })
    it('should return L for WATER', () => {
      expect(getFluidUnit(FluidType.WATER)).toBe('L')
    })
    it('should return € for MULTIFLUID', () => {
      expect(getFluidUnit(FluidType.MULTIFLUID)).toBe('€')
    })
    it('should throw error for invalid fluid type', () => {
      expect(() => getFluidUnit(99 as FluidType.GAS)).toThrow(
        'unknown fluidtype'
      )
    })
  })
})