package heroic_ops

import (
	"fmt"
	"math/rand"
	"time"
)

// NewHeroicOPStarter creates a new heroic opportunity starter
func NewHeroicOPStarter(id int32, startClass int8, starterIcon int16) *HeroicOPStarter {
	return &HeroicOPStarter{
		ID:          id,
		StartClass:  startClass,
		StarterIcon: starterIcon,
		Abilities:   [6]int16{},
		SaveNeeded:  false,
	}
}

// Copy creates a deep copy of the starter
func (hos *HeroicOPStarter) Copy() *HeroicOPStarter {
	hos.mu.RLock()
	defer hos.mu.RUnlock()

	newStarter := &HeroicOPStarter{
		ID:          hos.ID,
		StartClass:  hos.StartClass,
		StarterIcon: hos.StarterIcon,
		Abilities:   hos.Abilities, // Arrays are copied by value
		Name:        hos.Name,
		Description: hos.Description,
		SaveNeeded:  false,
	}

	return newStarter
}

// GetAbility returns the ability icon at the specified position
func (hos *HeroicOPStarter) GetAbility(position int) int16 {
	hos.mu.RLock()
	defer hos.mu.RUnlock()

	if position < 0 || position >= MaxAbilities {
		return AbilityIconNone
	}

	return hos.Abilities[position]
}

// SetAbility sets the ability icon at the specified position
func (hos *HeroicOPStarter) SetAbility(position int, abilityIcon int16) bool {
	hos.mu.Lock()
	defer hos.mu.Unlock()

	if position < 0 || position >= MaxAbilities {
		return false
	}

	hos.Abilities[position] = abilityIcon
	hos.SaveNeeded = true
	return true
}

// IsComplete checks if the starter chain is complete (has completion marker)
func (hos *HeroicOPStarter) IsComplete(position int) bool {
	hos.mu.RLock()
	defer hos.mu.RUnlock()

	if position < 0 || position >= MaxAbilities {
		return false
	}

	return hos.Abilities[position] == AbilityIconAny
}

// CanInitiate checks if the specified class can initiate this starter
func (hos *HeroicOPStarter) CanInitiate(playerClass int8) bool {
	hos.mu.RLock()
	defer hos.mu.RUnlock()

	return hos.StartClass == ClassAny || hos.StartClass == playerClass
}

// MatchesAbility checks if the given ability matches the current position
func (hos *HeroicOPStarter) MatchesAbility(position int, abilityIcon int16) bool {
	hos.mu.RLock()
	defer hos.mu.RUnlock()

	if position < 0 || position >= MaxAbilities {
		return false
	}

	requiredAbility := hos.Abilities[position]

	// Wildcard matches any ability
	if requiredAbility == AbilityIconAny {
		return true
	}

	// Exact match required
	return requiredAbility == abilityIcon
}

// Validate checks if the starter is properly configured
func (hos *HeroicOPStarter) Validate() error {
	hos.mu.RLock()
	defer hos.mu.RUnlock()

	if hos.ID <= 0 {
		return fmt.Errorf("invalid starter ID: %d", hos.ID)
	}

	if hos.StarterIcon <= 0 {
		return fmt.Errorf("invalid starter icon: %d", hos.StarterIcon)
	}

	// Check for at least one non-zero ability
	hasAbility := false
	for _, ability := range hos.Abilities {
		if ability != AbilityIconNone {
			hasAbility = true
			break
		}
	}

	if !hasAbility {
		return fmt.Errorf("starter must have at least one ability")
	}

	return nil
}

// NewHeroicOPWheel creates a new heroic opportunity wheel
func NewHeroicOPWheel(id int32, starterLinkID int32, order int8) *HeroicOPWheel {
	return &HeroicOPWheel{
		ID:            id,
		StarterLinkID: starterLinkID,
		Order:         order,
		Abilities:     [6]int16{},
		Chance:        1.0,
		SaveNeeded:    false,
	}
}

