package heroic_ops

import (
	"encoding/binary"
	"fmt"
	"math"
)

// HeroicOPPacketBuilder handles building packets for heroic opportunity client communication
type HeroicOPPacketBuilder struct {
	clientVersion int
}

// NewHeroicOPPacketBuilder creates a new packet builder
func NewHeroicOPPacketBuilder(clientVersion int) *HeroicOPPacketBuilder {
	return &HeroicOPPacketBuilder{
		clientVersion: clientVersion,
	}
}

// BuildHOStartPacket builds the initial HO start packet
func (hpb *HeroicOPPacketBuilder) BuildHOStartPacket(ho *HeroicOP) ([]byte, error) {
	if ho == nil {
		return nil, fmt.Errorf("heroic opportunity is nil")
	}

	// Start with base packet structure
	packet := make([]byte, 0, 256)

	// Packet header (simplified - real implementation would use proper packet structure)
	// This is a placeholder implementation
	packet = append(packet, 0x01) // HO Start packet type

	// HO Instance ID (8 bytes)
	idBytes := make([]byte, 8)
	binary.LittleEndian.PutUint64(idBytes, uint64(ho.ID))
	packet = append(packet, idBytes...)

	// Encounter ID (4 bytes)
	encounterBytes := make([]byte, 4)
	binary.LittleEndian.PutUint32(encounterBytes, uint32(ho.EncounterID))
	packet = append(packet, encounterBytes...)

	// State (1 byte)
	packet = append(packet, byte(ho.State))

	// Starter ID (4 bytes)
	starterBytes := make([]byte, 4)
	binary.LittleEndian.PutUint32(starterBytes, uint32(ho.StarterID))
	packet = append(packet, starterBytes...)

	return packet, nil
}

// BuildHOUpdatePacket builds an HO update packet for wheel phase
func (hpb *HeroicOPPacketBuilder) BuildHOUpdatePacket(ho *HeroicOP) ([]byte, error) {
	if ho == nil {
		return nil, fmt.Errorf("heroic opportunity is nil")
	}

	// Build packet based on HO state
	packet := make([]byte, 0, 512)

	// Packet header
	packet = append(packet, 0x02) // HO Update packet type

	// HO Instance ID (8 bytes)
	idBytes := make([]byte, 8)
	binary.LittleEndian.PutUint64(idBytes, uint64(ho.ID))
	packet = append(packet, idBytes...)

	// State (1 byte)
	packet = append(packet, byte(ho.State))

	if ho.State == HOStateWheelPhase {
		// Wheel ID (4 bytes)
		wheelBytes := make([]byte, 4)
		binary.LittleEndian.PutUint32(wheelBytes, uint32(ho.WheelID))
		packet = append(packet, wheelBytes...)

		// Time remaining (4 bytes)
		timeBytes := make([]byte, 4)
		binary.LittleEndian.PutUint32(timeBytes, uint32(ho.TimeRemaining))
		packet = append(packet, timeBytes...)

		// Total time (4 bytes)
		totalTimeBytes := make([]byte, 4)
		binary.LittleEndian.PutUint32(totalTimeBytes, uint32(ho.TotalTime))
		packet = append(packet, totalTimeBytes...)

		// Countered array (6 bytes)
		for i := 0; i < MaxAbilities; i++ {
			packet = append(packet, byte(ho.Countered[i]))
		}

		// Complete flag (1 byte)
		packet = append(packet, byte(ho.Complete))

		// Shift used flag (1 byte)
		packet = append(packet, byte(ho.ShiftUsed))

		// Spell name length and data
		spellNameBytes := []byte(ho.SpellName)
		packet = append(packet, byte(len(spellNameBytes)))
		packet = append(packet, spellNameBytes...)

		// Spell description length and data
		spellDescBytes := []byte(ho.SpellDescription)
		descLen := make([]byte, 2)
		binary.LittleEndian.PutUint16(descLen, uint16(len(spellDescBytes)))
		packet = append(packet, descLen...)
		packet = append(packet, spellDescBytes...)
	}

	return packet, nil
}

