package heroic_ops

import (
	"context"
	"fmt"
	"time"
)

// NewHeroicOPManager creates a new heroic opportunity manager
func NewHeroicOPManager(masterList *MasterHeroicOPList, database HeroicOPDatabase,
	clientManager ClientManager, encounterManager EncounterManager, playerManager PlayerManager) *HeroicOPManager {
	return &HeroicOPManager{
		activeHOs:         make(map[int64]*HeroicOP),
		encounterHOs:      make(map[int32][]*HeroicOP),
		masterList:        masterList,
		database:          database,
		nextInstanceID:    1,
		defaultWheelTimer: DefaultWheelTimerSeconds * 1000, // Convert to milliseconds
		maxConcurrentHOs:  MaxConcurrentHOs,
		enableLogging:     true,
		enableStatistics:  true,
	}
}

// SetEventHandler sets the event handler for HO events
func (hom *HeroicOPManager) SetEventHandler(handler HeroicOPEventHandler) {
	hom.eventHandler = handler
}

// SetLogger sets the logger for the manager
func (hom *HeroicOPManager) SetLogger(logger LogHandler) {
	hom.logger = logger
}

// Initialize loads configuration and prepares the manager
func (hom *HeroicOPManager) Initialize(ctx context.Context, config *HeroicOPConfig) error {
	hom.mu.Lock()
	defer hom.mu.Unlock()

	if config != nil {
		hom.defaultWheelTimer = config.DefaultWheelTimer
		hom.maxConcurrentHOs = config.MaxConcurrentHOs
		hom.enableLogging = config.EnableLogging
		hom.enableStatistics = config.EnableStatistics
	}

	// Ensure master list is loaded
	if !hom.masterList.IsLoaded() {
		if err := hom.masterList.LoadFromDatabase(ctx, hom.database); err != nil {
			return fmt.Errorf("failed to load heroic opportunities: %w", err)
		}
	}

	if hom.logger != nil {
		hom.logger.LogInfo("heroic_ops", "Initialized HO manager with %d starters and %d wheels",
			hom.masterList.GetStarterCount(), hom.masterList.GetWheelCount())
	}

	return nil
}

// StartHeroicOpportunity initiates a new heroic opportunity
func (hom *HeroicOPManager) StartHeroicOpportunity(ctx context.Context, encounterID int32, initiatorID int32) (*HeroicOP, error) {
	hom.mu.Lock()
	defer hom.mu.Unlock()

	// Check if encounter can have more HOs
	currentHOs := hom.encounterHOs[encounterID]
	if len(currentHOs) >= hom.maxConcurrentHOs {
		return nil, fmt.Errorf("encounter %d already has maximum concurrent HOs (%d)",
			encounterID, hom.maxConcurrentHOs)
	}

	// Get initiator's class
	playerInfo, err := hom.playerManager.GetPlayerInfo(initiatorID)
	if err != nil {
		return nil, fmt.Errorf("failed to get player info for initiator %d: %w", initiatorID, err)
	}

	// Get available starters for player's class
	starters := hom.masterList.GetStartersForClass(playerInfo.AdventureClass)
	if len(starters) == 0 {
		return nil, fmt.Errorf("no heroic opportunities available for class %d", playerInfo.AdventureClass)
	}

	// Create new HO instance
	instanceID := hom.nextInstanceID
	hom.nextInstanceID++

	ho := NewHeroicOP(instanceID, encounterID)
	ho.AddParticipant(initiatorID)

	// Prepare starter IDs for chain phase
	starterIDs := make([]int32, len(starters))
	for i, starter := range starters {
		starterIDs[i] = starter.ID
	}

	ho.StartStarterChain(starterIDs)

	// Add to tracking maps
	hom.activeHOs[instanceID] = ho
	hom.encounterHOs[encounterID] = append(hom.encounterHOs[encounterID], ho)

	// Save to database
	if err := hom.database.SaveHOInstance(ctx, ho); err != nil {
		if hom.logger != nil {
			hom.logger.LogError("heroic_ops", "Failed to save HO instance %d: %v", instanceID, err)
		}
	}

	// Notify event handler
	if hom.eventHandler != nil {
		hom.eventHandler.OnHOStarted(ho, initiatorID)
	}

	// Log event
	if hom.enableLogging {
		hom.logEvent(ctx, instanceID, EventHOStarted, initiatorID, 0, "HO started")
	}

	if hom.logger != nil {
		hom.logger.LogInfo("heroic_ops", "Started HO %d for encounter %d initiated by %d with %d starters",
			instanceID, encounterID, initiatorID, len(starterIDs))
	}

	return ho, nil
}