// Copy creates a deep copy of the wheel
func (how *HeroicOPWheel) Copy() *HeroicOPWheel {
	how.mu.RLock()
	defer how.mu.RUnlock()

	newWheel := &HeroicOPWheel{
		ID:              how.ID,
		StarterLinkID:   how.StarterLinkID,
		Order:           how.Order,
		ShiftIcon:       how.ShiftIcon,
		Chance:          how.Chance,
		Abilities:       how.Abilities, // Arrays are copied by value
		SpellID:         how.SpellID,
		Name:            how.Name,
		Description:     how.Description,
		RequiredPlayers: how.RequiredPlayers,
		SaveNeeded:      false,
	}

	return newWheel
}

// GetAbility returns the ability icon at the specified position
func (how *HeroicOPWheel) GetAbility(position int) int16 {
	how.mu.RLock()
	defer how.mu.RUnlock()

	if position < 0 || position >= MaxAbilities {
		return AbilityIconNone
	}

	return how.Abilities[position]
}

// SetAbility sets the ability icon at the specified position
func (how *HeroicOPWheel) SetAbility(position int, abilityIcon int16) bool {
	how.mu.Lock()
	defer how.mu.Unlock()

	if position < 0 || position >= MaxAbilities {
		return false
	}

	how.Abilities[position] = abilityIcon
	how.SaveNeeded = true
	return true
}

// IsOrdered checks if this wheel requires ordered completion
func (how *HeroicOPWheel) IsOrdered() bool {
	how.mu.RLock()
	defer how.mu.RUnlock()

	return how.Order >= WheelOrderOrdered
}

// HasShift checks if this wheel has a shift ability
func (how *HeroicOPWheel) HasShift() bool {
	how.mu.RLock()
	defer how.mu.RUnlock()

	return how.ShiftIcon > 0
}

// CanShift checks if shifting is possible with the given ability
func (how *HeroicOPWheel) CanShift(abilityIcon int16) bool {
	how.mu.RLock()
	defer how.mu.RUnlock()

	return how.ShiftIcon > 0 && how.ShiftIcon == abilityIcon
}

// GetNextRequiredAbility returns the next required ability for ordered wheels
func (how *HeroicOPWheel) GetNextRequiredAbility(countered [6]int8) int16 {
	how.mu.RLock()
	defer how.mu.RUnlock()

	if !how.IsOrdered() {
		return AbilityIconNone // Any uncompleted ability works for unordered
	}

	// Find first uncompleted ability in order
	for i := 0; i < MaxAbilities; i++ {
		if countered[i] == 0 && how.Abilities[i] != AbilityIconNone {
			return how.Abilities[i]
		}
	}

	return AbilityIconNone
}

// CanUseAbility checks if an ability can be used on this wheel
func (how *HeroicOPWheel) CanUseAbility(abilityIcon int16, countered [6]int8) bool {
	how.mu.RLock()
	defer how.mu.RUnlock()

	// Check if this is a shift attempt
	if how.CanShift(abilityIcon) {
		return true
	}

	if how.IsOrdered() {
		// For ordered wheels, only the next required ability can be used
		nextRequired := how.GetNextRequiredAbility(countered)
		return nextRequired == abilityIcon
	} else {
		// For unordered wheels, any uncompleted matching ability can be used
		for i := 0; i < MaxAbilities; i++ {
			if countered[i] == 0 && how.Abilities[i] == abilityIcon {
				return true
			}
		}
	}

	return false
}

// Validate checks if the wheel is properly configured
func (how *HeroicOPWheel) Validate() error {
	how.mu.RLock()
	defer how.mu.RUnlock()

	if how.ID <= 0 {
		return fmt.Errorf("invalid wheel ID: %d", how.ID)
	}

	if how.StarterLinkID <= 0 {
		return fmt.Errorf("invalid starter link ID: %d", how.StarterLinkID)
	}

	if how.Chance < MinChance || how.Chance > MaxChance {
		return fmt.Errorf("invalid chance: %f (must be %f-%f)", how.Chance, MinChance, MaxChance)
	}

	if how.SpellID <= 0 {
		return fmt.Errorf("invalid spell ID: %d", how.SpellID)
	}

	// Check for at least one non-zero ability
	hasAbility := false
	for _, ability := range how.Abilities {
		if ability != AbilityIconNone {
			hasAbility = true
			break
		}
	}

	if !hasAbility {
		return fmt.Errorf("wheel must have at least one ability")
	}

	return nil
}

