import {
  PAGE_MINUTES,
  currentBucket,
  currentPage,
  formatBucket,
  isPastBucket,
  isPastPage,
  nextPage,
  parseBucket,
  parsePage,
  previousPage,
} from 'src/lib/tapestry/window'

function utc(
  y: number,
  mo: number,
  d: number,
  h: number,
  mi: number,
  s = 0,
  ms = 0
): Date {
  return new Date(Date.UTC(y, mo - 1, d, h, mi, s, ms))
}

describe('tapestry window — formatBucket', () => {
  test('formats UTC components zero-padded to YYYYMMDDHHMM', () => {
    expect(formatBucket(utc(2026, 4, 28, 9, 5))).toBe('202604280905')
    expect(formatBucket(utc(2026, 1, 1, 0, 0))).toBe('202601010000')
    expect(formatBucket(utc(2026, 12, 31, 23, 59))).toBe('202612312359')
  })

  test('uses UTC, not the local timezone', () => {
    // 2026-04-28T03:30:00Z renders the same regardless of TZ.
    expect(formatBucket(new Date('2026-04-28T03:30:00Z'))).toBe('202604280330')
  })
})

describe('tapestry window — parseBucket', () => {
  test('round-trips a valid 12-digit minute', () => {
    const s = '202604280905'
    const d = parseBucket(s)
    expect(d).not.toBeNull()
    expect(formatBucket(d!)).toBe(s)
  })

  test('returns a UTC Date with seconds and ms zeroed', () => {
    const d = parseBucket('202604280905')!
    expect(d.getUTCSeconds()).toBe(0)
    expect(d.getUTCMilliseconds()).toBe(0)
    expect(d.getUTCFullYear()).toBe(2026)
    expect(d.getUTCMonth()).toBe(3) // April
    expect(d.getUTCDate()).toBe(28)
    expect(d.getUTCHours()).toBe(9)
    expect(d.getUTCMinutes()).toBe(5)
  })

  test('rejects non-12-digit input', () => {
    expect(parseBucket('')).toBeNull()
    expect(parseBucket('20260428090')).toBeNull() // 11 digits
    expect(parseBucket('2026042809055')).toBeNull() // 13 digits
    expect(parseBucket('2026-04-28-09-05')).toBeNull()
    expect(parseBucket('20260428090a')).toBeNull()
  })

  test('rejects rollover overflows that PHP/JS would silently round', () => {
    // month=13
    expect(parseBucket('202613320000')).toBeNull()
    // minute=60
    expect(parseBucket('202604280060')).toBeNull()
    // hour=24
    expect(parseBucket('202604282400')).toBeNull()
    // day=32
    expect(parseBucket('202601320000')).toBeNull()
    // Feb 30, 2026 (not a leap rollover safety net)
    expect(parseBucket('202602300000')).toBeNull()
  })
})

describe('tapestry window — parsePage', () => {
  test('accepts minute-aligned pages', () => {
    expect(parsePage('202604280900')).not.toBeNull()
    expect(parsePage('202604280930')).not.toBeNull()
    expect(parsePage('202604282330')).not.toBeNull()
    expect(parsePage('202604280000')).not.toBeNull()
  })

  test('rejects misaligned minutes', () => {
    expect(parsePage('202604280914')).toBeNull()
    expect(parsePage('202604280945')).toBeNull()
    expect(parsePage('202604280901')).toBeNull()
    expect(parsePage('202604280959')).toBeNull()
  })

  test('rejects malformed input the same way parseBucket does', () => {
    expect(parsePage('202613320000')).toBeNull()
    expect(parsePage('not-a-bucket')).toBeNull()
  })
})

describe('tapestry window — currentBucket / currentPage', () => {
  test('currentBucket floors `now` to the minute in UTC', () => {
    const now = utc(2026, 4, 28, 9, 5, 47, 123)
    expect(currentBucket(now)).toBe('202604280905')
  })

  test('currentPage floors `now` to the 30-minute boundary', () => {
    expect(currentPage(utc(2026, 4, 28, 9, 0))).toBe('202604280900')
    expect(currentPage(utc(2026, 4, 28, 9, 14, 59))).toBe('202604280900')
    expect(currentPage(utc(2026, 4, 28, 9, 29, 59))).toBe('202604280900')
    expect(currentPage(utc(2026, 4, 28, 9, 30))).toBe('202604280930')
    expect(currentPage(utc(2026, 4, 28, 9, 45))).toBe('202604280930')
    expect(currentPage(utc(2026, 4, 28, 9, 59, 59))).toBe('202604280930')
  })

  test('currentPage output is itself a valid page', () => {
    const p = currentPage(utc(2026, 4, 28, 9, 17))
    expect(parsePage(p)).not.toBeNull()
  })
})