// ProcessAbility processes an ability used by a player during an active HO
func (hom *HeroicOPManager) ProcessAbility(ctx context.Context, instanceID int64, characterID int32, abilityIcon int16) error {
	hom.mu.Lock()
	defer hom.mu.Unlock()

	ho, exists := hom.activeHOs[instanceID]
	if !exists {
		return fmt.Errorf("heroic opportunity %d not found", instanceID)
	}

	if !ho.IsActive() {
		return fmt.Errorf("heroic opportunity %d is not active (state: %d)", instanceID, ho.State)
	}

	// Add player as participant
	ho.AddParticipant(characterID)
	success := false

	switch ho.State {
	case HOStateStarterChain:
		success = ho.ProcessStarterAbility(abilityIcon, hom.masterList)
		if success && len(ho.CurrentStarters) == 1 {
			// Starter chain completed, transition to wheel phase
			starterID := ho.CurrentStarters[0]
			ho.StarterID = starterID

			// Select random wheel for this starter
			wheel := hom.masterList.SelectRandomWheel(starterID)
			if wheel == nil {
				// No wheels available, HO fails
				ho.State = HOStateFailed
				hom.failHO(ctx, ho, "No wheels available for starter")
				return fmt.Errorf("no wheels available for starter %d", starterID)
			}

			// Start wheel phase
			ho.StartWheelPhase(wheel, hom.defaultWheelTimer/1000) // Convert to seconds

			// Notify event handler
			if hom.eventHandler != nil {
				hom.eventHandler.OnWheelPhaseStarted(ho, wheel.ID, ho.TimeRemaining)
			}

			// Send wheel packet to participants
			hom.sendWheelUpdate(ho, wheel)

			if hom.logger != nil {
				hom.logger.LogDebug("heroic_ops", "HO %d transitioned to wheel phase with wheel %d",
					instanceID, wheel.ID)
			}
		}

	case HOStateWheelPhase:
		wheel := hom.masterList.GetWheel(ho.WheelID)
		if wheel == nil {
			return fmt.Errorf("wheel %d not found for HO %d", ho.WheelID, instanceID)
		}

		// Check for shift attempt
		if ho.ShiftUsed == ShiftNotUsed && wheel.CanShift(abilityIcon) {
			return hom.handleWheelShift(ctx, ho, wheel, characterID)
		}

		// Process regular ability
		success = ho.ProcessWheelAbility(abilityIcon, characterID, wheel)

		if success {
			// Send progress update
			hom.sendProgressUpdate(ho)

			// Check if HO is complete
			if ho.IsComplete() {
				hom.completeHO(ctx, ho, wheel, characterID)
			}
		}
	}

	// Log ability use
	if hom.enableLogging {
		eventType := EventHOAbilityUsed
		data := fmt.Sprintf("ability:%d,success:%t", abilityIcon, success)
		hom.logEvent(ctx, instanceID, eventType, characterID, abilityIcon, data)
	}

	// Notify event handler
	if hom.eventHandler != nil {
		hom.eventHandler.OnAbilityUsed(ho, characterID, abilityIcon, success)

		if success {
			progress := ho.GetProgress()
			hom.eventHandler.OnProgressMade(ho, characterID, progress)
		}
	}

	// Save changes
	if ho.SaveNeeded {
		if err := hom.database.SaveHOInstance(ctx, ho); err != nil {
			if hom.logger != nil {
				hom.logger.LogError("heroic_ops", "Failed to save HO instance %d: %v", instanceID, err)
			}
		}
		ho.SaveNeeded = false
	}

	if !success {
		return fmt.Errorf("ability %d not allowed for current HO state", abilityIcon)
	}

	return nil
}