// NewHeroicOP creates a new heroic opportunity instance
func NewHeroicOP(instanceID int64, encounterID int32) *HeroicOP {
	return &HeroicOP{
		ID:              instanceID,
		EncounterID:     encounterID,
		State:           HOStateInactive,
		StartTime:       time.Now(),
		Participants:    make(map[int32]bool),
		CurrentStarters: make([]int32, 0),
		TotalTime:       DefaultWheelTimerSeconds * 1000, // Convert to milliseconds
		TimeRemaining:   DefaultWheelTimerSeconds * 1000,
		SaveNeeded:      false,
	}
}

// AddParticipant adds a character to the HO participants
func (ho *HeroicOP) AddParticipant(characterID int32) {
	ho.mu.Lock()
	defer ho.mu.Unlock()

	ho.Participants[characterID] = true
	ho.SaveNeeded = true
}

// RemoveParticipant removes a character from the HO participants
func (ho *HeroicOP) RemoveParticipant(characterID int32) {
	ho.mu.Lock()
	defer ho.mu.Unlock()

	delete(ho.Participants, characterID)
	ho.SaveNeeded = true
}

// IsParticipant checks if a character is participating in this HO
func (ho *HeroicOP) IsParticipant(characterID int32) bool {
	ho.mu.RLock()
	defer ho.mu.RUnlock()

	return ho.Participants[characterID]
}

// GetParticipants returns a slice of participant character IDs
func (ho *HeroicOP) GetParticipants() []int32 {
	ho.mu.RLock()
	defer ho.mu.RUnlock()

	participants := make([]int32, 0, len(ho.Participants))
	for characterID := range ho.Participants {
		participants = append(participants, characterID)
	}

	return participants
}

// StartStarterChain initiates the starter chain phase
func (ho *HeroicOP) StartStarterChain(availableStarters []int32) {
	ho.mu.Lock()
	defer ho.mu.Unlock()

	ho.State = HOStateStarterChain
	ho.CurrentStarters = make([]int32, len(availableStarters))
	copy(ho.CurrentStarters, availableStarters)
	ho.StarterProgress = 0
	ho.StartTime = time.Now()
	ho.SaveNeeded = true
}

// ProcessStarterAbility processes an ability during starter chain phase
func (ho *HeroicOP) ProcessStarterAbility(abilityIcon int16, masterList *MasterHeroicOPList) bool {
	ho.mu.Lock()
	defer ho.mu.Unlock()

	if ho.State != HOStateStarterChain {
		return false
	}

	// Filter out starters that don't match this ability at current position
	newStarters := make([]int32, 0)

	for _, starterID := range ho.CurrentStarters {
		starter := masterList.GetStarter(starterID)
		if starter != nil && starter.MatchesAbility(int(ho.StarterProgress), abilityIcon) {
			// Check if this completes the starter
			if starter.IsComplete(int(ho.StarterProgress)) {
				// Starter completed, transition to wheel phase
				ho.StarterID = starterID
				ho.SaveNeeded = true
				return true
			}
			newStarters = append(newStarters, starterID)
		}
	}

	ho.CurrentStarters = newStarters
	ho.StarterProgress++
	ho.SaveNeeded = true

	// If no starters remain, HO fails
	return len(ho.CurrentStarters) > 0
}

// StartWheelPhase initiates the wheel phase
func (ho *HeroicOP) StartWheelPhase(wheel *HeroicOPWheel, timerSeconds int32) {
	ho.mu.Lock()
	defer ho.mu.Unlock()

	ho.State = HOStateWheelPhase
	ho.WheelID = wheel.ID
	ho.WheelStartTime = time.Now()
	ho.TotalTime = timerSeconds * 1000 // Convert to milliseconds
	ho.TimeRemaining = ho.TotalTime
	ho.SpellName = wheel.Name
	ho.SpellDescription = wheel.Description

	// Clear countered array
	for i := range ho.Countered {
		ho.Countered[i] = 0
	}

	ho.SaveNeeded = true
}