describe('tapestry window — previousPage / nextPage', () => {
  test('walks back and forward by 30 minutes', () => {
    expect(previousPage('202604280930')).toBe('202604280900')
    expect(nextPage('202604280900')).toBe('202604280930')
    expect(nextPage('202604280930')).toBe('202604281000')
  })

  test('crosses an hour boundary', () => {
    expect(previousPage('202604281000')).toBe('202604280930')
    expect(nextPage('202604280930')).toBe('202604281000')
  })

  test('crosses a day boundary', () => {
    expect(nextPage('202604282330')).toBe('202604290000')
    expect(previousPage('202604290000')).toBe('202604282330')
  })

  test('crosses a month boundary', () => {
    expect(nextPage('202604302330')).toBe('202605010000')
    expect(previousPage('202605010000')).toBe('202604302330')
  })

  test('crosses a year boundary', () => {
    expect(nextPage('202612312330')).toBe('202701010000')
    expect(previousPage('202701010000')).toBe('202612312330')
  })

  test('throws on a misaligned page input', () => {
    expect(() => previousPage('202604280914')).toThrow()
    expect(() => nextPage('202604280945')).toThrow()
  })
})

describe('tapestry window — isPastBucket', () => {
  const now = utc(2026, 4, 28, 9, 5, 30)

  test('a bucket whose trailing edge is before now is past', () => {
    // bucket 09:03 ends at 09:04 — past at 09:05:30
    expect(isPastBucket('202604280903', now)).toBe(true)
  })

  test('the bucket containing now is not past', () => {
    // bucket 09:05 ends at 09:06 — not past at 09:05:30
    expect(isPastBucket('202604280905', now)).toBe(false)
  })

  test('a future bucket is not past', () => {
    expect(isPastBucket('202604280906', now)).toBe(false)
    expect(isPastBucket('202604281000', now)).toBe(false)
  })

  test('boundary: bucket end == now is past', () => {
    // bucket 09:04 ends at 09:05; with now == 09:05:00 sharp, the
    // trailing edge has been reached, so the bucket is past.
    const sharp = utc(2026, 4, 28, 9, 5, 0)
    expect(isPastBucket('202604280904', sharp)).toBe(true)
  })

  test('returns false on malformed input rather than throwing', () => {
    expect(isPastBucket('not-a-bucket', now)).toBe(false)
    expect(isPastBucket('202613320000', now)).toBe(false)
  })
})

describe('tapestry window — isPastPage', () => {
  const now = utc(2026, 4, 28, 9, 35)

  test('a page whose trailing edge is before now is past', () => {
    // page 09:00 ends at 09:30 — past at 09:35
    expect(isPastPage('202604280900', now)).toBe(true)
  })

  test('the page containing now is not past', () => {
    // page 09:30 ends at 10:00 — not past at 09:35
    expect(isPastPage('202604280930', now)).toBe(false)
  })

  test('boundary: page start == now is current, not past', () => {
    // page start == now means the page just began; trailing edge is 30
    // minutes from now, so the page is current.
    const exact = utc(2026, 4, 28, 9, 30)
    expect(isPastPage('202604280930', exact)).toBe(false)
  })

  test('boundary: page end == now is past', () => {
    // page 09:00 ends at 09:30; with now == 09:30 sharp, the trailing
    // edge has been reached, so the page is past.
    const exact = utc(2026, 4, 28, 9, 30)
    expect(isPastPage('202604280900', exact)).toBe(true)
  })

  test('a future page is not past', () => {
    expect(isPastPage('202604281000', now)).toBe(false)
  })

  test('returns false on malformed input rather than throwing', () => {
    expect(isPastPage('202604280914', now)).toBe(false)
    expect(isPastPage('not-a-page', now)).toBe(false)
  })
})

describe('tapestry window — PAGE_MINUTES is the frozen mirrored constant', () => {
  test('PAGE_MINUTES === 30', () => {
    expect(PAGE_MINUTES).toBe(30)
  })
})
