import MockDate from 'mockdate'
import moment from 'moment-timezone'
import dayjs from '../../src'
import timezone from '../../src/plugin/timezone'
import customParseFormat from '../../src/plugin/customParseFormat'
import utc from '../../src/plugin/utc'

dayjs.extend(utc)
dayjs.extend(timezone)
dayjs.extend(customParseFormat)

beforeEach(() => {
  MockDate.set(new Date())
})

afterEach(() => {
  MockDate.reset()
})

const NY = 'America/New_York'
const VAN = 'America/Vancouver'
const DEN = 'America/Denver'
const TOKYO = 'Asia/Tokyo'
const PARIS = 'Europe/Paris'

describe('Guess', () => {
  it('return string', () => {
    expect(typeof dayjs.tz.guess()).toBe('string')
  })
})


describe('Parse', () => {
  it('parse target time string', () => {
    const newYork = dayjs.tz('2014-06-01 12:00', NY)
    const MnewYork = moment.tz('2014-06-01 12:00', NY)
    expect(newYork.format()).toBe('2014-06-01T12:00:00-04:00')
    expect(newYork.format()).toBe(MnewYork.format())
    expect(newYork.utcOffset()).toBe(-240)
    expect(newYork.utcOffset()).toBe(MnewYork.utcOffset())
    expect(newYork.valueOf()).toBe(1401638400000)
    expect(newYork.valueOf()).toBe(MnewYork.valueOf())
  })

  it('parse timestamp, js Date, Day.js object', () => {
    const d = new Date('2020-08-07T12:00-07:00')
    const result = '2020-08-07T12:00:00-07:00'
    const TjsDate = dayjs.tz(d, VAN)
    const Tdayjs = dayjs.tz(dayjs(d), VAN)
    const Timestamp = dayjs.tz(d.getTime(), VAN)
    const Tmoment = moment.tz(d, VAN)
    expect(TjsDate.format()).toBe(result)
    expect(Tdayjs.format()).toBe(result)
    expect(Timestamp.format()).toBe(result)
    expect(Tmoment.format()).toBe(result)
  })

  it('parse and convert between timezones', () => {
    const newYork = dayjs.tz('2014-06-01 12:00', NY)
    expect(newYork.tz('America/Los_Angeles').format()).toBe('2014-06-01T09:00:00-07:00')
    expect(newYork.tz('Europe/London').format()).toBe('2014-06-01T17:00:00+01:00')
  })

  it('preserve milliseconds', () => {
    const d = dayjs(1596735327399)
    const oldMs = d.millisecond()
    const dTz = d.tz('America/New_York')
    const newMs = dTz.millisecond()
    expect(oldMs).toEqual(newMs)
  })
})