// UpdateTimers updates all active HO timers
func (hom *HeroicOPManager) UpdateTimers(ctx context.Context, deltaMS int32) {
	hom.mu.Lock()
	defer hom.mu.Unlock()

	var expiredHOs []int64

	for instanceID, ho := range hom.activeHOs {
		if ho.State == HOStateWheelPhase {
			if !ho.UpdateTimer(deltaMS) {
				// Timer expired
				expiredHOs = append(expiredHOs, instanceID)
			} else {
				// Send timer update to participants
				hom.sendTimerUpdate(ho)
			}
		}
	}

	// Handle expired HOs
	for _, instanceID := range expiredHOs {
		ho := hom.activeHOs[instanceID]
		hom.failHO(ctx, ho, "Timer expired")
	}
}

// GetActiveHO returns an active HO by instance ID
func (hom *HeroicOPManager) GetActiveHO(instanceID int64) (*HeroicOP, bool) {
	hom.mu.RLock()
	defer hom.mu.RUnlock()

	ho, exists := hom.activeHOs[instanceID]
	return ho, exists
}

// GetEncounterHOs returns all HOs for an encounter
func (hom *HeroicOPManager) GetEncounterHOs(encounterID int32) []*HeroicOP {
	hom.mu.RLock()
	defer hom.mu.RUnlock()

	hos := hom.encounterHOs[encounterID]
	result := make([]*HeroicOP, len(hos))
	copy(result, hos)
	return result
}

// CleanupExpiredHOs removes completed and failed HOs
func (hom *HeroicOPManager) CleanupExpiredHOs(ctx context.Context, maxAge time.Duration) {
	hom.mu.Lock()
	defer hom.mu.Unlock()

	cutoff := time.Now().Add(-maxAge)
	var toRemove []int64

	for instanceID, ho := range hom.activeHOs {
		if !ho.IsActive() && ho.StartTime.Before(cutoff) {
			toRemove = append(toRemove, instanceID)
		}
	}

	for _, instanceID := range toRemove {
		ho := hom.activeHOs[instanceID]

		// Remove from encounter tracking
		encounterHOs := hom.encounterHOs[ho.EncounterID]
		for i, encounterHO := range encounterHOs {
			if encounterHO.ID == instanceID {
				hom.encounterHOs[ho.EncounterID] = append(encounterHOs[:i], encounterHOs[i+1:]...)
				break
			}
		}

		// Clean up empty encounter list
		if len(hom.encounterHOs[ho.EncounterID]) == 0 {
			delete(hom.encounterHOs, ho.EncounterID)
		}

		// Remove from active list
		delete(hom.activeHOs, instanceID)

		// Delete from database
		if err := hom.database.DeleteHOInstance(ctx, instanceID); err != nil {
			if hom.logger != nil {
				hom.logger.LogError("heroic_ops", "Failed to delete HO instance %d: %v", instanceID, err)
			}
		}
	}

	if len(toRemove) > 0 && hom.logger != nil {
		hom.logger.LogDebug("heroic_ops", "Cleaned up %d expired HO instances", len(toRemove))
	}
}

