import { describe, it, expect, beforeEach, vi } from 'vitest'
import { mount } from '@vue/test-utils'
import DiceRoller from '~/components/Gameplay/DiceRoller.vue'
import DiceShapes from '~/components/Gameplay/DiceShapes.vue'
import type { RollData } from '~/types'

describe('DiceRoller', () => {
  const createRollData = (overrides = {}): RollData => ({
    roll_id: 'test-roll-123',
    d6_one: 3,
    d6_two_a: 2,
    d6_two_b: 4,
    d6_two_total: 6,
    chaos_d20: 15,
    resolution_d20: 8,
    check_wild_pitch: false,
    check_passed_ball: false,
    chaos_check_skipped: false,
    timestamp: '2025-01-13T12:00:00Z',
    ...overrides,
  })

  const defaultProps = {
    canRoll: true,
    pendingRoll: null as RollData | null,
    diceColor: 'cc0000', // Default red
  }

  beforeEach(() => {
    vi.clearAllTimers()
    vi.useFakeTimers()
  })

  // ============================================================================
  // Rendering Tests
  // ============================================================================

  describe('Rendering', () => {
    it('renders roll button when no pending roll', () => {
      const wrapper = mount(DiceRoller, {
        props: defaultProps,
      })

      expect(wrapper.find('.roll-button').exists()).toBe(true)
      expect(wrapper.text()).toContain('Roll Dice')
      expect(wrapper.find('.dice-results').exists()).toBe(false)
    })

    it('renders dice results when pending roll exists', () => {
      const rollData = createRollData()
      const wrapper = mount(DiceRoller, {
        props: {
          ...defaultProps,
          canRoll: false,
          pendingRoll: rollData,
        },
      })

      expect(wrapper.find('.roll-button').exists()).toBe(false)
      expect(wrapper.find('.dice-results').exists()).toBe(true)
      expect(wrapper.text()).toContain('Dice Results')
    })

    it('displays three dice when no WP/PB check triggered (chaos d20 hidden)', () => {
      /**
       * When chaos d20 doesn't trigger WP (1) or PB (2), it's hidden since values 3-20
       * have no game effect. This reduces visual noise in the dice display.
       */
      const rollData = createRollData({
        d6_one: 5,
        d6_two_total: 8,
        chaos_d20: 17, // Not 1 or 2, so no check triggered
        resolution_d20: 3,
        check_wild_pitch: false,
        check_passed_ball: false,
      })

      const wrapper = mount(DiceRoller, {
        props: {
          ...defaultProps,
          canRoll: false,
          pendingRoll: rollData,
        },
      })

      // DiceShapes components are rendered for each die
      const diceComponents = wrapper.findAllComponents(DiceShapes)
      expect(diceComponents).toHaveLength(3) // chaos d20 hidden when no check triggered
    })

    it('displays all four dice when wild pitch check triggered', () => {
      /**
       * Chaos d20 is shown when it triggers a Wild Pitch check (value == 1),
       * since this affects gameplay and the user needs to see the dice value.
       */
      const rollData = createRollData({
        d6_one: 5,
        d6_two_total: 8,
        chaos_d20: 1, // Triggers WP check
        resolution_d20: 3,
        check_wild_pitch: true,
        check_passed_ball: false,
      })

      const wrapper = mount(DiceRoller, {
        props: {
          ...defaultProps,
          canRoll: false,
          pendingRoll: rollData,
        },
      })

      const diceComponents = wrapper.findAllComponents(DiceShapes)
      expect(diceComponents).toHaveLength(4) // chaos d20 shown for WP check
    })

    it('displays all four dice when passed ball check triggered', () => {
      /**
       * Chaos d20 is shown when it triggers a Passed Ball check (value == 2),
       * since this affects gameplay and the user needs to see the dice value.
       */
      const rollData = createRollData({
        d6_one: 5,
        d6_two_total: 8,
        chaos_d20: 2, // Triggers PB check
        resolution_d20: 3,
        check_wild_pitch: false,
        check_passed_ball: true,
      })

      const wrapper = mount(DiceRoller, {
        props: {
          ...defaultProps,
          canRoll: false,
          pendingRoll: rollData,
        },
      })

      const diceComponents = wrapper.findAllComponents(DiceShapes)
      expect(diceComponents).toHaveLength(4) // chaos d20 shown for PB check
    })

    it('displays d6_two component dice values', () => {
      const rollData = createRollData({
        d6_two_a: 3,
        d6_two_b: 5,
        d6_two_total: 8,
      })

      const wrapper = mount(DiceRoller, {
        props: {
          ...defaultProps,
          canRoll: false,
          pendingRoll: rollData,
        },
      })

      expect(wrapper.text()).toContain('(3 + 5)')
    })
  })

  // ============================================================================
  // Button State Tests
  // ============================================================================

  describe('Button States', () => {
    it('enables button when canRoll is true', () => {
      const wrapper = mount(DiceRoller, {
        props: defaultProps,
      })

      const button = wrapper.find('.roll-button')
      expect(button.classes()).toContain('roll-button-enabled')
      expect(button.classes()).not.toContain('roll-button-disabled')
      expect(button.attributes('disabled')).toBeUndefined()
    })

    it('disables button when canRoll is false', () => {
      const wrapper = mount(DiceRoller, {
        props: {
          ...defaultProps,
          canRoll: false,
        },
      })

      const button = wrapper.find('.roll-button')
      expect(button.classes()).toContain('roll-button-disabled')
      expect(button.classes()).not.toContain('roll-button-enabled')
      expect(button.attributes('disabled')).toBeDefined()
    })

    it('shows loading state when rolling', async () => {
      const wrapper = mount(DiceRoller, {
        props: defaultProps,
      })

      await wrapper.find('.roll-button').trigger('click')

      expect(wrapper.text()).toContain('Rolling...')
      expect(wrapper.find('.animate-spin').exists()).toBe(true)
    })

    it('disables button during rolling animation', async () => {
      const wrapper = mount(DiceRoller, {
        props: defaultProps,
      })

      await wrapper.find('.roll-button').trigger('click')
      const button = wrapper.find('.roll-button')

      expect(button.attributes('disabled')).toBeDefined()
    })

    it('resets rolling state after timeout', async () => {
      const wrapper = mount(DiceRoller, {
        props: defaultProps,
      })

      await wrapper.find('.roll-button').trigger('click')
      expect(wrapper.text()).toContain('Rolling...')

      vi.advanceTimersByTime(2000)
      await wrapper.vm.$nextTick()

      expect(wrapper.text()).not.toContain('Rolling...')
    })
  })

  // ============================================================================
  // Event Emission Tests
  // ============================================================================

  describe('Event Emission', () => {
    it('emits roll event when button clicked', async () => {
      const wrapper = mount(DiceRoller, {
        props: defaultProps,
      })

      await wrapper.find('.roll-button').trigger('click')

      expect(wrapper.emitted('roll')).toBeTruthy()
      expect(wrapper.emitted('roll')).toHaveLength(1)
    })

    it('does not emit roll when canRoll is false', async () => {
      const wrapper = mount(DiceRoller, {
        props: {
          ...defaultProps,
          canRoll: false,
        },
      })

      await wrapper.find('.roll-button').trigger('click')

      expect(wrapper.emitted('roll')).toBeFalsy()
    })

    it('does not emit roll during rolling animation', async () => {
      const wrapper = mount(DiceRoller, {
        props: defaultProps,
      })

      await wrapper.find('.roll-button').trigger('click')
      await wrapper.find('.roll-button').trigger('click')

      expect(wrapper.emitted('roll')).toHaveLength(1)
    })
  })

  // ============================================================================
  // Special Events Tests
  // ============================================================================

  describe('Special Events', () => {
    it('shows wild pitch indicator when check_wild_pitch is true', () => {
      const rollData = createRollData({ check_wild_pitch: true })
      const wrapper = mount(DiceRoller, {
        props: {
          ...defaultProps,
          canRoll: false,
          pendingRoll: rollData,
        },
      })

      expect(wrapper.find('.wild-pitch').exists()).toBe(true)
      expect(wrapper.text()).toContain('Wild Pitch Check')
    })

    it('shows passed ball indicator when check_passed_ball is true', () => {
      const rollData = createRollData({ check_passed_ball: true })
      const wrapper = mount(DiceRoller, {
        props: {
          ...defaultProps,
          canRoll: false,
          pendingRoll: rollData,
        },
      })

      expect(wrapper.find('.passed-ball').exists()).toBe(true)
      expect(wrapper.text()).toContain('Passed Ball Check')
    })

    it('shows both indicators when both checks are true', () => {
      const rollData = createRollData({
        check_wild_pitch: true,
        check_passed_ball: true,
      })

      const wrapper = mount(DiceRoller, {
        props: {
          ...defaultProps,
          canRoll: false,
          pendingRoll: rollData,
        },
      })

      expect(wrapper.find('.wild-pitch').exists()).toBe(true)
      expect(wrapper.find('.passed-ball').exists()).toBe(true)
    })

    it('hides special events section when no checks are true', () => {
      const rollData = createRollData({
        check_wild_pitch: false,
        check_passed_ball: false,
      })

      const wrapper = mount(DiceRoller, {
        props: {
          ...defaultProps,
          canRoll: false,
          pendingRoll: rollData,
        },
      })

      expect(wrapper.find('.special-events').exists()).toBe(false)
    })
  })

  // ============================================================================
  // Chaos d20 Conditional Display Tests
  // ============================================================================

  describe('Chaos d20 Conditional Display', () => {
    /**
     * The chaos d20 dice is only displayed when it triggers a Wild Pitch (1)
     * or Passed Ball (2) check. Values 3-20 have no game effect and showing
     * them creates visual noise. When bases are empty, the chaos check is
     * skipped entirely since WP/PB is meaningless without runners.
     */

    it('hides chaos d20 when no check triggered (values 3-20)', () => {
      const rollData = createRollData({
        chaos_d20: 15,
        check_wild_pitch: false,
        check_passed_ball: false,
        chaos_check_skipped: false, // Runners on base, but roll was 3-20
      })

      const wrapper = mount(DiceRoller, {
        props: {
          ...defaultProps,
          canRoll: false,
          pendingRoll: rollData,
        },
      })

      const chaosItem = wrapper.find('.dice-chaos')
      expect(chaosItem.exists()).toBe(false)
    })

    it('hides chaos d20 when chaos check was skipped (bases empty)', () => {
      const rollData = createRollData({
        chaos_d20: 1, // Would trigger WP but bases empty
        check_wild_pitch: false, // Skipped due to no runners
        check_passed_ball: false,
        chaos_check_skipped: true, // No runners on base
      })

      const wrapper = mount(DiceRoller, {
        props: {
          ...defaultProps,
          canRoll: false,
          pendingRoll: rollData,
        },
      })

      const chaosItem = wrapper.find('.dice-chaos')
      expect(chaosItem.exists()).toBe(false)
    })

    it('shows chaos d20 when wild pitch check triggered', () => {
      const rollData = createRollData({
        chaos_d20: 1,
        check_wild_pitch: true,
        check_passed_ball: false,
        chaos_check_skipped: false,
      })

      const wrapper = mount(DiceRoller, {
        props: {
          ...defaultProps,
          canRoll: false,
          pendingRoll: rollData,
        },
      })

      const chaosItem = wrapper.find('.dice-chaos')
      expect(chaosItem.exists()).toBe(true)
    })

    it('shows chaos d20 when passed ball check triggered', () => {
      const rollData = createRollData({
        chaos_d20: 2,
        check_wild_pitch: false,
        check_passed_ball: true,
        chaos_check_skipped: false,
      })

      const wrapper = mount(DiceRoller, {
        props: {
          ...defaultProps,
          canRoll: false,
          pendingRoll: rollData,
        },
      })

      const chaosItem = wrapper.find('.dice-chaos')
      expect(chaosItem.exists()).toBe(true)
    })

    it('displays correct chaos d20 value when shown', () => {
      const rollData = createRollData({
        chaos_d20: 1,
        check_wild_pitch: true,
        check_passed_ball: false,
      })

      const wrapper = mount(DiceRoller, {
        props: {
          ...defaultProps,
          canRoll: false,
          pendingRoll: rollData,
        },
      })

      const diceComponents = wrapper.findAllComponents(DiceShapes)
      // Find the chaos d20 component (3rd one when WP/PB triggered)
      const chaosDie = diceComponents[2]
      expect(chaosDie.props('value')).toBe(1)
    })
  })

  // ============================================================================
  // Timestamp Formatting Tests
  // ============================================================================

  describe('Timestamp Formatting', () => {
    it('formats timestamp correctly', () => {
      const rollData = createRollData({
        timestamp: '2025-01-13T14:30:45Z',
      })

      const wrapper = mount(DiceRoller, {
        props: {
          ...defaultProps,
          canRoll: false,
          pendingRoll: rollData,
        },
      })

      const timestampText = wrapper.find('.dice-header .text-sm').text()
      expect(timestampText).toMatch(/\d{1,2}:\d{2}:\d{2}/)
    })
  })

  // ============================================================================
  // Dice Color Tests
  // ============================================================================

  describe('Dice Color', () => {
    it('passes dice color to d6 DiceShapes components', () => {
      const rollData = createRollData()
      const wrapper = mount(DiceRoller, {
        props: {
          ...defaultProps,
          canRoll: false,
          pendingRoll: rollData,
          diceColor: '0066ff', // Blue
        },
      })

      const diceComponents = wrapper.findAllComponents(DiceShapes)
      // First two are d6 dice
      expect(diceComponents[0].props('color')).toBe('0066ff')
      expect(diceComponents[1].props('color')).toBe('0066ff')
    })

    it('uses white for resolution d20', () => {
      const rollData = createRollData()
      const wrapper = mount(DiceRoller, {
        props: {
          ...defaultProps,
          canRoll: false,
          pendingRoll: rollData,
        },
      })

      const diceComponents = wrapper.findAllComponents(DiceShapes)
      // Last one is resolution d20 (when no chaos shown)
      const resolutionD20 = diceComponents[diceComponents.length - 1]
      expect(resolutionD20.props('color')).toBe('ffffff')
    })

    it('uses amber for chaos d20 when shown', () => {
      const rollData = createRollData({
        check_wild_pitch: true,
        chaos_d20: 1,
      })

      const wrapper = mount(DiceRoller, {
        props: {
          ...defaultProps,
          canRoll: false,
          pendingRoll: rollData,
        },
      })

      const diceComponents = wrapper.findAllComponents(DiceShapes)
      // Third one is chaos d20 when WP triggered
      const chaosD20 = diceComponents[2]
      expect(chaosD20.props('color')).toBe('f59e0b')
    })

    it('uses default red when no diceColor prop provided', () => {
      const rollData = createRollData()
      const wrapper = mount(DiceRoller, {
        props: {
          canRoll: false,
          pendingRoll: rollData,
          // No diceColor prop
        },
      })

      const diceComponents = wrapper.findAllComponents(DiceShapes)
      expect(diceComponents[0].props('color')).toBe('cc0000')
    })
  })

  // ============================================================================
  // Dice Type Tests
  // ============================================================================

  describe('Dice Types', () => {
    it('renders d6 type for first two dice', () => {
      const rollData = createRollData()
      const wrapper = mount(DiceRoller, {
        props: {
          ...defaultProps,
          canRoll: false,
          pendingRoll: rollData,
        },
      })

      const diceComponents = wrapper.findAllComponents(DiceShapes)
      expect(diceComponents[0].props('type')).toBe('d6')
      expect(diceComponents[1].props('type')).toBe('d6')
    })

    it('renders d20 type for resolution die', () => {
      const rollData = createRollData()
      const wrapper = mount(DiceRoller, {
        props: {
          ...defaultProps,
          canRoll: false,
          pendingRoll: rollData,
        },
      })

      const diceComponents = wrapper.findAllComponents(DiceShapes)
      // Last one is resolution d20
      const resolutionD20 = diceComponents[diceComponents.length - 1]
      expect(resolutionD20.props('type')).toBe('d20')
    })

    it('renders d20 type for chaos die when shown', () => {
      const rollData = createRollData({
        check_wild_pitch: true,
        chaos_d20: 1,
      })

      const wrapper = mount(DiceRoller, {
        props: {
          ...defaultProps,
          canRoll: false,
          pendingRoll: rollData,
        },
      })

      const diceComponents = wrapper.findAllComponents(DiceShapes)
      // Third one is chaos d20
      expect(diceComponents[2].props('type')).toBe('d20')
    })
  })

  // ============================================================================
  // Layout Tests
  // ============================================================================

  describe('Layout', () => {
    it('applies compact display class when chaos d20 is hidden', () => {
      const rollData = createRollData() // Default: no WP/PB check
      const wrapper = mount(DiceRoller, {
        props: {
          ...defaultProps,
          canRoll: false,
          pendingRoll: rollData,
        },
      })

      const diceDisplay = wrapper.find('.dice-display')
      expect(diceDisplay.classes()).toContain('dice-display-compact')
    })

    it('does not apply compact display class when chaos d20 is shown', () => {
      const rollData = createRollData({ check_wild_pitch: true, chaos_d20: 1 })
      const wrapper = mount(DiceRoller, {
        props: {
          ...defaultProps,
          canRoll: false,
          pendingRoll: rollData,
        },
      })

      const diceDisplay = wrapper.find('.dice-display')
      expect(diceDisplay.classes()).not.toContain('dice-display-compact')
    })
  })

  // ============================================================================
  // Edge Cases
  // ============================================================================

  describe('Edge Cases', () => {
    it('handles maximum dice values', () => {
      const rollData = createRollData({
        d6_one: 6,
        d6_two_a: 6,
        d6_two_b: 6,
        d6_two_total: 12,
        chaos_d20: 20,
        resolution_d20: 20,
      })

      const wrapper = mount(DiceRoller, {
        props: {
          ...defaultProps,
          canRoll: false,
          pendingRoll: rollData,
        },
      })

      const diceComponents = wrapper.findAllComponents(DiceShapes)
      expect(diceComponents[0].props('value')).toBe(6)
      expect(diceComponents[1].props('value')).toBe(12)
    })

    it('handles minimum dice values', () => {
      const rollData = createRollData({
        d6_one: 1,
        d6_two_a: 1,
        d6_two_b: 1,
        d6_two_total: 2,
        chaos_d20: 1,
        resolution_d20: 1,
        check_wild_pitch: true, // Show chaos d20
      })

      const wrapper = mount(DiceRoller, {
        props: {
          ...defaultProps,
          canRoll: false,
          pendingRoll: rollData,
        },
      })

      const diceComponents = wrapper.findAllComponents(DiceShapes)
      expect(diceComponents[0].props('value')).toBe(1)
      expect(diceComponents[1].props('value')).toBe(2)
      expect(diceComponents[2].props('value')).toBe(1) // chaos d20
      expect(diceComponents[3].props('value')).toBe(1) // resolution d20
    })

    it('transitions from no roll to roll result', async () => {
      const wrapper = mount(DiceRoller, {
        props: defaultProps,
      })

      expect(wrapper.find('.roll-button').exists()).toBe(true)
      expect(wrapper.find('.dice-results').exists()).toBe(false)

      await wrapper.setProps({ pendingRoll: createRollData(), canRoll: false })

      expect(wrapper.find('.roll-button').exists()).toBe(false)
      expect(wrapper.find('.dice-results').exists()).toBe(true)
    })

    it('clears roll result when pendingRoll set to null', async () => {
      const wrapper = mount(DiceRoller, {
        props: {
          ...defaultProps,
          canRoll: false,
          pendingRoll: createRollData(),
        },
      })

      expect(wrapper.find('.dice-results').exists()).toBe(true)

      await wrapper.setProps({ pendingRoll: null, canRoll: true })

      expect(wrapper.find('.dice-results').exists()).toBe(false)
      expect(wrapper.find('.roll-button').exists()).toBe(true)
    })
  })
})