describe('Convert', () => {
  it('convert to target time', () => {
    const losAngeles = dayjs('2014-06-01T12:00:00Z').tz('America/Los_Angeles')
    const MlosAngeles = moment('2014-06-01T12:00:00Z').tz('America/Los_Angeles')
    expect(losAngeles.format()).toBe('2014-06-01T05:00:00-07:00')
    expect(losAngeles.format()).toBe(MlosAngeles.format())
    expect(losAngeles.valueOf()).toBe(1401624000000)
    expect(losAngeles.valueOf()).toBe(MlosAngeles.valueOf())
    expect(losAngeles.utcOffset()).toBe(-420)
    expect(losAngeles.utcOffset()).toBe(MlosAngeles.utcOffset())
  })

  it('convert to target time', () => {
    [dayjs, moment].forEach((_) => {
      const losAngeles = _('2014-06-01T12:00:00Z').tz('America/Los_Angeles')
      expect(losAngeles.format()).toBe('2014-06-01T05:00:00-07:00')
      expect(losAngeles.valueOf()).toBe(1401624000000)
    })
  })

  it('convert from time with timezone to target time', () => {
    const losAngelesInUTC = dayjs('2014-06-01T05:00:00-07:00').tz('UTC')
    const MlosAngelesInUTC = moment('2014-06-01T05:00:00-07:00').tz('UTC')
    expect(losAngelesInUTC.format()).toBe('2014-06-01T12:00:00Z')
    expect(losAngelesInUTC.format()).toBe(MlosAngelesInUTC.format())
  })

  it('DST', () => {
    [dayjs, moment].forEach((_) => {
      const jun = _('2014-06-01T12:00:00Z')
      const dec = _('2014-12-01T12:00:00Z')
      expect(jun.tz('America/Los_Angeles').format('ha')).toBe('5am')
      expect(jun.tz('America/Los_Angeles').utcOffset()).toBe(-7 * 60)
      expect(dec.tz('America/Los_Angeles').format('ha')).toBe('4am')
      expect(dec.tz('America/Los_Angeles').utcOffset()).toBe(-8 * 60)
      expect(jun.tz(NY).format('ha')).toBe('8am')
      expect(dec.tz(NY).format('ha')).toBe('7am')
      expect(jun.tz(TOKYO).format('ha')).toBe('9pm')
      expect(dec.tz(TOKYO).format('ha')).toBe('9pm')
      expect(jun.tz('Australia/Sydney').format('ha')).toBe('10pm')
      expect(dec.tz('Australia/Sydney').format('ha')).toBe('11pm')
    })
  })

  it('format Z', () => {
    [dayjs, moment].forEach((_) => {
      const t = _('2020-08-06T03:48:10.258Z').tz(TOKYO)
      expect(t.format('Z')).toBe('+09:00')
    })
  })
})


describe('DST, a time that never existed Spring Forward', () => {
  // 11 March 2012, 02:00:00 clocks were
  // turned forward 1 hour to 11 March 2012, 03:00:00 local
  // daylight time instead.
  // 02:00 -> 03:00
  // 02:59 -> 03:59

  it('2012-03-11 01:59:59', () => {
    const s = '2012-03-11 01:59:59'
    const d = dayjs.tz(s, NY)
    const m = moment.tz(s, NY)
    expect(d.format()).toBe('2012-03-11T01:59:59-05:00')
    expect(d.format()).toBe(m.format())
    expect(d.utcOffset()).toBe(-300)
    expect(d.utcOffset()).toBe(m.utcOffset())
    expect(d.valueOf()).toBe(1331449199000)
    expect(d.valueOf()).toBe(m.valueOf())
  })
  it('2012-03-11 02:00:00', () => {
    const s = '2012-03-11 02:00:00'
    const d = dayjs.tz(s, NY)
    const m = moment.tz(s, NY)
    expect(d.format()).toBe('2012-03-11T03:00:00-04:00')
    expect(d.format()).toBe(m.format())
    expect(d.valueOf()).toBe(m.valueOf())
    expect(d.valueOf()).toBe(1331449200000)
    expect(d.utcOffset()).toBe(-240)
    expect(d.utcOffset()).toBe(m.utcOffset())
  })
  it('2012-03-11 02:59:59', () => {
    const s = '2012-03-11 02:59:59'
    const d = dayjs.tz(s, NY)
    const m = moment.tz(s, NY)
    expect(d.format()).toBe('2012-03-11T03:59:59-04:00')
    expect(d.format()).toBe(m.format())
    expect(d.valueOf()).toBe(m.valueOf())
    expect(d.valueOf()).toBe(1331452799000)
    expect(d.utcOffset()).toBe(-240)
    expect(d.utcOffset()).toBe(m.utcOffset())
  })
  it('2012-03-11 03:00:00', () => {
    const s = '2012-03-11 03:00:00'
    const d = dayjs.tz(s, NY)
    const m = moment.tz(s, NY)
    expect(d.format()).toBe('2012-03-11T03:00:00-04:00')
    expect(d.format()).toBe(m.format())
    expect(d.valueOf()).toBe(m.valueOf())
    expect(d.valueOf()).toBe(1331449200000)
    expect(d.utcOffset()).toBe(-240)
    expect(d.utcOffset()).toBe(m.utcOffset())
  })
})