// BuildHOCompletePacket builds completion packet
func (hpb *HeroicOPPacketBuilder) BuildHOCompletePacket(ho *HeroicOP, success bool) ([]byte, error) {
	if ho == nil {
		return nil, fmt.Errorf("heroic opportunity is nil")
	}

	packet := make([]byte, 0, 256)

	// Packet header
	packet = append(packet, 0x03) // HO Complete packet type

	// HO Instance ID (8 bytes)
	idBytes := make([]byte, 8)
	binary.LittleEndian.PutUint64(idBytes, uint64(ho.ID))
	packet = append(packet, idBytes...)

	// Success flag (1 byte)
	if success {
		packet = append(packet, 0x01)
	} else {
		packet = append(packet, 0x00)
	}

	// Completed by character ID (4 bytes)
	completedByBytes := make([]byte, 4)
	binary.LittleEndian.PutUint32(completedByBytes, uint32(ho.CompletedBy))
	packet = append(packet, completedByBytes...)

	if success {
		// Spell ID if successful (4 bytes)
		spellBytes := make([]byte, 4)
		// Note: In real implementation, get spell ID from wheel
		binary.LittleEndian.PutUint32(spellBytes, 0) // Placeholder
		packet = append(packet, spellBytes...)
	}

	return packet, nil
}

// BuildHOTimerPacket builds timer update packet
func (hpb *HeroicOPPacketBuilder) BuildHOTimerPacket(timeRemaining, totalTime int32) ([]byte, error) {
	packet := make([]byte, 0, 16)

	// Packet header
	packet = append(packet, 0x04) // HO Timer packet type

	// Time remaining (4 bytes)
	timeBytes := make([]byte, 4)
	binary.LittleEndian.PutUint32(timeBytes, uint32(timeRemaining))
	packet = append(packet, timeBytes...)

	// Total time (4 bytes)
	totalTimeBytes := make([]byte, 4)
	binary.LittleEndian.PutUint32(totalTimeBytes, uint32(totalTime))
	packet = append(packet, totalTimeBytes...)

	return packet, nil
}

// BuildHOWheelPacket builds wheel-specific packet with abilities
func (hpb *HeroicOPPacketBuilder) BuildHOWheelPacket(ho *HeroicOP, wheel *HeroicOPWheel) ([]byte, error) {
	if ho == nil || wheel == nil {
		return nil, fmt.Errorf("heroic opportunity or wheel is nil")
	}

	packet := make([]byte, 0, 512)

	// Packet header
	packet = append(packet, 0x05) // HO Wheel packet type

	// HO Instance ID (8 bytes)
	idBytes := make([]byte, 8)
	binary.LittleEndian.PutUint64(idBytes, uint64(ho.ID))
	packet = append(packet, idBytes...)

	// Wheel ID (4 bytes)
	wheelBytes := make([]byte, 4)
	binary.LittleEndian.PutUint32(wheelBytes, uint32(wheel.ID))
	packet = append(packet, wheelBytes...)

	// Order type (1 byte)
	packet = append(packet, byte(wheel.Order))

	// Shift icon (2 bytes)
	shiftBytes := make([]byte, 2)
	binary.LittleEndian.PutUint16(shiftBytes, uint16(wheel.ShiftIcon))
	packet = append(packet, shiftBytes...)

	// Abilities (12 bytes - 2 bytes per ability)
	for i := 0; i < MaxAbilities; i++ {
		abilityBytes := make([]byte, 2)
		binary.LittleEndian.PutUint16(abilityBytes, uint16(wheel.Abilities[i]))
		packet = append(packet, abilityBytes...)
	}

	// Countered status (6 bytes)
	for i := 0; i < MaxAbilities; i++ {
		packet = append(packet, byte(ho.Countered[i]))
	}

	// Timer information
	timeBytes := make([]byte, 4)
	binary.LittleEndian.PutUint32(timeBytes, uint32(ho.TimeRemaining))
	packet = append(packet, timeBytes...)

	totalTimeBytes := make([]byte, 4)
	binary.LittleEndian.PutUint32(totalTimeBytes, uint32(ho.TotalTime))
	packet = append(packet, totalTimeBytes...)

	// Spell information
	spellBytes := make([]byte, 4)
	binary.LittleEndian.PutUint32(spellBytes, uint32(wheel.SpellID))
	packet = append(packet, spellBytes...)

	// Spell name length and data
	spellNameBytes := []byte(wheel.Name)
	packet = append(packet, byte(len(spellNameBytes)))
	packet = append(packet, spellNameBytes...)

	// Spell description length and data
	spellDescBytes := []byte(wheel.Description)
	descLen := make([]byte, 2)
	binary.LittleEndian.PutUint16(descLen, uint16(len(spellDescBytes)))
	packet = append(packet, descLen...)
	packet = append(packet, spellDescBytes...)

	return packet, nil
}

