import { describe, it, expect, beforeEach, vi } from 'vitest';
import { NavigationManager } from '../../src/managers/NavigationManager';
import { EventBus } from '../../src/core/EventBus';
import { EventRenderingService } from '../../src/renderers/EventRendererManager';
import { DateService } from '../../src/utils/DateService';
import { createTestConfig } from '../helpers/config-helpers';

describe('NavigationManager - Edge Cases', () => {
  let navigationManager: NavigationManager;
  let eventBus: EventBus;
  let dateService: DateService;

  beforeEach(() => {
    eventBus = new EventBus();
    const config = createTestConfig();
    dateService = new DateService(config);
    const mockEventRenderer = {} as EventRenderingService;
    const mockGridRenderer = {} as any;
    const mockNavigationRenderer = {} as any;
    navigationManager = new NavigationManager(eventBus, mockEventRenderer, mockGridRenderer, dateService, mockNavigationRenderer);
  });

  describe('Week 53 Navigation', () => {
    it('should correctly navigate to week 53 (year 2020)', () => {
      // Dec 28, 2020 is start of week 53
      const week53Start = new Date(2020, 11, 28);

      const weekNum = dateService.getWeekNumber(week53Start);
      expect(weekNum).toBe(53);

      const weekBounds = dateService.getWeekBounds(week53Start);
      expect(weekBounds.start.getDate()).toBe(28);
      expect(weekBounds.start.getMonth()).toBe(11); // December
      expect(weekBounds.start.getFullYear()).toBe(2020);
    });

    it('should navigate from week 53 to week 1 of next year', () => {
      const week53 = new Date(2020, 11, 28); // Week 53, 2020

      // Add 1 week should go to week 1 of 2021
      const nextWeek = dateService.addWeeks(week53, 1);
      const nextWeekNum = dateService.getWeekNumber(nextWeek);

      expect(nextWeek.getFullYear()).toBe(2021);
      expect(nextWeekNum).toBe(1);
    });

    it('should navigate from week 1 back to week 53 of previous year', () => {
      const week1_2021 = new Date(2021, 0, 4); // Monday Jan 4, 2021 (week 1)

      // Subtract 1 week should go to week 53 of 2020
      const prevWeek = dateService.addWeeks(week1_2021, -1);
      const prevWeekNum = dateService.getWeekNumber(prevWeek);

      expect(prevWeek.getFullYear()).toBe(2020);
      expect(prevWeekNum).toBe(53);
    });

    it('should handle years without week 53 (2021)', () => {
      const dec27_2021 = new Date(2021, 11, 27); // Monday Dec 27, 2021
      const weekNum = dateService.getWeekNumber(dec27_2021);

      expect(weekNum).toBe(52); // No week 53 in 2021

      const nextWeek = dateService.addWeeks(dec27_2021, 1);
      const nextWeekNum = dateService.getWeekNumber(nextWeek);

      // ISO week logic: Adding 1 week from Dec 27 gives Jan 3, which is week 1 of 2022
      expect(nextWeekNum).toBe(1); // Week 1 of 2022

      // Jan 3, 2022 is indeed week 1
      const jan3_2022 = new Date(2022, 0, 3);
      expect(dateService.getWeekNumber(jan3_2022)).toBe(1);
    });

    it('should correctly identify week 53 in 2026', () => {
      const dec28_2026 = new Date(2026, 11, 28); // Monday Dec 28, 2026
      const weekNum = dateService.getWeekNumber(dec28_2026);

      expect(weekNum).toBe(53);
    });
  });

  describe('Year Boundary Navigation', () => {
    it('should navigate across year boundary (Dec -> Jan)', () => {
      const lastWeekDec = new Date(2024, 11, 23); // Dec 23, 2024
      const firstWeekJan = dateService.addWeeks(lastWeekDec, 1);

      // Adding 1 week gives Dec 30, which is in week 1 of 2025 (ISO week logic)
      expect(firstWeekJan.getMonth()).toBe(11); // Still December

      const weekNum = dateService.getWeekNumber(firstWeekJan);
      // Week number can be 1 (of next year) or 52 depending on ISO week rules
      expect(weekNum).toBeGreaterThanOrEqual(1);
    });

    it('should navigate across year boundary (Jan -> Dec)', () => {
      const firstWeekJan = new Date(2024, 0, 1);
      const lastWeekDec = dateService.addWeeks(firstWeekJan, -1);

      expect(lastWeekDec.getFullYear()).toBe(2023);

      const weekNum = dateService.getWeekNumber(lastWeekDec);
      expect(weekNum).toBeGreaterThanOrEqual(52);
    });

    it('should get correct week bounds at year start', () => {
      const jan1_2024 = new Date(2024, 0, 1); // Monday Jan 1, 2024
      const weekBounds = dateService.getWeekBounds(jan1_2024);

      // Week should start on Monday
      const startDayOfWeek = weekBounds.start.getDay();
      expect(startDayOfWeek).toBe(1); // Monday = 1

      expect(weekBounds.start.getDate()).toBe(1);
      expect(weekBounds.start.getMonth()).toBe(0); // January
    });

    it('should get correct week bounds at year end', () => {
      const dec31_2024 = new Date(2024, 11, 31); // Tuesday Dec 31, 2024
      const weekBounds = dateService.getWeekBounds(dec31_2024);

      // Week should start on Monday (Dec 30, 2024)
      expect(weekBounds.start.getDate()).toBe(30);
      expect(weekBounds.start.getMonth()).toBe(11);
      expect(weekBounds.start.getFullYear()).toBe(2024);

      // Week should end on Sunday (Jan 5, 2025)
      expect(weekBounds.end.getDate()).toBe(5);
      expect(weekBounds.end.getMonth()).toBe(0); // January
      expect(weekBounds.end.getFullYear()).toBe(2025);
    });
  });

  describe('DST Transition Navigation', () => {
    it('should navigate across spring DST transition (March 2024)', () => {
      // Spring DST: March 31, 2024, 02:00 -> 03:00
      const beforeDST = new Date(2024, 2, 25); // Week before DST
      const duringDST = dateService.addWeeks(beforeDST, 1);

      expect(duringDST.getMonth()).toBe(3); // April
      expect(dateService.isValid(duringDST)).toBe(true);

      const weekBounds = dateService.getWeekBounds(duringDST);
      expect(weekBounds.start.getMonth()).toBeGreaterThanOrEqual(2); // March or April
    });

    it('should navigate across fall DST transition (October 2024)', () => {
      // Fall DST: October 27, 2024, 03:00 -> 02:00
      const beforeDST = new Date(2024, 9, 21); // Week before DST
      const duringDST = dateService.addWeeks(beforeDST, 1);

      expect(duringDST.getMonth()).toBe(9); // October
      expect(dateService.isValid(duringDST)).toBe(true);

      const weekBounds = dateService.getWeekBounds(duringDST);
      expect(weekBounds.end.getMonth()).toBeLessThanOrEqual(10); // October or November
    });

    it('should maintain week integrity across DST', () => {
      const beforeDST = new Date(2024, 2, 25, 12, 0);
      const afterDST = dateService.addWeeks(beforeDST, 1);

      // Week bounds should still give 7-day span
      const weekBounds = dateService.getWeekBounds(afterDST);
      const daysDiff = (weekBounds.end.getTime() - weekBounds.start.getTime()) / (1000 * 60 * 60 * 24);

      // Should be close to 7 days (accounting for DST hour change)
      expect(daysDiff).toBeGreaterThanOrEqual(6.9);
      expect(daysDiff).toBeLessThanOrEqual(7.1);
    });
  });

  describe('Month Boundary Week Navigation', () => {
    it('should handle week spanning month boundary', () => {
      const endOfMonth = new Date(2024, 0, 29); // Jan 29, 2024 (Monday)
      const weekBounds = dateService.getWeekBounds(endOfMonth);

      // Week should span into February
      expect(weekBounds.end.getMonth()).toBe(1); // February
      expect(weekBounds.end.getDate()).toBe(4);
    });

    it('should navigate to next week across month boundary', () => {
      const lastWeekJan = new Date(2024, 0, 29);
      const firstWeekFeb = dateService.addWeeks(lastWeekJan, 1);

      expect(firstWeekFeb.getMonth()).toBe(1); // February
      expect(firstWeekFeb.getDate()).toBe(5);
    });

    it('should handle February-March boundary in leap year', () => {
      const lastWeekFeb = new Date(2024, 1, 26); // Feb 26, 2024 (leap year)
      const weekBounds = dateService.getWeekBounds(lastWeekFeb);

      // Week should span from Feb into March
      expect(weekBounds.start.getMonth()).toBe(1); // February
      expect(weekBounds.end.getMonth()).toBe(2);   // March
    });
  });

  describe('Invalid Date Navigation', () => {
    it('should reject navigation to invalid date', () => {
      const invalidDate = new Date('invalid');
      const validation = dateService.validateDate(invalidDate);

      expect(validation.valid).toBe(false);
      expect(validation.error).toBeDefined();
    });

    it('should reject navigation to out-of-bounds date', () => {
      const outOfBounds = new Date(2150, 0, 1);
      const validation = dateService.validateDate(outOfBounds);

      expect(validation.valid).toBe(false);
      expect(validation.error).toContain('bounds');
    });

    it('should accept valid date within bounds', () => {
      const validDate = new Date(2024, 6, 15);
      const validation = dateService.validateDate(validDate);

      expect(validation.valid).toBe(true);
      expect(validation.error).toBeUndefined();
    });
  });

  describe('Week Number Edge Cases', () => {
    it('should handle first day of year in previous year\'s week', () => {
      // Jan 1, 2023 is a Sunday, part of week 52 of 2022
      const jan1_2023 = new Date(2023, 0, 1);
      const weekNum = dateService.getWeekNumber(jan1_2023);

      expect(weekNum).toBe(52); // Part of 2022's last week
    });

    it('should handle last day of year in next year\'s week', () => {
      // Dec 31, 2023 is a Sunday, part of week 52 of 2023
      const dec31_2023 = new Date(2023, 11, 31);
      const weekNum = dateService.getWeekNumber(dec31_2023);

      expect(weekNum).toBe(52);
    });

    it('should correctly number weeks in leap year', () => {
      const dates2024 = [
        new Date(2024, 0, 1),   // Week 1
        new Date(2024, 6, 1),   // Mid-year
        new Date(2024, 11, 31)  // Last week
      ];

      dates2024.forEach(date => {
        const weekNum = dateService.getWeekNumber(date);
        expect(weekNum).toBeGreaterThanOrEqual(1);
        expect(weekNum).toBeLessThanOrEqual(53);
      });
    });
  });

  describe('Navigation Continuity', () => {
    it('should maintain continuity over multiple forward navigations', () => {
      let currentWeek = new Date(2024, 0, 1);

      for (let i = 0; i < 60; i++) { // Navigate 60 weeks forward
        currentWeek = dateService.addWeeks(currentWeek, 1);
        expect(dateService.isValid(currentWeek)).toBe(true);
      }

      // Should be in 2025
      expect(currentWeek.getFullYear()).toBe(2025);
    });

    it('should maintain continuity over multiple backward navigations', () => {
      let currentWeek = new Date(2024, 11, 31);

      for (let i = 0; i < 60; i++) { // Navigate 60 weeks backward
        currentWeek = dateService.addWeeks(currentWeek, -1);
        expect(dateService.isValid(currentWeek)).toBe(true);
      }

      // Should be in 2023
      expect(currentWeek.getFullYear()).toBe(2023);
    });

    it('should return to same week after forward+backward navigation', () => {
      const originalWeek = new Date(2024, 6, 15);
      const weekBoundsOriginal = dateService.getWeekBounds(originalWeek);

      // Navigate 10 weeks forward, then 10 weeks back
      const forward = dateService.addWeeks(originalWeek, 10);
      const backAgain = dateService.addWeeks(forward, -10);

      const weekBoundsBack = dateService.getWeekBounds(backAgain);

      expect(weekBoundsBack.start.getTime()).toBe(weekBoundsOriginal.start.getTime());
      expect(weekBoundsBack.end.getTime()).toBe(weekBoundsOriginal.end.getTime());
    });
  });
});
