package factions

import (
	"sync"
)

// PlayerFaction manages faction standing for a single player
type PlayerFaction struct {
	factionValues       map[int32]int32 // Faction ID -> current value
	factionPercent      map[int32]int8  // Faction ID -> percentage within con level
	factionUpdateNeeded []int32         // Factions that need client updates
	masterFactionList   *MasterFactionList
	updateMutex         sync.Mutex   // Thread safety for updates
	mutex               sync.RWMutex // Thread safety for faction data
}

// NewPlayerFaction creates a new player faction system
func NewPlayerFaction(masterFactionList *MasterFactionList) *PlayerFaction {
	return &PlayerFaction{
		factionValues:       make(map[int32]int32),
		factionPercent:      make(map[int32]int8),
		factionUpdateNeeded: make([]int32, 0),
		masterFactionList:   masterFactionList,
	}
}

// GetMaxValue returns the maximum faction value for a given consideration level
func (pf *PlayerFaction) GetMaxValue(con int8) int32 {
	if con < 0 {
		return int32(con) * ConMultiplier
	}
	return (int32(con) * ConMultiplier) + ConRemainder
}

// GetMinValue returns the minimum faction value for a given consideration level
func (pf *PlayerFaction) GetMinValue(con int8) int32 {
	if con <= 0 {
		return (int32(con) * ConMultiplier) - ConRemainder
	}
	return int32(con) * ConMultiplier
}

// ShouldAttack returns true if the player should attack based on faction
func (pf *PlayerFaction) ShouldAttack(factionID int32) bool {
	return pf.GetCon(factionID) <= AttackThreshold
}

// GetCon returns the consideration level (-4 to 4) for a faction
func (pf *PlayerFaction) GetCon(factionID int32) int8 {
	// Special faction IDs have predefined cons
	if factionID <= SpecialFactionIDMax {
		if factionID == 0 {
			return ConIndiff
		}
		return int8(factionID - 5)
	}

	value := pf.GetFactionValue(factionID)

	// Neutral range
	if value >= ConNeutralMin && value <= ConNeutralMax {
		return ConIndiff
	}

	// Maximum ally
	if value >= ConAllyMin {
		return ConAlly
	}

	// Maximum hostile
	if value <= ConHostileMax {
		return ConKOS
	}

	// Calculate con based on value
	return int8(value / ConMultiplier)
}

// GetPercent returns the percentage within the current consideration level
func (pf *PlayerFaction) GetPercent(factionID int32) int8 {
	// Special factions have no percentage
	if factionID <= SpecialFactionIDMax {
		return 0
	}

	con := pf.GetCon(factionID)
	value := pf.GetFactionValue(factionID)

	if con != ConIndiff {
		// Make value positive for calculation
		if value <= 0 {
			value *= -1
		}

		// Make con positive for calculation
		if con < 0 {
			con *= -1
		}

		// Calculate percentage within the con level
		value -= int32(con) * ConMultiplier
		value *= PercentMultiplier
		return int8(value / ConMultiplier)
	} else {
		// Neutral range calculation
		value += PercentNeutralOffset
		value *= PercentMultiplier
		return int8(value / PercentNeutralDivisor)
	}
}

// FactionUpdate builds a faction update packet for the client
func (pf *PlayerFaction) FactionUpdate(version int16) ([]byte, error) {
	pf.updateMutex.Lock()
	defer pf.updateMutex.Unlock()

	if len(pf.factionUpdateNeeded) == 0 {
		return nil, nil
	}

	// This is a placeholder for packet building
	// In the full implementation, this would use the PacketStruct system:
	// packet := configReader.getStruct("WS_FactionUpdate", version)
	// packet.setArrayLengthByName("num_factions", len(pf.factionUpdateNeeded))
	// for i, factionID := range pf.factionUpdateNeeded {
	//     faction := pf.masterFactionList.GetFaction(factionID)
	//     if faction != nil {
	//         packet.setArrayDataByName("faction_id", faction.ID, i)
	//         packet.setArrayDataByName("name", faction.Name, i)
	//         packet.setArrayDataByName("description", faction.Description, i)
	//         packet.setArrayDataByName("category", faction.Type, i)
	//         packet.setArrayDataByName("con", pf.GetCon(faction.ID), i)
	//         packet.setArrayDataByName("percentage", pf.GetPercent(faction.ID), i)
	//         packet.setArrayDataByName("value", pf.GetFactionValue(faction.ID), i)
	//     }
	// }
	// return packet.serialize()

	// Clear update list
	pf.factionUpdateNeeded = pf.factionUpdateNeeded[:0]

	// Return empty packet for now
	return make([]byte, 0), nil
}

// GetFactionValue returns the current faction value for a faction
func (pf *PlayerFaction) GetFactionValue(factionID int32) int32 {
	// Special factions always return 0
	if factionID <= SpecialFactionIDMax {
		return 0
	}

	pf.mutex.RLock()
	defer pf.mutex.RUnlock()

	// Return current value or 0 if not set
	// Note: The C++ code has a comment about always returning the default value,
	// but the actual implementation returns the stored value or 0
	return pf.factionValues[factionID]
}