// BuildHOProgressPacket builds progress update packet
func (hpb *HeroicOPPacketBuilder) BuildHOProgressPacket(ho *HeroicOP, progressPercent float32) ([]byte, error) {
	if ho == nil {
		return nil, fmt.Errorf("heroic opportunity is nil")
	}

	packet := make([]byte, 0, 32)

	// Packet header
	packet = append(packet, 0x06) // HO Progress packet type

	// HO Instance ID (8 bytes)
	idBytes := make([]byte, 8)
	binary.LittleEndian.PutUint64(idBytes, uint64(ho.ID))
	packet = append(packet, idBytes...)

	// Progress percentage as float (4 bytes)
	progressBits := math.Float32bits(progressPercent)
	progressBytes := make([]byte, 4)
	binary.LittleEndian.PutUint32(progressBytes, progressBits)
	packet = append(packet, progressBytes...)

	// Current completion count (1 byte)
	completed := int8(0)
	for i := 0; i < MaxAbilities; i++ {
		if ho.Countered[i] != 0 {
			completed++
		}
	}
	packet = append(packet, byte(completed))

	return packet, nil
}

// BuildHOErrorPacket builds error notification packet
func (hpb *HeroicOPPacketBuilder) BuildHOErrorPacket(instanceID int64, errorCode int, errorMessage string) ([]byte, error) {
	packet := make([]byte, 0, 256)

	// Packet header
	packet = append(packet, 0x07) // HO Error packet type

	// HO Instance ID (8 bytes)
	idBytes := make([]byte, 8)
	binary.LittleEndian.PutUint64(idBytes, uint64(instanceID))
	packet = append(packet, idBytes...)

	// Error code (2 bytes)
	errorBytes := make([]byte, 2)
	binary.LittleEndian.PutUint16(errorBytes, uint16(errorCode))
	packet = append(packet, errorBytes...)

	// Error message length and data
	messageBytes := []byte(errorMessage)
	packet = append(packet, byte(len(messageBytes)))
	packet = append(packet, messageBytes...)

	return packet, nil
}

// BuildHOShiftPacket builds wheel shift notification packet
func (hpb *HeroicOPPacketBuilder) BuildHOShiftPacket(ho *HeroicOP, oldWheelID, newWheelID int32) ([]byte, error) {
	if ho == nil {
		return nil, fmt.Errorf("heroic opportunity is nil")
	}

	packet := make([]byte, 0, 32)

	// Packet header
	packet = append(packet, 0x08) // HO Shift packet type

	// HO Instance ID (8 bytes)
	idBytes := make([]byte, 8)
	binary.LittleEndian.PutUint64(idBytes, uint64(ho.ID))
	packet = append(packet, idBytes...)

	// Old wheel ID (4 bytes)
	oldWheelBytes := make([]byte, 4)
	binary.LittleEndian.PutUint32(oldWheelBytes, uint32(oldWheelID))
	packet = append(packet, oldWheelBytes...)

	// New wheel ID (4 bytes)
	newWheelBytes := make([]byte, 4)
	binary.LittleEndian.PutUint32(newWheelBytes, uint32(newWheelID))
	packet = append(packet, newWheelBytes...)

	return packet, nil
}