// GetStatistics returns current HO system statistics
func (hom *HeroicOPManager) GetStatistics() *HeroicOPStatistics {
	hom.mu.RLock()
	defer hom.mu.RUnlock()

	stats := &HeroicOPStatistics{
		ActiveHOCount:      len(hom.activeHOs),
		ParticipationStats: make(map[int32]int64),
	}

	// Count participants
	for _, ho := range hom.activeHOs {
		for characterID := range ho.Participants {
			stats.ParticipationStats[characterID]++
		}
	}

	// TODO: Get additional statistics from database
	// This is a simplified implementation

	return stats
}

// Helper methods

// handleWheelShift handles wheel shifting logic
func (hom *HeroicOPManager) handleWheelShift(ctx context.Context, ho *HeroicOP, currentWheel *HeroicOPWheel, characterID int32) error {
	// Check if shift is allowed (no progress made)
	canShift := true
	for i := 0; i < MaxAbilities; i++ {
		if ho.Countered[i] != 0 {
			canShift = false
			break
		}
	}

	if !canShift {
		return fmt.Errorf("wheel shift not allowed after progress has been made")
	}

	// Select new random wheel
	newWheel := hom.masterList.SelectRandomWheel(ho.StarterID)
	if newWheel == nil || newWheel.ID == currentWheel.ID {
		return fmt.Errorf("no alternative wheel available for shift")
	}

	oldWheelID := ho.WheelID
	ho.WheelID = newWheel.ID
	ho.ShiftUsed = ShiftUsed
	ho.SpellName = newWheel.Name
	ho.SpellDescription = newWheel.Description
	ho.SaveNeeded = true

	// Send shift notification
	hom.sendShiftUpdate(ho, oldWheelID, newWheel.ID)

	// Log shift event
	if hom.enableLogging {
		data := fmt.Sprintf("old_wheel:%d,new_wheel:%d", oldWheelID, newWheel.ID)
		hom.logEvent(ctx, ho.ID, EventHOWheelShifted, characterID, 0, data)
	}

	// Notify event handler
	if hom.eventHandler != nil {
		hom.eventHandler.OnWheelShifted(ho, characterID, newWheel.ID)
	}

	if hom.logger != nil {
		hom.logger.LogDebug("heroic_ops", "HO %d wheel shifted from %d to %d by character %d",
			ho.ID, oldWheelID, newWheel.ID, characterID)
	}

	return nil
}

// completeHO handles HO completion
func (hom *HeroicOPManager) completeHO(ctx context.Context, ho *HeroicOP, wheel *HeroicOPWheel, completedBy int32) {
	ho.State = HOStateComplete
	ho.Complete = HOComplete
	ho.CompletedBy = completedBy
	ho.SaveNeeded = true

	// Cast completion spell
	if wheel.SpellID > 0 {
		participants := ho.GetParticipants()
		// TODO: Cast spell on participants through spell manager
		// hom.spellManager.CastSpell(completedBy, wheel.SpellID, participants)
	}

	// Send completion packet
	hom.sendCompletionUpdate(ho, true)

	// Log completion
	if hom.enableLogging {
		data := fmt.Sprintf("completed_by:%d,spell_id:%d,participants:%d",
			completedBy, wheel.SpellID, len(ho.Participants))
		hom.logEvent(ctx, ho.ID, EventHOCompleted, completedBy, 0, data)
	}

	// Notify event handler
	if hom.eventHandler != nil {
		hom.eventHandler.OnHOCompleted(ho, completedBy, wheel.SpellID)
	}

	if hom.logger != nil {
		hom.logger.LogInfo("heroic_ops", "HO %d completed by character %d, spell %d cast",
			ho.ID, completedBy, wheel.SpellID)
	}
}

// failHO handles HO failure
func (hom *HeroicOPManager) failHO(ctx context.Context, ho *HeroicOP, reason string) {
	ho.State = HOStateFailed
	ho.SaveNeeded = true

	// Send failure packet
	hom.sendCompletionUpdate(ho, false)

	// Log failure
	if hom.enableLogging {
		hom.logEvent(ctx, ho.ID, EventHOFailed, 0, 0, reason)
	}

	// Notify event handler
	if hom.eventHandler != nil {
		hom.eventHandler.OnHOFailed(ho, reason)
	}

	if hom.logger != nil {
		hom.logger.LogDebug("heroic_ops", "HO %d failed: %s", ho.ID, reason)
	}
}

