import { describe, it, expect, beforeEach } from 'vitest'
import { useTabataProgramStore } from '../../shared/stores/tabataProgramStore'
import type { TabataProgramId } from '../../shared/types/program'

const PROGRAM_IDS: TabataProgramId[] = ['debutant', 'intermediaire', 'avance', 'bureau']

const resetStore = () => {
  const initial: Record<TabataProgramId, any> = {} as any
  for (const id of PROGRAM_IDS) {
    initial[id] = {
      programId: id,
      currentWeek: 1,
      currentSessionIndex: 0,
      completedSessionIds: [],
      isProgramCompleted: false,
      startDate: undefined,
      lastSessionDate: undefined,
    }
  }
  useTabataProgramStore.setState({
    selectedProgramId: null,
    programsProgress: initial,
  })
}

describe('tabataProgramStore', () => {
  beforeEach(() => {
    resetStore()
  })

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

    it('should have initial progress for all 4 programs', () => {
      const progress = useTabataProgramStore.getState().programsProgress
      for (const id of PROGRAM_IDS) {
        expect(progress[id]).toBeDefined()
        expect(progress[id].completedSessionIds).toEqual([])
        expect(progress[id].isProgramCompleted).toBe(false)
        expect(progress[id].currentWeek).toBe(1)
        expect(progress[id].currentSessionIndex).toBe(0)
      }
    })
  })

  describe('selectProgram', () => {
    it('should set selectedProgramId and startDate on first selection', () => {
      useTabataProgramStore.getState().selectProgram('debutant')
      const state = useTabataProgramStore.getState()
      expect(state.selectedProgramId).toBe('debutant')
      expect(state.programsProgress.debutant.startDate).toBeDefined()
    })

    it('should not overwrite startDate on re-selection after progress', () => {
      useTabataProgramStore.getState().selectProgram('debutant')
      const firstDate = useTabataProgramStore.getState().programsProgress.debutant.startDate

      // Simulate some progress
      useTabataProgramStore.setState(s => ({
        programsProgress: {
          ...s.programsProgress,
          debutant: {
            ...s.programsProgress.debutant,
            completedSessionIds: ['deb-w1-s1'],
          },
        },
      }))

      // Re-select
      useTabataProgramStore.getState().selectProgram('debutant')
      expect(useTabataProgramStore.getState().selectedProgramId).toBe('debutant')
      // startDate should remain unchanged
      expect(useTabataProgramStore.getState().programsProgress.debutant.startDate).toBe(firstDate)
    })
  })

  describe('completeSession', () => {
    it('should add session ID to completed list', () => {
      useTabataProgramStore.getState().completeSession('debutant', 'deb-w1-s1')
      const progress = useTabataProgramStore.getState().programsProgress.debutant
      expect(progress.completedSessionIds).toContain('deb-w1-s1')
      expect(progress.lastSessionDate).toBeDefined()
    })

    it('should not duplicate session IDs', () => {
      useTabataProgramStore.getState().completeSession('debutant', 'deb-w1-s1')
      useTabataProgramStore.getState().completeSession('debutant', 'deb-w1-s1')
      const progress = useTabataProgramStore.getState().programsProgress.debutant
      expect(progress.completedSessionIds.filter(id => id === 'deb-w1-s1')).toHaveLength(1)
    })

    it('should advance session index within same week', () => {
      useTabataProgramStore.getState().completeSession('debutant', 'deb-w1-s1')
      const progress = useTabataProgramStore.getState().programsProgress.debutant
      expect(progress.currentSessionIndex).toBe(1)
      expect(progress.currentWeek).toBe(1)
    })

    it('should ignore unknown program IDs', () => {
      // Should not throw
      useTabataProgramStore.getState().completeSession('nonexistent' as TabataProgramId, 'x')
      // State unchanged
      expect(useTabataProgramStore.getState().programsProgress.debutant.completedSessionIds).toEqual([])
    })
  })

  describe('resetProgram', () => {
    it('should reset progress to initial state', () => {
      useTabataProgramStore.getState().selectProgram('debutant')
      useTabataProgramStore.getState().completeSession('debutant', 'deb-w1-s1')
      useTabataProgramStore.getState().resetProgram('debutant')

      const state = useTabataProgramStore.getState()
      expect(state.selectedProgramId).toBeNull()
      expect(state.programsProgress.debutant.completedSessionIds).toEqual([])
      expect(state.programsProgress.debutant.startDate).toBeUndefined()
    })

    it('should not affect other programs when resetting one', () => {
      useTabataProgramStore.getState().selectProgram('intermediaire')
      useTabataProgramStore.getState().completeSession('intermediaire', 'int-w1-s1')
      useTabataProgramStore.getState().resetProgram('debutant')

      const state = useTabataProgramStore.getState()
      expect(state.selectedProgramId).toBe('intermediaire')
      expect(state.programsProgress.intermediaire.completedSessionIds).toContain('int-w1-s1')
    })
  })

  describe('changeProgram', () => {
    it('should change selected program without resetting progress', () => {
      useTabataProgramStore.getState().selectProgram('debutant')
      useTabataProgramStore.getState().completeSession('debutant', 'deb-w1-s1')
      useTabataProgramStore.getState().changeProgram('intermediaire')

      const state = useTabataProgramStore.getState()
      expect(state.selectedProgramId).toBe('intermediaire')
      expect(state.programsProgress.debutant.completedSessionIds).toContain('deb-w1-s1')
    })
  })

  describe('getters', () => {
    it('getCurrentSession should return first session initially', () => {
      const session = useTabataProgramStore.getState().getCurrentSession('debutant')
      expect(session).not.toBeNull()
      expect(session?.id).toMatch(/^deb-/)
    })

    it('getCurrentSession should return null for unknown program', () => {
      const session = useTabataProgramStore.getState().getCurrentSession('nonexistent' as TabataProgramId)
      expect(session).toBeNull()
    })

    it('getProgramCompletion should return 0 initially', () => {
      expect(useTabataProgramStore.getState().getProgramCompletion('debutant')).toBe(0)
    })

    it('getTotalSessionsCompleted should sum across all programs', () => {
      useTabataProgramStore.getState().completeSession('debutant', 'deb-w1-s1')
      useTabataProgramStore.getState().completeSession('intermediaire', 'int-w1-s1')
      expect(useTabataProgramStore.getState().getTotalSessionsCompleted()).toBe(2)
    })

    it('getProgramStatus should return not-started initially', () => {
      expect(useTabataProgramStore.getState().getProgramStatus('debutant')).toBe('not-started')
    })

    it('getProgramStatus should return in-progress after completing a session', () => {
      useTabataProgramStore.getState().completeSession('debutant', 'deb-w1-s1')
      expect(useTabataProgramStore.getState().getProgramStatus('debutant')).toBe('in-progress')
    })

    it('isWeekUnlocked should return true for week 1', () => {
      expect(useTabataProgramStore.getState().isWeekUnlocked('debutant', 1)).toBe(true)
    })

    it('isWeekUnlocked should return false for week 2 initially', () => {
      expect(useTabataProgramStore.getState().isWeekUnlocked('debutant', 2)).toBe(false)
    })

    it('getProgram should return program data', () => {
      const program = useTabataProgramStore.getState().getProgram('debutant')
      expect(program).toBeDefined()
      expect(program?.id).toBe('debutant')
    })

    it('getRecommendedNext should return current session of selected program', () => {
      useTabataProgramStore.getState().selectProgram('debutant')
      const rec = useTabataProgramStore.getState().getRecommendedNext()
      expect(rec).not.toBeNull()
      expect(rec?.programId).toBe('debutant')
    })

    it('getRecommendedNext should return null when no programs started', () => {
      const rec = useTabataProgramStore.getState().getRecommendedNext()
      expect(rec).toBeNull()
    })
  })
})
