import { afterEach, describe, expect, it, vi } from 'vitest'
import {
  groupTimezonesByContinent,
  isValidTimezone,
  listSupportedTimezones,
  UTC,
  type IanaTimezone,
} from './timezone'

describe('isValidTimezone', () => {
  it('accepts recognized IANA zones (and narrows the type)', () => {
    expect(isValidTimezone('Asia/Jerusalem')).toBe(true)
    expect(isValidTimezone('America/New_York')).toBe(true)
    expect(isValidTimezone('UTC')).toBe(true)
    expect(isValidTimezone(UTC)).toBe(true) // the neutral constant is itself a valid zone
  })

  it('rejects empty and unknown zones (trust-boundary, no throw)', () => {
    expect(isValidTimezone('')).toBe(false)
    expect(isValidTimezone('Not/AZone')).toBe(false)
    expect(isValidTimezone('Mars/Olympus_Mons')).toBe(false)
  })
})

describe('listSupportedTimezones', () => {
  it('returns a non-empty, ascending-sorted snapshot that includes UTC', () => {
    const zones = listSupportedTimezones()
    expect(zones.length).toBeGreaterThan(0)
    expect([...zones]).toEqual([...zones].sort())
    // supportedValuesOf returns canonical primaries (excludes the 'UTC' alias) → assert a stable one.
    expect(zones).toContain('Asia/Jerusalem')
  })

  it('is memoized — returns the same frozen reference across calls', () => {
    expect(listSupportedTimezones()).toBe(listSupportedTimezones())
    expect(Object.isFrozen(listSupportedTimezones())).toBe(true)
  })
})

describe('listSupportedTimezones — Intl.supportedValuesOf-absent fallback', () => {
  afterEach(() => {
    vi.restoreAllMocks()
    vi.resetModules()
  })

  it('falls back to a frozen [UTC] when the runtime lacks supportedValuesOf', async () => {
    // The `: [UTC]` arm (timezone.ts) is unreachable on workerd/Node 18+, where
    // supportedValuesOf is universal — so exercise it by deleting the method, on a
    // FRESH module instance (resetModules) so the module-level memo starts empty.
    // The code branches on truthiness, so the property must be genuinely absent
    // (undefined), not a truthy mock that would silently test the present-branch.
    const descriptor = Object.getOwnPropertyDescriptor(Intl, 'supportedValuesOf')
    Object.defineProperty(Intl, 'supportedValuesOf', { value: undefined, configurable: true })
    try {
      vi.resetModules()
      const fresh = await import('./timezone')
      const zones = fresh.listSupportedTimezones()
      expect(zones).toEqual([UTC])
      expect(Object.isFrozen(zones)).toBe(true)
    } finally {
      if (descriptor) Object.defineProperty(Intl, 'supportedValuesOf', descriptor)
    }
  })
})

describe('groupTimezonesByContinent', () => {
  it('groups by continent prefix and buckets slashless zones under Other', () => {
    const grouped = groupTimezonesByContinent([
      'Europe/Paris',
      'Europe/Berlin',
      'Asia/Tokyo',
      'UTC',
    ] as IanaTimezone[])
    expect(grouped.Europe).toEqual(['Europe/Paris', 'Europe/Berlin'])
    expect(grouped.Asia).toEqual(['Asia/Tokyo'])
    expect(grouped.Other).toEqual(['UTC'])
  })

  it('returns an empty record for no zones', () => {
    expect(groupTimezonesByContinent([])).toEqual({})
  })
})
