import { describe, it, expect, beforeEach, vi, afterEach } from 'vitest'
import { usePlayerStore } from '../../shared/stores/playerStore'
import type { Workout } from '../../shared/types'

const mockWorkout: Workout = {
  id: 'test-workout',
  title: 'Test Workout',
  trainerId: 'trainer-1',
  category: 'full-body',
  level: 'Beginner',
  duration: 4,
  calories: 48,
  rounds: 8,
  prepTime: 10,
  workTime: 20,
  restTime: 10,
  equipment: [],
  musicVibe: 'electronic',
  exercises: [
    { name: 'Jumping Jacks', duration: 20 },
    { name: 'Squats', duration: 20 },
    { name: 'Push-ups', duration: 20 },
    { name: 'High Knees', duration: 20 },
  ],
}

function getExerciseForRound(round: number, exercises: typeof mockWorkout.exercises): string {
  const index = (round - 1) % exercises.length
  return exercises[index]?.name ?? ''
}

function getNextExercise(round: number, exercises: typeof mockWorkout.exercises): string | undefined {
  const index = round % exercises.length
  return exercises[index]?.name
}

function calculateProgress(timeRemaining: number, phaseDuration: number): number {
  return phaseDuration > 0 ? 1 - timeRemaining / phaseDuration : 1
}

function getPhaseDuration(phase: string, workout: typeof mockWorkout): number {
  switch (phase) {
    case 'PREP': return workout.prepTime
    case 'WORK': return workout.workTime
    case 'REST': return workout.restTime
    default: return 0
  }
}