describe('DST, a time that never existed Fall Back', () => {
  // In the fall, at the end of DST

  it('2012-11-04 00:59:59', () => {
    const s = '2012-11-04 00:59:59';
    [dayjs, moment].forEach((_) => {
      const d = _.tz(s, NY)
      expect(d.format()).toBe('2012-11-04T00:59:59-04:00')
      expect(d.utcOffset()).toBe(-240)
      expect(d.valueOf()).toBe(1352005199000)
    })
  })
  it('2012-11-04 00:59:59', () => {
    const s = '2012-11-04 00:59:59';
    [dayjs, moment].forEach((_) => {
      const d = _.tz(s, NY)
      expect(d.format()).toBe('2012-11-04T00:59:59-04:00')
      expect(d.utcOffset()).toBe(-240)
      expect(d.valueOf()).toBe(1352005199000)
    })
  })

  // there's no sense to test "2012-11-04 01:59:59 America/New_York"
  // cause it's an invalid date and never exist
  // and dayjs result it as "2012-11-04T01:59:00-05:00"

  it('2012-11-04 02:00:00', () => {
    const s = '2012-11-04 02:00:00';
    [dayjs, moment].forEach((_) => {
      const d = _.tz(s, NY)
      expect(d.format()).toBe('2012-11-04T02:00:00-05:00')
      expect(d.utcOffset()).toBe(-300)
      expect(d.valueOf()).toBe(1352012400000)
    })
  })
})

it('DST valueOf', () => {
  const day1 = '2021-11-17T09:45:00.000Z'
  const d1 = dayjs.utc(day1).tz(PARIS)
  const m1 = moment.tz(day1, PARIS)
  expect(d1.valueOf()).toBe(m1.valueOf())

  const day2 = '2021-05-17T09:45:00.000Z'
  const d2 = dayjs.utc(day2).tz(PARIS)
  const m2 = moment.tz(day2, PARIS)
  expect(d2.valueOf()).toBe(m2.valueOf())
})

describe('set Default', () => {
  it('default timezone', () => {
    const dateStr = '2014-06-01 12:00'
    dayjs.tz.setDefault(NY)
    const newYork = dayjs.tz(dateStr)
    expect(newYork.format()).toBe('2014-06-01T12:00:00-04:00')
    expect(newYork.utcOffset()).toBe(-240)
    expect(newYork.valueOf()).toBe(1401638400000)

    expect(dayjs(dateStr).tz().format()).toBe(dayjs(dateStr).tz(NY).format())
  })

  it('empty timezone means local timezone', () => {
    const LOCAL_TZ = dayjs.tz.guess()
    const dateStr = '2014-06-01 12:00'
    dayjs.tz.setDefault()
    expect(dayjs(dateStr).tz().valueOf()).toBe(dayjs(dateStr).tz(LOCAL_TZ).valueOf())
    expect(dayjs.tz(dateStr).valueOf()).toBe(dayjs.tz(dateStr, LOCAL_TZ).valueOf())
  })

  it('change default timezone', () => {
    dayjs.tz.setDefault(NY)
    const newYork = dayjs.tz('2014-06-01 12:00')
    expect(newYork.utcOffset()).toBe(-240)

    dayjs.tz.setDefault(TOKYO)
    const tokyo = dayjs.tz('2014-06-01 12:00')
    expect(tokyo.format()).toBe('2014-06-01T12:00:00+09:00')
    expect(tokyo.format('Z')).toBe('+09:00')
    expect(tokyo.valueOf()).toBe(1401591600000)
  })

  it('override default timezone in proto.tz', () => {
    dayjs.tz.setDefault(NY)
    const tokyo = dayjs.tz('2014-06-01 12:00', TOKYO)
    expect(tokyo.format()).toBe('2014-06-01T12:00:00+09:00')
    expect(tokyo.format('Z')).toBe('+09:00')
    expect(tokyo.valueOf()).toBe(1401591600000)
  })

  it('override default timezone in d.tz', () => {
    dayjs.tz.setDefault(NY)
    const tokyo = dayjs.tz('2014-06-01 12:00', TOKYO)
    expect(tokyo.format()).toBe('2014-06-01T12:00:00+09:00')
    expect(tokyo.format('Z')).toBe('+09:00')
    expect(tokyo.valueOf()).toBe(1401591600000)
  })
})

