import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
import {
  calculateDelay,
  calculateTypingDuration,
  isNightTime,
  DEFAULT_TIMER_CONFIG,
  type TimerConfig,
} from './index.js';

describe('Timer System', () => {
  describe('DEFAULT_TIMER_CONFIG', () => {
    it('should have sensible default values', () => {
      expect(DEFAULT_TIMER_CONFIG.baseDelay.min).toBe(5);
      expect(DEFAULT_TIMER_CONFIG.baseDelay.max).toBe(30);
      expect(DEFAULT_TIMER_CONFIG.typingSpeedCps).toBe(5);
      expect(DEFAULT_TIMER_CONFIG.nightHours.start).toBe(23);
      expect(DEFAULT_TIMER_CONFIG.nightHours.end).toBe(7);
      expect(DEFAULT_TIMER_CONFIG.nightMultiplier).toBe(2.5);
    });
  });

  describe('isNightTime', () => {
    const config = DEFAULT_TIMER_CONFIG;

    it('should return true for hours within night range (wrapping midnight)', () => {
      expect(isNightTime(23, config)).toBe(true);
      expect(isNightTime(0, config)).toBe(true);
      expect(isNightTime(3, config)).toBe(true);
      expect(isNightTime(6, config)).toBe(true);
    });

    it('should return false for daytime hours', () => {
      expect(isNightTime(7, config)).toBe(false);
      expect(isNightTime(12, config)).toBe(false);
      expect(isNightTime(18, config)).toBe(false);
      expect(isNightTime(22, config)).toBe(false);
    });

    it('should handle non-wrapping night range', () => {
      const nonWrapConfig: TimerConfig = {
        ...config,
        nightHours: { start: 1, end: 5 },
      };
      expect(isNightTime(0, nonWrapConfig)).toBe(false);
      expect(isNightTime(1, nonWrapConfig)).toBe(true);
      expect(isNightTime(3, nonWrapConfig)).toBe(true);
      expect(isNightTime(5, nonWrapConfig)).toBe(false);
    });

    it('should handle edge at start boundary', () => {
      expect(isNightTime(23, config)).toBe(true);
    });

    it('should handle edge at end boundary', () => {
      // end=7 means hour < 7 is night, hour=7 is NOT night
      expect(isNightTime(7, config)).toBe(false);
    });
  });

  describe('calculateTypingDuration', () => {
    it('should calculate duration based on message length and cps', () => {
      expect(calculateTypingDuration(50, 5)).toBe(10);
      expect(calculateTypingDuration(100, 10)).toBe(10);
      expect(calculateTypingDuration(25, 5)).toBe(5);
    });

    it('should return 0 for empty messages', () => {
      expect(calculateTypingDuration(0, 5)).toBe(0);
    });

    it('should return 0 for negative message length', () => {
      expect(calculateTypingDuration(-10, 5)).toBe(0);
    });

    it('should return 0 for zero or negative cps', () => {
      expect(calculateTypingDuration(50, 0)).toBe(0);
      expect(calculateTypingDuration(50, -1)).toBe(0);
    });
  });

  describe('calculateDelay', () => {
    let mathRandomSpy: ReturnType<typeof vi.spyOn>;

    beforeEach(() => {
      // Fix Math.random to return 0.5 for deterministic tests
      mathRandomSpy = vi.spyOn(Math, 'random').mockReturnValue(0.5);
    });

    afterEach(() => {
      mathRandomSpy.mockRestore();
    });

    it('should calculate delay with base + typing duration during daytime', () => {
      const config = DEFAULT_TIMER_CONFIG;
      // random=0.5 → base = 5 + 0.5*(30-5) = 17.5
      // typing = 50/5 = 10
      // total = 17.5 + 10 = 27.5
      const delay = calculateDelay(config, 50, 12);
      expect(delay).toBe(27.5);
    });

    it('should apply night multiplier during night hours', () => {
      const config = DEFAULT_TIMER_CONFIG;
      // random=0.5 → base = 5 + 0.5*(30-5) = 17.5
      // night multiplier: 17.5 * 2.5 = 43.75
      // typing = 50/5 = 10
      // total = 43.75 + 10 = 53.75
      const delay = calculateDelay(config, 50, 2);
      expect(delay).toBe(53.75);
    });

    it('should return only base delay for zero-length messages', () => {
      const config = DEFAULT_TIMER_CONFIG;
      // random=0.5 → base = 17.5, typing = 0
      const delay = calculateDelay(config, 0, 12);
      expect(delay).toBe(17.5);
    });

    it('should respect custom config values', () => {
      const config: TimerConfig = {
        baseDelay: { min: 10, max: 20 },
        busyScenarioDelay: { min: 5, max: 15 },
        typingSpeedCps: 10,
        nightHours: { start: 22, end: 6 },
        nightMultiplier: 3,
      };
      // random=0.5 → base = 10 + 0.5*(20-10) = 15
      // daytime (hour=12): no multiplier
      // typing = 100/10 = 10
      // total = 15 + 10 = 25
      const delay = calculateDelay(config, 100, 12);
      expect(delay).toBe(25);
    });

    it('should produce delay within expected bounds', () => {
      mathRandomSpy.mockRestore();
      const config = DEFAULT_TIMER_CONFIG;
      const messageLength = 50;
      const typingDuration = messageLength / config.typingSpeedCps;

      for (let i = 0; i < 100; i++) {
        const delay = calculateDelay(config, messageLength, 12);
        expect(delay).toBeGreaterThanOrEqual(config.baseDelay.min + typingDuration);
        expect(delay).toBeLessThanOrEqual(config.baseDelay.max + typingDuration);
      }
    });

    it('should produce night delay within expected bounds', () => {
      mathRandomSpy.mockRestore();
      const config = DEFAULT_TIMER_CONFIG;
      const messageLength = 50;
      const typingDuration = messageLength / config.typingSpeedCps;
      const nightMin = config.baseDelay.min * config.nightMultiplier + typingDuration;
      const nightMax = config.baseDelay.max * config.nightMultiplier + typingDuration;

      for (let i = 0; i < 100; i++) {
        const delay = calculateDelay(config, messageLength, 2);
        expect(delay).toBeGreaterThanOrEqual(nightMin);
        expect(delay).toBeLessThanOrEqual(nightMax);
      }
    });
  });
});