// PacketData conversion methods

// ToPacketData converts HO and wheel to packet data structure
func (hpb *HeroicOPPacketBuilder) ToPacketData(ho *HeroicOP, wheel *HeroicOPWheel) *PacketData {
	data := &PacketData{
		TimeRemaining: ho.TimeRemaining,
		TotalTime:     ho.TotalTime,
		Complete:      ho.Complete,
		State:         ho.State,
		Countered:     ho.Countered,
	}

	if wheel != nil {
		data.SpellName = wheel.Name
		data.SpellDescription = wheel.Description
		data.Abilities = wheel.Abilities
		data.CanShift = ho.ShiftUsed == ShiftNotUsed && wheel.HasShift()
		data.ShiftIcon = wheel.ShiftIcon
	} else {
		data.SpellName = ho.SpellName
		data.SpellDescription = ho.SpellDescription
		// Abilities will be zero-initialized
	}

	return data
}

// Helper methods for packet validation

// ValidatePacketSize checks if packet size is within acceptable limits
func (hpb *HeroicOPPacketBuilder) ValidatePacketSize(packet []byte) error {
	const maxPacketSize = 1024 // 1KB limit for HO packets

	if len(packet) > maxPacketSize {
		return fmt.Errorf("packet size %d exceeds maximum %d", len(packet), maxPacketSize)
	}

	return nil
}

// GetPacketTypeDescription returns human-readable packet type description
func (hpb *HeroicOPPacketBuilder) GetPacketTypeDescription(packetType byte) string {
	switch packetType {
	case 0x01:
		return "HO Start"
	case 0x02:
		return "HO Update"
	case 0x03:
		return "HO Complete"
	case 0x04:
		return "HO Timer"
	case 0x05:
		return "HO Wheel"
	case 0x06:
		return "HO Progress"
	case 0x07:
		return "HO Error"
	case 0x08:
		return "HO Shift"
	default:
		return "Unknown"
	}
}

// Client version specific methods

// IsVersionSupported checks if client version supports specific features
func (hpb *HeroicOPPacketBuilder) IsVersionSupported(feature string) bool {
	// Version-specific feature support
	switch feature {
	case "wheel_shifting":
		return hpb.clientVersion >= 546 // Example version requirement
	case "progress_updates":
		return hpb.clientVersion >= 564
	case "extended_timers":
		return hpb.clientVersion >= 572
	default:
		return true // Basic features supported in all versions
	}
}

// GetVersionSpecificPacketSize returns packet size limits for client version
func (hpb *HeroicOPPacketBuilder) GetVersionSpecificPacketSize() int {
	if hpb.clientVersion >= 564 {
		return 1024 // Newer clients support larger packets
	}
	return 512 // Older clients have smaller limits
}

// Error codes for HO system
const (
	HOErrorNone = iota
	HOErrorInvalidState
	HOErrorTimerExpired
	HOErrorAbilityNotAllowed
	HOErrorShiftAlreadyUsed
	HOErrorPlayerNotInEncounter
	HOErrorEncounterEnded
	HOErrorSystemDisabled
)

// GetErrorMessage returns human-readable error message for error code
func GetErrorMessage(errorCode int) string {
	switch errorCode {
	case HOErrorNone:
		return "No error"
	case HOErrorInvalidState:
		return "Heroic opportunity is in an invalid state"
	case HOErrorTimerExpired:
		return "Heroic opportunity timer has expired"
	case HOErrorAbilityNotAllowed:
		return "This ability cannot be used for the current heroic opportunity"
	case HOErrorShiftAlreadyUsed:
		return "Wheel shift has already been used"
	case HOErrorPlayerNotInEncounter:
		return "Player is not in the encounter"
	case HOErrorEncounterEnded:
		return "Encounter has ended"
	case HOErrorSystemDisabled:
		return "Heroic opportunity system is disabled"
	default:
		return "Unknown error"
	}
}