describe('keepLocalTime', () => {
  const base = dayjs.tz('2013-11-18 11:55', 'America/Toronto')
  it('keepLocalTime', () => {
    expect(base.tz('Europe/Berlin').format()).toBe('2013-11-18T17:55:00+01:00')
    expect(base.tz('Europe/Berlin', true).format()).toBe('2013-11-18T11:55:00+01:00')
  })
})

describe('Get offsetName', () => {
  const dtz = dayjs.tz('2012-03-11 01:59:59', NY)
  it('short', () => {
    const d = dtz.offsetName('short')
    const m = moment.tz('2012-03-11 01:59:59', NY).format('z')
    expect(d).toBe(m)
    expect(d).toBe('EST')
  })
  it('long', () => {
    const d = dtz.offsetName('long')
    expect(d).toBe('Eastern Standard Time')
  })
})

describe('CustomPraseFormat', () => {
  const result = 1602786600
  it('normal', () => {
    expect(dayjs.tz('2020/10/15 12:30', DEN).unix()).toBe(result)
  })
  it('custom', () => {
    expect(dayjs.tz('10/15/2020 12:30', 'MM/DD/YYYY HH:mm', DEN).unix()).toBe(result)
  })
})

describe('startOf and endOf', () => {
  it('corrects for timezone offset in startOf', () => {
    const originalDay = dayjs.tz('2010-01-01 00:00:00', NY)
    const startOfDay = originalDay.startOf('day')
    expect(startOfDay.valueOf()).toEqual(originalDay.valueOf())
  })

  it('corrects for timezone offset in endOf', () => {
    const originalDay = dayjs.tz('2009-12-31 23:59:59.999', NY)
    const endOfDay = originalDay.endOf('day')
    expect(endOfDay.valueOf()).toEqual(originalDay.valueOf())
  })

  it('preserves locality when tz is called', () => {
    const tzWithoutLocality = dayjs.tz('2023-02-17 00:00:00', NY)
    const tzWithLocality = dayjs.tz('2023-02-17 00:00:00', NY).locale({
      name: 'locale_test',
      weekStart: 3
    })

    expect(tzWithoutLocality.startOf('week').format('YYYY-MM-DD')).toEqual('2023-02-12')
    expect(tzWithLocality.startOf('week').format('YYYY-MM-DD')).toEqual('2023-02-15')
  })
})


describe('UTC timezone', () => {
  it('TZ with UTC with Locale', () => {
    const test1 = dayjs('2000-01-01T09:00:00+09:00').tz('Asia/Seoul').locale('en')
    expect(test1.hour()).toBe(9)
    const test2 = dayjs('2000-01-01T09:00:00+09:00').tz('Asia/Hong_Kong').locale('en')
    expect(test2.hour()).toBe(8)
    const test3 = dayjs('2000-01-01T09:00:00+09:00').tz('Etc/UTC').locale('en')
    expect(test3.hour()).toBe(0)
  })

  it('TZ with UTC', () => {
    const dayjs1 = dayjs('2000-01-01T09:01:00+09:00').tz('Etc/UTC', false)
    expect(dayjs1.format()).toBe('2000-01-01T00:01:00Z')
    const moment1 = moment('2000-01-01T09:01:00+09:00').tz('Etc/UTC', false)
    expect(moment1.format()).toBe('2000-01-01T00:01:00Z')
    const dayjs2 = dayjs('2000-01-01T09:01:00+09:00').tz('Etc/UTC', true)
    const moment2 = moment('2000-01-01T09:01:00+09:00').tz('Etc/UTC', true)
    expect(dayjs2.format()).toBe(moment2.format())
  })
})