// Communication helper methods

func (hom *HeroicOPManager) sendWheelUpdate(ho *HeroicOP, wheel *HeroicOPWheel) {
	if hom.clientManager == nil {
		return
	}

	participants := ho.GetParticipants()
	packetBuilder := NewHeroicOPPacketBuilder(0) // Default version
	data := packetBuilder.ToPacketData(ho, wheel)

	for _, characterID := range participants {
		if err := hom.clientManager.SendHOUpdate(characterID, data); err != nil {
			if hom.logger != nil {
				hom.logger.LogWarning("heroic_ops", "Failed to send HO update to character %d: %v",
					characterID, err)
			}
		}
	}
}

func (hom *HeroicOPManager) sendProgressUpdate(ho *HeroicOP) {
	if hom.clientManager == nil {
		return
	}

	participants := ho.GetParticipants()
	wheel := hom.masterList.GetWheel(ho.WheelID)
	packetBuilder := NewHeroicOPPacketBuilder(0)
	data := packetBuilder.ToPacketData(ho, wheel)

	for _, characterID := range participants {
		if err := hom.clientManager.SendHOUpdate(characterID, data); err != nil {
			if hom.logger != nil {
				hom.logger.LogWarning("heroic_ops", "Failed to send progress update to character %d: %v",
					characterID, err)
			}
		}
	}
}

func (hom *HeroicOPManager) sendTimerUpdate(ho *HeroicOP) {
	if hom.clientManager == nil {
		return
	}

	participants := ho.GetParticipants()

	for _, characterID := range participants {
		if err := hom.clientManager.SendHOTimer(characterID, ho.TimeRemaining, ho.TotalTime); err != nil {
			if hom.logger != nil {
				hom.logger.LogWarning("heroic_ops", "Failed to send timer update to character %d: %v",
					characterID, err)
			}
		}
	}
}

func (hom *HeroicOPManager) sendCompletionUpdate(ho *HeroicOP, success bool) {
	if hom.clientManager == nil {
		return
	}

	participants := ho.GetParticipants()

	for _, characterID := range participants {
		if err := hom.clientManager.SendHOComplete(characterID, ho, success); err != nil {
			if hom.logger != nil {
				hom.logger.LogWarning("heroic_ops", "Failed to send completion update to character %d: %v",
					characterID, err)
			}
		}
	}
}

func (hom *HeroicOPManager) sendShiftUpdate(ho *HeroicOP, oldWheelID, newWheelID int32) {
	if hom.clientManager == nil {
		return
	}

	participants := ho.GetParticipants()
	packetBuilder := NewHeroicOPPacketBuilder(0)

	for _, characterID := range participants {
		if packet, err := packetBuilder.BuildHOShiftPacket(ho, oldWheelID, newWheelID); err == nil {
			// TODO: Send packet through client manager
			_ = packet // Placeholder
		}
	}
}

// logEvent logs an HO event to the database
func (hom *HeroicOPManager) logEvent(ctx context.Context, instanceID int64, eventType int, characterID int32, abilityIcon int16, data string) {
	if !hom.enableLogging {
		return
	}

	event := &HeroicOPEvent{
		InstanceID:  instanceID,
		EventType:   eventType,
		CharacterID: characterID,
		AbilityIcon: abilityIcon,
		Timestamp:   time.Now(),
		Data:        data,
	}

	if err := hom.database.SaveHOEvent(ctx, event); err != nil {
		if hom.logger != nil {
			hom.logger.LogError("heroic_ops", "Failed to save HO event: %v", err)
		}
	}
}