// ShouldIncrease returns true if the faction can be increased
func (pf *PlayerFaction) ShouldIncrease(factionID int32) bool {
	if factionID <= SpecialFactionIDMax {
		return false
	}

	if pf.masterFactionList == nil {
		return false
	}

	return pf.masterFactionList.GetIncreaseAmount(factionID) != 0
}

// ShouldDecrease returns true if the faction can be decreased
func (pf *PlayerFaction) ShouldDecrease(factionID int32) bool {
	if factionID <= SpecialFactionIDMax {
		return false
	}

	if pf.masterFactionList == nil {
		return false
	}

	return pf.masterFactionList.GetDecreaseAmount(factionID) != 0
}

// IncreaseFaction increases a faction value
func (pf *PlayerFaction) IncreaseFaction(factionID int32, amount int32) bool {
	// Special factions cannot be changed
	if factionID <= SpecialFactionIDMax {
		return true
	}

	pf.mutex.Lock()
	defer pf.mutex.Unlock()

	// Use default amount if not specified
	if amount == 0 && pf.masterFactionList != nil {
		amount = pf.masterFactionList.GetIncreaseAmount(factionID)
	}

	// Increase the faction value
	pf.factionValues[factionID] += amount

	canContinue := true

	// Cap at maximum value
	if pf.factionValues[factionID] >= MaxFactionValue {
		pf.factionValues[factionID] = MaxFactionValue
		canContinue = false
	}

	// Mark for update
	pf.addFactionUpdateNeeded(factionID)

	return canContinue
}

// DecreaseFaction decreases a faction value
func (pf *PlayerFaction) DecreaseFaction(factionID int32, amount int32) bool {
	// Special factions cannot be changed
	if factionID <= SpecialFactionIDMax {
		return true
	}

	pf.mutex.Lock()
	defer pf.mutex.Unlock()

	// Use default amount if not specified
	if amount == 0 && pf.masterFactionList != nil {
		amount = pf.masterFactionList.GetDecreaseAmount(factionID)
	}

	// Cannot decrease if no amount specified
	if amount == 0 {
		return false
	}

	// Decrease the faction value
	pf.factionValues[factionID] -= amount

	canContinue := true

	// Cap at minimum value
	if pf.factionValues[factionID] <= MinFactionValue {
		pf.factionValues[factionID] = MinFactionValue
		canContinue = false
	}

	// Mark for update
	pf.addFactionUpdateNeeded(factionID)

	return canContinue
}

// SetFactionValue sets a faction to a specific value
func (pf *PlayerFaction) SetFactionValue(factionID int32, value int32) bool {
	pf.mutex.Lock()
	defer pf.mutex.Unlock()

	pf.factionValues[factionID] = value

	// Mark for update
	pf.addFactionUpdateNeeded(factionID)

	return true
}

// GetFactionValues returns a copy of all faction values
func (pf *PlayerFaction) GetFactionValues() map[int32]int32 {
	pf.mutex.RLock()
	defer pf.mutex.RUnlock()

	// Return a copy to prevent external modification
	result := make(map[int32]int32)
	for id, value := range pf.factionValues {
		result[id] = value
	}

	return result
}

// HasFaction returns true if the player has a value for the given faction
func (pf *PlayerFaction) HasFaction(factionID int32) bool {
	pf.mutex.RLock()
	defer pf.mutex.RUnlock()

	_, exists := pf.factionValues[factionID]
	return exists
}

// GetFactionCount returns the number of factions the player has values for
func (pf *PlayerFaction) GetFactionCount() int {
	pf.mutex.RLock()
	defer pf.mutex.RUnlock()

	return len(pf.factionValues)
}

// ClearFactionValues removes all faction values
func (pf *PlayerFaction) ClearFactionValues() {
	pf.mutex.Lock()
	defer pf.mutex.Unlock()

	pf.factionValues = make(map[int32]int32)
	pf.factionPercent = make(map[int32]int8)
}

// addFactionUpdateNeeded marks a faction as needing an update (internal use, assumes lock held)
func (pf *PlayerFaction) addFactionUpdateNeeded(factionID int32) {
	// Note: This method assumes the mutex is already held by the caller
	pf.updateMutex.Lock()
	defer pf.updateMutex.Unlock()

	pf.factionUpdateNeeded = append(pf.factionUpdateNeeded, factionID)
}

// GetPendingUpdates returns factions that need client updates
func (pf *PlayerFaction) GetPendingUpdates() []int32 {
	pf.updateMutex.Lock()
	defer pf.updateMutex.Unlock()

	if len(pf.factionUpdateNeeded) == 0 {
		return nil
	}

	// Return a copy
	result := make([]int32, len(pf.factionUpdateNeeded))
	copy(result, pf.factionUpdateNeeded)

	return result
}

// ClearPendingUpdates clears the pending update list
func (pf *PlayerFaction) ClearPendingUpdates() {
	pf.updateMutex.Lock()
	defer pf.updateMutex.Unlock()

	pf.factionUpdateNeeded = pf.factionUpdateNeeded[:0]
}

// HasPendingUpdates returns true if there are pending faction updates
func (pf *PlayerFaction) HasPendingUpdates() bool {
	pf.updateMutex.Lock()
	defer pf.updateMutex.Unlock()

	return len(pf.factionUpdateNeeded) > 0
}