describe('useTimer', () => {
  beforeEach(() => {
    vi.useFakeTimers()
    usePlayerStore.getState().reset()
  })

  afterEach(() => {
    vi.useRealTimers()
    vi.clearAllMocks()
  })

  describe('initialization', () => {
    it('should have correct default values', () => {
      const state = usePlayerStore.getState()

      expect(state.phase).toBe('PREP')
      expect(state.timeRemaining).toBe(10)
      expect(state.currentRound).toBe(1)
      expect(state.isPaused).toBe(false)
      expect(state.isRunning).toBe(false)
      expect(state.calories).toBe(0)
    })

    it('should load workout and set prepTime', () => {
      usePlayerStore.getState().loadWorkout(mockWorkout)

      const state = usePlayerStore.getState()
      expect(state.workout).toEqual(mockWorkout)
      expect(state.timeRemaining).toBe(mockWorkout.prepTime)
    })

    it('should return correct totalRounds from workout', () => {
      const totalRounds = mockWorkout.rounds
      expect(totalRounds).toBe(8)
    })

    it('should return correct currentExercise for round 1', () => {
      const exercise = getExerciseForRound(1, mockWorkout.exercises)
      expect(exercise).toBe('Jumping Jacks')
    })
  })

  describe('progress calculation', () => {
    it('should calculate progress as 0 at start', () => {
      usePlayerStore.getState().loadWorkout(mockWorkout)
      const state = usePlayerStore.getState()

      const phaseDuration = getPhaseDuration(state.phase, mockWorkout)
      const progress = calculateProgress(state.timeRemaining, phaseDuration)

      expect(progress).toBe(0)
    })

    it('should calculate progress correctly mid-phase', () => {
      usePlayerStore.getState().loadWorkout(mockWorkout)
      usePlayerStore.getState().setTimeRemaining(5)

      const state = usePlayerStore.getState()
      const phaseDuration = getPhaseDuration(state.phase, mockWorkout)
      const progress = calculateProgress(state.timeRemaining, phaseDuration)

      expect(progress).toBe(0.5)
    })

    it('should calculate progress as 1 when time is 0', () => {
      usePlayerStore.getState().loadWorkout(mockWorkout)
      usePlayerStore.getState().setTimeRemaining(0)

      const state = usePlayerStore.getState()
      const phaseDuration = getPhaseDuration(state.phase, mockWorkout)
      const progress = calculateProgress(state.timeRemaining, phaseDuration)

      expect(progress).toBe(1)
    })
  })

  describe('exercise tracking', () => {
    it('should return correct exercise for each round', () => {
      const expectedOrder = [
        'Jumping Jacks', 'Squats', 'Push-ups', 'High Knees',
        'Jumping Jacks', 'Squats', 'Push-ups', 'High Knees'
      ]

      for (let i = 0; i < 8; i++) {
        const round = i + 1
        const exercise = getExerciseForRound(round, mockWorkout.exercises)
        expect(exercise).toBe(expectedOrder[i])
      }
    })

    it('should cycle through exercises continuously', () => {
      expect(getExerciseForRound(1, mockWorkout.exercises)).toBe('Jumping Jacks')
      expect(getExerciseForRound(5, mockWorkout.exercises)).toBe('Jumping Jacks')
      expect(getExerciseForRound(9, mockWorkout.exercises)).toBe('Jumping Jacks')
    })
  })

  describe('controls', () => {
    describe('start', () => {
      it('should set running and clear pause', () => {
        const store = usePlayerStore.getState()

        store.setRunning(true)
        store.setPaused(false)

        const state = usePlayerStore.getState()
        expect(state.isRunning).toBe(true)
        expect(state.isPaused).toBe(false)
      })
    })

    describe('pause', () => {
      it('should set paused state', () => {
        const store = usePlayerStore.getState()

        store.setRunning(true)
        store.setPaused(true)

        const state = usePlayerStore.getState()
        expect(state.isRunning).toBe(true)
        expect(state.isPaused).toBe(true)
      })
    })

    describe('resume', () => {
      it('should clear paused state', () => {
        const store = usePlayerStore.getState()

        store.setRunning(true)
        store.setPaused(true)
        store.setPaused(false)

        const state = usePlayerStore.getState()
        expect(state.isPaused).toBe(false)
      })
    })

    describe('stop', () => {
      it('should reset all state', () => {
        const store = usePlayerStore.getState()

        store.loadWorkout(mockWorkout)
        store.setRunning(true)
        store.addCalories(25)
        store.setPhase('WORK')
        store.reset()

        const state = usePlayerStore.getState()
        expect(state.isRunning).toBe(false)
        expect(state.phase).toBe('PREP')
        expect(state.calories).toBe(0)
      })
    })
  })

  describe('skip functionality', () => {
    it('should skip from PREP to WORK', () => {
      const store = usePlayerStore.getState()
      store.loadWorkout(mockWorkout)
      expect(store.phase).toBe('PREP')

      store.setPhase('WORK')
      store.setTimeRemaining(mockWorkout.workTime)

      const state = usePlayerStore.getState()
      expect(state.phase).toBe('WORK')
      expect(state.timeRemaining).toBe(mockWorkout.workTime)
    })

    it('should skip from WORK to REST', () => {
      const store = usePlayerStore.getState()
      store.loadWorkout(mockWorkout)
      store.setPhase('WORK')

      store.setPhase('REST')
      store.setTimeRemaining(mockWorkout.restTime)

      const state = usePlayerStore.getState()
      expect(state.phase).toBe('REST')
      expect(state.timeRemaining).toBe(mockWorkout.restTime)
    })

    it('should skip from REST to next WORK round', () => {
      const store = usePlayerStore.getState()
      store.loadWorkout(mockWorkout)
      store.setPhase('REST')
      store.setCurrentRound(1)

      store.setPhase('WORK')
      store.setTimeRemaining(mockWorkout.workTime)
      store.setCurrentRound(2)

      const state = usePlayerStore.getState()
      expect(state.phase).toBe('WORK')
      expect(state.currentRound).toBe(2)
    })

    it('should complete workout when skipping REST on final round', () => {
      const store = usePlayerStore.getState()
      store.loadWorkout(mockWorkout)
      store.setPhase('REST')
      store.setCurrentRound(mockWorkout.rounds)

      store.setPhase('COMPLETE')
      store.setRunning(false)

      const state = usePlayerStore.getState()
      expect(state.phase).toBe('COMPLETE')
      expect(state.isRunning).toBe(false)
    })
  })

  describe('timer tick simulation', () => {
    it('should decrement timeRemaining', () => {
      const store = usePlayerStore.getState()
      store.loadWorkout(mockWorkout)

      const initial = store.timeRemaining
      store.setTimeRemaining(initial - 1)

      expect(usePlayerStore.getState().timeRemaining).toBe(initial - 1)
    })

    it('should transition from PREP to WORK when time expires', () => {
      const store = usePlayerStore.getState()
      store.loadWorkout(mockWorkout)
      store.setRunning(true)

      store.setTimeRemaining(0)
      store.setPhase('WORK')
      store.setTimeRemaining(mockWorkout.workTime)

      const state = usePlayerStore.getState()
      expect(state.phase).toBe('WORK')
      expect(state.timeRemaining).toBe(mockWorkout.workTime)
    })

    it('should transition from WORK to REST and add calories', () => {
      const store = usePlayerStore.getState()
      store.loadWorkout(mockWorkout)
      store.setPhase('WORK')
      store.setRunning(true)

      store.setTimeRemaining(0)
      const caloriesPerRound = Math.round(mockWorkout.calories / mockWorkout.rounds)
      store.addCalories(caloriesPerRound)
      store.setPhase('REST')
      store.setTimeRemaining(mockWorkout.restTime)

      const state = usePlayerStore.getState()
      expect(state.phase).toBe('REST')
      expect(state.timeRemaining).toBe(mockWorkout.restTime)

      const expectedCalories = Math.round(mockWorkout.calories / mockWorkout.rounds)
      expect(state.calories).toBe(expectedCalories)
    })

    it('should transition from REST to WORK for next round', () => {
      const store = usePlayerStore.getState()
      store.loadWorkout(mockWorkout)
      store.setPhase('REST')
      store.setCurrentRound(1)
      store.setRunning(true)

      store.setTimeRemaining(0)
      store.setPhase('WORK')
      store.setTimeRemaining(mockWorkout.workTime)
      store.setCurrentRound(2)

      const state = usePlayerStore.getState()
      expect(state.phase).toBe('WORK')
      expect(state.currentRound).toBe(2)
    })

    it('should complete workout after final round', () => {
      const store = usePlayerStore.getState()
      store.loadWorkout(mockWorkout)
      store.setPhase('REST')
      store.setCurrentRound(mockWorkout.rounds)
      store.setRunning(true)

      store.setTimeRemaining(0)
      store.setPhase('COMPLETE')
      store.setTimeRemaining(0)
      store.setRunning(false)

      const state = usePlayerStore.getState()
      expect(state.phase).toBe('COMPLETE')
      expect(state.isRunning).toBe(false)
    })
  })

  describe('pause/resume behavior', () => {
    it('should not update timeRemaining when paused', () => {
      const store = usePlayerStore.getState()
      store.loadWorkout(mockWorkout)
      store.setRunning(true)
      store.setPaused(true)

      const pausedTime = store.timeRemaining

      vi.advanceTimersByTime(5000)

      expect(usePlayerStore.getState().timeRemaining).toBe(pausedTime)
    })

    it('should resume timer when resumed', () => {
      const store = usePlayerStore.getState()
      store.loadWorkout(mockWorkout)
      store.setRunning(true)
      store.setPaused(false)

      expect(store.isPaused).toBe(false)
    })
  })

  describe('isComplete flag', () => {
    it('should be false initially', () => {
      const store = usePlayerStore.getState()
      expect(store.phase === 'COMPLETE').toBe(false)
    })

    it('should be true when phase is COMPLETE', () => {
      const store = usePlayerStore.getState()
      store.setPhase('COMPLETE')

      expect(usePlayerStore.getState().phase === 'COMPLETE').toBe(true)
    })
  })

  describe('nextExercise', () => {
    it('should return next exercise during REST phase', () => {
      const nextExercise = getNextExercise(1, mockWorkout.exercises)
      expect(nextExercise).toBe('Squats')
    })

    it('should cycle to first exercise after last', () => {
      const nextExercise = getNextExercise(4, mockWorkout.exercises)
      expect(nextExercise).toBe('Jumping Jacks')
    })
  })

  describe('calorie tracking', () => {
    it('should accumulate calories for each WORK phase completed', () => {
      const store = usePlayerStore.getState()
      const caloriesPerRound = Math.round(mockWorkout.calories / mockWorkout.rounds)

      store.addCalories(caloriesPerRound)
      expect(usePlayerStore.getState().calories).toBe(caloriesPerRound)

      store.addCalories(caloriesPerRound)
      expect(usePlayerStore.getState().calories).toBe(caloriesPerRound * 2)
    })
  })

  describe('startedAt tracking', () => {
    it('should set startedAt when first running', () => {
      const store = usePlayerStore.getState()
      const beforeSet = Date.now()

      store.setRunning(true)

      const state = usePlayerStore.getState()
      expect(state.startedAt).toBeGreaterThanOrEqual(beforeSet)
    })

    it('should not update startedAt if already set', () => {
      const store = usePlayerStore.getState()

      store.setRunning(true)
      const firstStartedAt = usePlayerStore.getState().startedAt

      store.setRunning(false)
      store.setRunning(true)

      expect(usePlayerStore.getState().startedAt).toBe(firstStartedAt)
    })
  })
})