// ProcessWheelAbility processes an ability during wheel phase
func (ho *HeroicOP) ProcessWheelAbility(abilityIcon int16, characterID int32, wheel *HeroicOPWheel) bool {
	ho.mu.Lock()
	defer ho.mu.Unlock()

	if ho.State != HOStateWheelPhase {
		return false
	}

	// Check for shift attempt
	if ho.ShiftUsed == ShiftNotUsed && wheel.CanShift(abilityIcon) {
		// Allow shift only if no progress made (unordered) or at start (ordered)
		canShift := false
		if wheel.IsOrdered() {
			// For ordered, can shift only if no abilities completed
			canShift = true
			for i := 0; i < MaxAbilities; i++ {
				if ho.Countered[i] != 0 {
					canShift = false
					break
				}
			}
		} else {
			// For unordered, can shift only if no abilities completed
			canShift = true
			for i := 0; i < MaxAbilities; i++ {
				if ho.Countered[i] != 0 {
					canShift = false
					break
				}
			}
		}

		if canShift {
			ho.ShiftUsed = ShiftUsed
			ho.SaveNeeded = true
			return true // Caller should handle wheel shifting
		}
		return false
	}

	// Check if ability can be used
	if !wheel.CanUseAbility(abilityIcon, ho.Countered) {
		return false
	}

	// Find matching ability position and mark as countered
	for i := 0; i < MaxAbilities; i++ {
		if ho.Countered[i] == 0 && wheel.GetAbility(i) == abilityIcon {
			ho.Countered[i] = 1
			ho.AddParticipant(characterID)
			ho.SaveNeeded = true

			// Check if wheel is complete
			complete := true
			for j := 0; j < MaxAbilities; j++ {
				if wheel.GetAbility(j) != AbilityIconNone && ho.Countered[j] == 0 {
					complete = false
					break
				}
			}

			if complete {
				ho.Complete = HOComplete
				ho.State = HOStateComplete
				ho.CompletedBy = characterID
			}

			return true
		}
	}

	return false
}

// UpdateTimer updates the remaining time for the HO
func (ho *HeroicOP) UpdateTimer(deltaMS int32) bool {
	ho.mu.Lock()
	defer ho.mu.Unlock()

	if ho.State != HOStateWheelPhase {
		return true // Timer not active
	}

	ho.TimeRemaining -= deltaMS

	if ho.TimeRemaining <= 0 {
		ho.TimeRemaining = 0
		ho.State = HOStateFailed
		ho.SaveNeeded = true
		return false // Timer expired
	}

	ho.SaveNeeded = true
	return true
}

// IsComplete checks if the HO is successfully completed
func (ho *HeroicOP) IsComplete() bool {
	ho.mu.RLock()
	defer ho.mu.RUnlock()

	return ho.Complete == HOComplete && ho.State == HOStateComplete
}

// IsFailed checks if the HO has failed
func (ho *HeroicOP) IsFailed() bool {
	ho.mu.RLock()
	defer ho.mu.RUnlock()

	return ho.State == HOStateFailed
}

// IsActive checks if the HO is currently active (in progress)
func (ho *HeroicOP) IsActive() bool {
	ho.mu.RLock()
	defer ho.mu.RUnlock()

	return ho.State == HOStateStarterChain || ho.State == HOStateWheelPhase
}

// GetProgress returns the completion percentage (0.0 - 1.0)
func (ho *HeroicOP) GetProgress() float32 {
	ho.mu.RLock()
	defer ho.mu.RUnlock()

	if ho.State != HOStateWheelPhase {
		return 0.0
	}

	completed := 0
	total := 0

	for i := 0; i < MaxAbilities; i++ {
		if ho.Countered[i] != 0 {
			completed++
		}
		if ho.Countered[i] != 0 || ho.Countered[i] == 0 { // All positions count
			total++
		}
	}

	if total == 0 {
		return 0.0
	}

	return float32(completed) / float32(total)
}

// GetPacketData returns data formatted for client packets
func (ho *HeroicOP) GetPacketData(wheel *HeroicOPWheel) *PacketData {
	ho.mu.RLock()
	defer ho.mu.RUnlock()

	data := &PacketData{
		SpellName:        ho.SpellName,
		SpellDescription: ho.SpellDescription,
		TimeRemaining:    ho.TimeRemaining,
		TotalTime:        ho.TotalTime,
		Complete:         ho.Complete,
		State:            ho.State,
		CanShift:         false,
		ShiftIcon:        0,
	}

	if wheel != nil {
		data.Abilities = wheel.Abilities
		data.CanShift = ho.ShiftUsed == ShiftNotUsed && wheel.HasShift()
		data.ShiftIcon = wheel.ShiftIcon
	}

	data.Countered = ho.Countered

	return data
}

