import { describe, it, expect, beforeEach } from 'vitest'
import { useProgramStore } from '../../shared/stores/programStore'
import type { ProgramId, AssessmentResult } from '../../shared/types/program'

const resetAllPrograms = () => {
  useProgramStore.setState({
    selectedProgramId: null,
    programsProgress: {
      'upper-body': {
        programId: 'upper-body',
        currentWeek: 1,
        currentWorkoutIndex: 0,
        completedWorkoutIds: [],
        isProgramCompleted: false,
        startDate: undefined,
        lastWorkoutDate: undefined,
      },
      'lower-body': {
        programId: 'lower-body',
        currentWeek: 1,
        currentWorkoutIndex: 0,
        completedWorkoutIds: [],
        isProgramCompleted: false,
        startDate: undefined,
        lastWorkoutDate: undefined,
      },
      'full-body': {
        programId: 'full-body',
        currentWeek: 1,
        currentWorkoutIndex: 0,
        completedWorkoutIds: [],
        isProgramCompleted: false,
        startDate: undefined,
        lastWorkoutDate: undefined,
      },
    },
    assessment: {
      isCompleted: false,
      result: null,
    },
  })
}

describe('programStore', () => {
  beforeEach(() => {
    resetAllPrograms()
  })

  describe('initial state', () => {
    it('should have no selected program', () => {
      expect(useProgramStore.getState().selectedProgramId).toBeNull()
    })

    it('should have initial progress for all programs', () => {
      const progress = useProgramStore.getState().programsProgress
      
      expect(progress['upper-body']).toBeDefined()
      expect(progress['lower-body']).toBeDefined()
      expect(progress['full-body']).toBeDefined()
    })

    it('should have incomplete assessment', () => {
      const assessment = useProgramStore.getState().assessment
      
      expect(assessment.isCompleted).toBe(false)
      expect(assessment.result).toBeNull()
    })
  })

  describe('selectProgram', () => {
    it('should set selected program', () => {
      const store = useProgramStore.getState()
      
      store.selectProgram('upper-body')
      
      expect(useProgramStore.getState().selectedProgramId).toBe('upper-body')
    })

    it('should set start date when selecting program for first time', () => {
      const store = useProgramStore.getState()
      
      store.selectProgram('upper-body')
      
      const progress = useProgramStore.getState().programsProgress['upper-body']
      expect(progress.startDate).toBeDefined()
    })
  })

  describe('completeWorkout', () => {
    it('should add workout to completed list', () => {
      const store = useProgramStore.getState()
      store.selectProgram('upper-body')
      
      store.completeWorkout('upper-body', 'ub-w1-d1')
      
      const progress = useProgramStore.getState().programsProgress['upper-body']
      expect(progress.completedWorkoutIds).toContain('ub-w1-d1')
    })

    it('should advance workout index', () => {
      const store = useProgramStore.getState()
      store.selectProgram('upper-body')
      
      store.completeWorkout('upper-body', 'ub-w1-d1')
      
      const progress = useProgramStore.getState().programsProgress['upper-body']
      expect(progress.currentWorkoutIndex).toBe(1)
    })

    it('should advance week after completing last workout of week', () => {
      const store = useProgramStore.getState()
      store.selectProgram('upper-body')
      
      const progress = useProgramStore.getState().programsProgress['upper-body']
      progress.currentWorkoutIndex = 4
      progress.currentWeek = 1
      
      store.completeWorkout('upper-body', 'ub-w1-d5')
      
      const updated = useProgramStore.getState().programsProgress['upper-body']
      expect(updated.currentWeek).toBe(2)
      expect(updated.currentWorkoutIndex).toBe(0)
    })

    it('should set lastWorkoutDate', () => {
      const store = useProgramStore.getState()
      store.selectProgram('upper-body')
      
      store.completeWorkout('upper-body', 'ub-w1-d1')
      
      const progress = useProgramStore.getState().programsProgress['upper-body']
      expect(progress.lastWorkoutDate).toBeDefined()
    })
  })

  describe('completeAssessment', () => {
    it('should mark assessment as completed', () => {
      const store = useProgramStore.getState()
      const result: AssessmentResult = {
        completedAt: new Date().toISOString(),
        exercisesCompleted: ['exercise-1', 'exercise-2'],
        recommendedProgram: 'upper-body',
      }
      
      store.completeAssessment(result)
      
      const assessment = useProgramStore.getState().assessment
      expect(assessment.isCompleted).toBe(true)
      expect(assessment.result).toEqual(result)
    })
  })

  describe('skipAssessment', () => {
    it('should mark assessment as completed without result', () => {
      const store = useProgramStore.getState()
      
      store.skipAssessment()
      
      const assessment = useProgramStore.getState().assessment
      expect(assessment.isCompleted).toBe(true)
      expect(assessment.result).toBeNull()
    })
  })

  describe('resetProgram', () => {
    it('should reset program progress', () => {
      const store = useProgramStore.getState()
      store.selectProgram('upper-body')
      store.completeWorkout('upper-body', 'ub-w1-d1')
      
      store.resetProgram('upper-body')
      
      const progress = useProgramStore.getState().programsProgress['upper-body']
      expect(progress.completedWorkoutIds).toEqual([])
      expect(progress.currentWeek).toBe(1)
      expect(progress.currentWorkoutIndex).toBe(0)
    })

    it('should deselect program if it was the selected one', () => {
      const store = useProgramStore.getState()
      store.selectProgram('upper-body')
      
      store.resetProgram('upper-body')
      
      expect(useProgramStore.getState().selectedProgramId).toBeNull()
    })
  })

  describe('changeProgram', () => {
    it('should change to different program', () => {
      const store = useProgramStore.getState()
      store.selectProgram('upper-body')
      
      store.changeProgram('lower-body')
      
      expect(useProgramStore.getState().selectedProgramId).toBe('lower-body')
    })
  })

  describe('getCurrentWorkout', () => {
    it('should return null when no program selected', () => {
      const store = useProgramStore.getState()
      
      const workout = store.getCurrentWorkout('upper-body')
      expect(workout).toBeDefined()
    })
  })

  describe('getProgramCompletion', () => {
    it('should return 0 for new program', () => {
      const store = useProgramStore.getState()
      
      const completion = store.getProgramCompletion('upper-body')
      expect(completion).toBe(0)
    })

    it('should calculate completion percentage', () => {
      const store = useProgramStore.getState()
      store.selectProgram('upper-body')
      
      for (let i = 1; i <= 10; i++) {
        store.completeWorkout('upper-body', `ub-w1-d${i}`)
      }
      
      const completion = useProgramStore.getState().getProgramCompletion('upper-body')
      expect(completion).toBe(50)
    })
  })

  describe('getTotalWorkoutsCompleted', () => {
    it('should return 0 when no workouts completed', () => {
      const store = useProgramStore.getState()
      
      expect(store.getTotalWorkoutsCompleted()).toBe(0)
    })

    it('should count workouts across all programs', () => {
      const store = useProgramStore.getState()
      
      const ubProgress = useProgramStore.getState().programsProgress['upper-body']
      ubProgress.completedWorkoutIds = ['w1', 'w2']
      
      const lbProgress = useProgramStore.getState().programsProgress['lower-body']
      lbProgress.completedWorkoutIds = ['w3']
      
      expect(store.getTotalWorkoutsCompleted()).toBe(3)
    })
  })

  describe('getProgramStatus', () => {
    it('should return not-started for new program', () => {
      const store = useProgramStore.getState()
      
      expect(store.getProgramStatus('upper-body')).toBe('not-started')
    })

    it('should return in-progress after completing workout', () => {
      const store = useProgramStore.getState()
      store.selectProgram('upper-body')
      store.completeWorkout('upper-body', 'ub-w1-d1')
      
      expect(useProgramStore.getState().getProgramStatus('upper-body')).toBe('in-progress')
    })

    it('should return completed after all 20 workouts', () => {
      const store = useProgramStore.getState()
      const progress = useProgramStore.getState().programsProgress['upper-body']
      progress.completedWorkoutIds = Array.from({ length: 20 }, (_, i) => `w${i + 1}`)
      progress.isProgramCompleted = true
      
      expect(store.getProgramStatus('upper-body')).toBe('completed')
    })
  })

  describe('isWeekUnlocked', () => {
    it('should always unlock week 1', () => {
      const store = useProgramStore.getState()
      
      expect(store.isWeekUnlocked('upper-body', 1)).toBe(true)
    })

    it('should lock week 2 before completing week 1', () => {
      const store = useProgramStore.getState()
      
      expect(store.isWeekUnlocked('upper-body', 2)).toBe(false)
    })
  })

  describe('getRecommendedNextWorkout', () => {
    it('should return null when no programs started', () => {
      const store = useProgramStore.getState()
      
      expect(store.getRecommendedNextWorkout()).toBeNull()
    })

    it('should return current workout from selected program', () => {
      const store = useProgramStore.getState()
      store.selectProgram('upper-body')
      
      const recommendation = store.getRecommendedNextWorkout()
      
      expect(recommendation).not.toBeNull()
      expect(recommendation?.programId).toBe('upper-body')
    })

    it('should find in-progress program when no program selected', () => {
      const store = useProgramStore.getState()
      const progress = useProgramStore.getState().programsProgress['lower-body']
      progress.completedWorkoutIds = ['lb-w1-d1']
      
      const recommendation = store.getRecommendedNextWorkout()
      
      expect(recommendation).not.toBeNull()
      expect(recommendation?.programId).toBe('lower-body')
    })
  })

  describe('getNextWorkout', () => {
    it('should return next workout in same week', () => {
      const store = useProgramStore.getState()
      store.selectProgram('upper-body')
      
      const nextWorkout = store.getNextWorkout('upper-body')
      
      expect(nextWorkout).not.toBeNull()
      expect(nextWorkout?.id).toBe('ub-w1-d2')
    })

    it('should return first workout of next week when at end of week', () => {
      const store = useProgramStore.getState()
      const progress = useProgramStore.getState().programsProgress['upper-body']
      progress.currentWeek = 1
      progress.currentWorkoutIndex = 4
      
      const nextWorkout = store.getNextWorkout('upper-body')
      
      expect(nextWorkout).not.toBeNull()
      expect(nextWorkout?.id).toBe('ub-w2-d1')
    })

    it('should return null when at end of program', () => {
      const store = useProgramStore.getState()
      const progress = useProgramStore.getState().programsProgress['upper-body']
      progress.currentWeek = 4
      progress.currentWorkoutIndex = 4
      
      const nextWorkout = store.getNextWorkout('upper-body')
      
      expect(nextWorkout).toBeNull()
    })
  })

  describe('isWorkoutUnlocked', () => {
    it('should unlock first workout of week 1', () => {
      const store = useProgramStore.getState()
      
      expect(store.isWorkoutUnlocked('upper-body', 'ub-w1-d1')).toBe(true)
    })

    it('should lock later workouts in week 1', () => {
      const store = useProgramStore.getState()
      
      expect(store.isWorkoutUnlocked('upper-body', 'ub-w1-d2')).toBe(false)
      expect(store.isWorkoutUnlocked('upper-body', 'ub-w1-d5')).toBe(false)
    })

    it('should unlock workout after completing previous', () => {
      const store = useProgramStore.getState()
      store.selectProgram('upper-body')
      store.completeWorkout('upper-body', 'ub-w1-d1')
      
      expect(useProgramStore.getState().isWorkoutUnlocked('upper-body', 'ub-w1-d2')).toBe(true)
    })

    it('should lock week 2 workouts before completing week 1', () => {
      const store = useProgramStore.getState()
      
      expect(store.isWorkoutUnlocked('upper-body', 'ub-w2-d1')).toBe(false)
    })
  })

  describe('program completion', () => {
    it('should mark program as completed after 20 workouts', () => {
      const store = useProgramStore.getState()
      store.selectProgram('upper-body')
      
      for (let week = 1; week <= 4; week++) {
        for (let day = 1; day <= 5; day++) {
          store.completeWorkout('upper-body', `ub-w${week}-d${day}`)
        }
      }
      
      const progress = useProgramStore.getState().programsProgress['upper-body']
      expect(progress.isProgramCompleted).toBe(true)
      expect(progress.completedWorkoutIds).toHaveLength(20)
    })
  })
})