// Validate checks if the HO instance is in a valid state
func (ho *HeroicOP) Validate() error {
	ho.mu.RLock()
	defer ho.mu.RUnlock()

	if ho.ID <= 0 {
		return fmt.Errorf("invalid HO instance ID: %d", ho.ID)
	}

	if ho.EncounterID <= 0 {
		return fmt.Errorf("invalid encounter ID: %d", ho.EncounterID)
	}

	if ho.State < HOStateInactive || ho.State > HOStateFailed {
		return fmt.Errorf("invalid HO state: %d", ho.State)
	}

	if ho.State == HOStateWheelPhase {
		if ho.WheelID <= 0 {
			return fmt.Errorf("wheel phase requires valid wheel ID")
		}

		if ho.TotalTime <= 0 {
			return fmt.Errorf("wheel phase requires valid timer")
		}
	}

	return nil
}

// Copy creates a deep copy of the HO instance
func (ho *HeroicOP) Copy() *HeroicOP {
	ho.mu.RLock()
	defer ho.mu.RUnlock()

	newHO := &HeroicOP{
		ID:               ho.ID,
		EncounterID:      ho.EncounterID,
		StarterID:        ho.StarterID,
		WheelID:          ho.WheelID,
		State:            ho.State,
		StartTime:        ho.StartTime,
		WheelStartTime:   ho.WheelStartTime,
		TimeRemaining:    ho.TimeRemaining,
		TotalTime:        ho.TotalTime,
		Complete:         ho.Complete,
		Countered:        ho.Countered, // Arrays are copied by value
		ShiftUsed:        ho.ShiftUsed,
		StarterProgress:  ho.StarterProgress,
		CompletedBy:      ho.CompletedBy,
		SpellName:        ho.SpellName,
		SpellDescription: ho.SpellDescription,
		Participants:     make(map[int32]bool, len(ho.Participants)),
		CurrentStarters:  make([]int32, len(ho.CurrentStarters)),
		SaveNeeded:       false,
	}

	// Deep copy participants map
	for characterID, participating := range ho.Participants {
		newHO.Participants[characterID] = participating
	}

	// Deep copy current starters slice
	copy(newHO.CurrentStarters, ho.CurrentStarters)

	return newHO
}

// Helper functions for random selection

// SelectRandomWheel selects a random wheel from a list based on chance values
func SelectRandomWheel(wheels []*HeroicOPWheel) *HeroicOPWheel {
	if len(wheels) == 0 {
		return nil
	}

	if len(wheels) == 1 {
		return wheels[0]
	}

	// Calculate total chance
	totalChance := float32(0.0)
	for _, wheel := range wheels {
		totalChance += wheel.Chance
	}

	if totalChance <= 0.0 {
		// If no chances set, select randomly with equal probability
		return wheels[rand.Intn(len(wheels))]
	}

	// Random selection based on weighted chance
	randomValue := rand.Float32() * totalChance
	currentChance := float32(0.0)

	for _, wheel := range wheels {
		currentChance += wheel.Chance
		if randomValue <= currentChance {
			return wheel
		}
	}

	// Fallback to last wheel (shouldn't happen with proper math)
	return wheels[len(wheels)-1]
}

// GetElapsedTime returns the elapsed time since HO started
func (ho *HeroicOP) GetElapsedTime() time.Duration {
	ho.mu.RLock()
	defer ho.mu.RUnlock()

	return time.Since(ho.StartTime)
}

// GetWheelElapsedTime returns the elapsed time since wheel phase started
func (ho *HeroicOP) GetWheelElapsedTime() time.Duration {
	ho.mu.RLock()
	defer ho.mu.RUnlock()

	if ho.State != HOStateWheelPhase {
		return 0
	}

	return time.Since(ho.WheelStartTime)
}
