package player

import (
	"sync"

	"eq2emu/internal/common"
	"eq2emu/internal/entity"
	"eq2emu/internal/quests"
)

// Global XP table
var levelXPReq map[int8]int32
var xpTableOnce sync.Once

// NewPlayer creates a new player instance
func NewPlayer() *Player {
	p := &Player{
		charID:          0,
		spawnID:         1,
		spawnIndex:      1,
		tutorialStep:    0,
		packetNum:       0,
		posPacketSpeed:  0,
		houseVaultSlots: 0,

		// Initialize maps
		playerQuests:                 make(map[int32]*quests.Quest),
		completedQuests:              make(map[int32]*quests.Quest),
		pendingQuests:                make(map[int32]*quests.Quest),
		currentQuestFlagged:          make(map[*entity.Spawn]bool),
		playerSpawnQuestsRequired:    make(map[int32][]int32),
		playerSpawnHistoryRequired:   make(map[int32][]int32),
		spawnVisPacketList:           make(map[int32]string),
		spawnInfoPacketList:          make(map[int32]string),
		spawnPosPacketList:           make(map[int32]string),
		spawnPacketSent:              make(map[int32]int8),
		spawnStateList:               make(map[int32]*SpawnQueueState),
		playerSpawnIDMap:             make(map[int32]*entity.Spawn),
		playerSpawnReverseIDMap:      make(map[*entity.Spawn]int32),
		playerAggroRangeSpawns:       make(map[int32]bool),
		pendingLootItems:             make(map[int32]map[int32]bool),
		friendList:                   make(map[string]int8),
		ignoreList:                   make(map[string]int8),
		characterHistory:             make(map[int8]map[int8][]*HistoryData),
		charLuaHistory:               make(map[int32]*LUAHistory),
		playersPoiList:               make(map[int32][]int32),
		pendingSelectableItemRewards: make(map[int32][]Item),
		statistics:                   make(map[int32]*Statistic),
		mailList:                     make(map[int32]*Mail),
		targetInvisHistory:           make(map[int32]bool),
		spawnedBots:                  make(map[int32]int32),
		macroIcons:                   make(map[int32]int16),
		sortedTraitList:              make(map[int8]map[int8][]*TraitData),
		classTraining:                make(map[int8][]*TraitData),
		raceTraits:                   make(map[int8][]*TraitData),
		innateRaceTraits:             make(map[int8][]*TraitData),
		focusEffects:                 make(map[int8][]*TraitData),
	}

	// Initialize entity base
	p.Entity = *entity.NewEntity()

	// Set player-specific defaults
	p.SetSpawnType(4) // Player spawn type
	p.appearance.DisplayName = 1
	p.appearance.ShowCommandIcon = 1
	p.appearance.PlayerFlag = 1
	p.appearance.Targetable = 1
	p.appearance.ShowLevel = 1

	// Set default away message
	p.awayMessage = "Sorry, I am A.F.K. (Away From Keyboard)"

	// Add player-specific commands
	p.AddSecondaryEntityCommand("Inspect", 10000, "inspect_player", "", 0, 0)
	p.AddSecondaryEntityCommand("Who", 10000, "who", "", 0, 0)

	// Initialize self in spawn maps
	p.playerSpawnIDMap[1] = &p.Entity.Spawn
	p.playerSpawnReverseIDMap[&p.Entity.Spawn] = 1

	// Set save spell effects
	p.stopSaveSpellEffects = false

	// Initialize trait update flag
	p.needTraitUpdate.Store(true)

	// Initialize control flags
	p.controlFlags = PlayerControlFlags{
		flagChanges:  make(map[int8]map[int8]int8),
		currentFlags: make(map[int8]map[int8]bool),
	}

	// Initialize character instances
	p.characterInstances = CharacterInstances{
		instanceList: make([]InstanceData, 0),
	}

	return p
}

// GetClient returns the player's client connection
func (p *Player) GetClient() *Client {
	return p.client
}

// SetClient sets the player's client connection
func (p *Player) SetClient(client *Client) {
	p.client = client
}

// GetPlayerInfo returns the player's info structure, creating it if needed
func (p *Player) GetPlayerInfo() *PlayerInfo {
	if p.info == nil {
		p.info = NewPlayerInfo(p)
	}
	return p.info
}

// IsPlayer always returns true for Player type
func (p *Player) IsPlayer() bool {
	return true
}

// GetCharacterID returns the character's database ID
func (p *Player) GetCharacterID() int32 {
	return p.charID
}

// SetCharacterID sets the character's database ID
func (p *Player) SetCharacterID(id int32) {
	p.charID = id
}

// GetTutorialStep returns the current tutorial step
func (p *Player) GetTutorialStep() int8 {
	return p.tutorialStep
}

// SetTutorialStep sets the current tutorial step
func (p *Player) SetTutorialStep(step int8) {
	p.tutorialStep = step
}

// SetCharSheetChanged marks the character sheet as needing an update
func (p *Player) SetCharSheetChanged(val bool) {
	p.charsheetChanged.Store(val)
}

// GetCharSheetChanged returns whether the character sheet needs updating
func (p *Player) GetCharSheetChanged() bool {
	return p.charsheetChanged.Load()
}

// SetRaidSheetChanged marks the raid sheet as needing an update
func (p *Player) SetRaidSheetChanged(val bool) {
	p.raidsheetChanged.Store(val)
}

// GetRaidSheetChanged returns whether the raid sheet needs updating
func (p *Player) GetRaidSheetChanged() bool {
	return p.raidsheetChanged.Load()
}

// AddFriend adds a friend to the player's friend list
func (p *Player) AddFriend(name string, save bool) {
	p.friendList[name] = 1
	if save {
		// TODO: Save to database
	}
}

// IsFriend checks if a name is in the friend list
func (p *Player) IsFriend(name string) bool {
	_, exists := p.friendList[name]
	return exists
}

// RemoveFriend removes a friend from the friend list
func (p *Player) RemoveFriend(name string) {
	delete(p.friendList, name)
	// TODO: Remove from database
}

// GetFriends returns the friend list
func (p *Player) GetFriends() map[string]int8 {
	return p.friendList
}

// AddIgnore adds a player to the ignore list
func (p *Player) AddIgnore(name string, save bool) {
	p.ignoreList[name] = 1
	if save {
		// TODO: Save to database
	}
}

// IsIgnored checks if a player is ignored
func (p *Player) IsIgnored(name string) bool {
	_, exists := p.ignoreList[name]
	return exists
}

// RemoveIgnore removes a player from the ignore list
func (p *Player) RemoveIgnore(name string) {
	delete(p.ignoreList, name)
	// TODO: Remove from database
}

// GetIgnoredPlayers returns the ignore list
func (p *Player) GetIgnoredPlayers() map[string]int8 {
	return p.ignoreList
}

// GetPlayerDiscoveredPOIs returns the player's discovered POIs
func (p *Player) GetPlayerDiscoveredPOIs() map[int32][]int32 {
	return p.playersPoiList
}

// AddPlayerDiscoveredPOI adds a discovered POI for the player
func (p *Player) AddPlayerDiscoveredPOI(locationID int32) {
	// TODO: Implement POI discovery logic
	if p.playersPoiList[locationID] == nil {
		p.playersPoiList[locationID] = make([]int32, 0)
	}
}

// SetSideSpeed sets the player's side movement speed
func (p *Player) SetSideSpeed(sideSpeed float32, updateFlags bool) {
	p.SetPos(&p.appearance.Pos.SideSpeed, sideSpeed, updateFlags)
}

// GetSideSpeed returns the player's side movement speed
func (p *Player) GetSideSpeed() float32 {
	return p.appearance.Pos.SideSpeed
}

// SetVertSpeed sets the player's vertical movement speed
func (p *Player) SetVertSpeed(vertSpeed float32, updateFlags bool) {
	p.SetPos(&p.appearance.Pos.VertSpeed, vertSpeed, updateFlags)
}

// GetVertSpeed returns the player's vertical movement speed
func (p *Player) GetVertSpeed() float32 {
	return p.appearance.Pos.VertSpeed
}

// SetClientHeading1 sets the client heading 1
func (p *Player) SetClientHeading1(heading float32, updateFlags bool) {
	p.SetPos(&p.appearance.Pos.ClientHeading1, heading, updateFlags)
}

// GetClientHeading1 returns the client heading 1
func (p *Player) GetClientHeading1() float32 {
	return p.appearance.Pos.ClientHeading1
}

// SetClientHeading2 sets the client heading 2
func (p *Player) SetClientHeading2(heading float32, updateFlags bool) {
	p.SetPos(&p.appearance.Pos.ClientHeading2, heading, updateFlags)
}

// GetClientHeading2 returns the client heading 2
func (p *Player) GetClientHeading2() float32 {
	return p.appearance.Pos.ClientHeading2
}

// SetClientPitch sets the client pitch
func (p *Player) SetClientPitch(pitch float32, updateFlags bool) {
	p.SetPos(&p.appearance.Pos.ClientPitch, pitch, updateFlags)
}

// GetClientPitch returns the client pitch
func (p *Player) GetClientPitch() float32 {
	return p.appearance.Pos.ClientPitch
}

// IsResurrecting returns whether the player is currently resurrecting
func (p *Player) IsResurrecting() bool {
	return p.resurrecting
}

// SetResurrecting sets the player's resurrection state
func (p *Player) SetResurrecting(val bool) {
	p.resurrecting = val
}

// GetAwayMessage returns the player's away message
func (p *Player) GetAwayMessage() string {
	return p.awayMessage
}

// SetAwayMessage sets the player's away message
func (p *Player) SetAwayMessage(message string) {
	p.awayMessage = message
}

// GetIsTracking returns whether the player is tracking
func (p *Player) GetIsTracking() bool {
	return p.isTracking
}

// SetIsTracking sets the player's tracking state
func (p *Player) SetIsTracking(val bool) {
	p.isTracking = val
}

// GetBiography returns the player's biography
func (p *Player) GetBiography() string {
	return p.biography
}

// SetBiography sets the player's biography
func (p *Player) SetBiography(bio string) {
	p.biography = bio
}

// GetGuild returns the player's guild
func (p *Player) GetGuild() *Guild {
	return p.guild
}

// SetGuild sets the player's guild
func (p *Player) SetGuild(guild *Guild) {
	p.guild = guild
}

// SetPendingDeletion marks the player for deletion
func (p *Player) SetPendingDeletion(val bool) {
	p.pendingDeletion = val
}

// GetPendingDeletion returns whether the player is marked for deletion
func (p *Player) GetPendingDeletion() bool {
	return p.pendingDeletion
}

// GetPosPacketSpeed returns the position packet speed
func (p *Player) GetPosPacketSpeed() float32 {
	return p.posPacketSpeed
}

// IsReturningFromLD returns whether the player is returning from linkdead
func (p *Player) IsReturningFromLD() bool {
	return p.returningFromLD
}

// SetReturningFromLD sets whether the player is returning from linkdead
func (p *Player) SetReturningFromLD(val bool) {
	p.returningFromLD = val
}

// GetLastMovementActivity returns the last movement activity value
func (p *Player) GetLastMovementActivity() int16 {
	return p.lastMovementActivity
}

// SetRangeAttack sets whether the player is using ranged attacks
func (p *Player) SetRangeAttack(val bool) {
	p.rangeAttack = val
}

// GetRangeAttack returns whether the player is using ranged attacks
func (p *Player) GetRangeAttack() bool {
	return p.rangeAttack
}

// HasGMVision returns whether the player has GM vision enabled
func (p *Player) HasGMVision() bool {
	return p.gmVision
}

// SetGMVision sets the player's GM vision state
func (p *Player) SetGMVision(val bool) {
	p.gmVision = val
}

// GetCurrentLanguage returns the player's current language ID
func (p *Player) GetCurrentLanguage() int32 {
	return p.currentLanguageID
}

// SetCurrentLanguage sets the player's current language ID
func (p *Player) SetCurrentLanguage(languageID int32) {
	p.currentLanguageID = languageID
}

// SetActiveReward sets whether the player has an active reward
func (p *Player) SetActiveReward(val bool) {
	p.activeReward = val
}

// IsActiveReward returns whether the player has an active reward
func (p *Player) IsActiveReward() bool {
	return p.activeReward
}

// GetActiveFoodUniqueID returns the active food item's unique ID
func (p *Player) GetActiveFoodUniqueID() int64 {
	return p.activeFoodUniqueID.Load()
}

// SetActiveFoodUniqueID sets the active food item's unique ID
func (p *Player) SetActiveFoodUniqueID(uniqueID int32, updateDB bool) {
	p.activeFoodUniqueID.Store(int64(uniqueID))
	if updateDB {
		// TODO: Update database
	}
}

// GetActiveDrinkUniqueID returns the active drink item's unique ID
func (p *Player) GetActiveDrinkUniqueID() int64 {
	return p.activeDrinkUniqueID.Load()
}

// SetActiveDrinkUniqueID sets the active drink item's unique ID
func (p *Player) SetActiveDrinkUniqueID(uniqueID int32, updateDB bool) {
	p.activeDrinkUniqueID.Store(int64(uniqueID))
	if updateDB {
		// TODO: Update database
	}
}

// GetHouseVaultSlots returns the number of house vault slots
func (p *Player) GetHouseVaultSlots() int8 {
	return p.houseVaultSlots
}

// SetHouseVaultSlots sets the number of house vault slots
func (p *Player) SetHouseVaultSlots(slots int8) {
	p.houseVaultSlots = slots
}

// GetCurrentRecipe returns the current recipe ID
func (p *Player) GetCurrentRecipe() int32 {
	return p.currentRecipe
}

// SetCurrentRecipe sets the current recipe ID
func (p *Player) SetCurrentRecipe(recipeID int32) {
	p.currentRecipe = recipeID
}

// GetTempMount returns the temporary mount model ID
func (p *Player) GetTempMount() int32 {
	return p.tmpMountModel
}

// SetTempMount sets the temporary mount model ID
func (p *Player) SetTempMount(id int32) {
	p.tmpMountModel = id
}

// GetTempMountColor returns the temporary mount color
func (p *Player) GetTempMountColor() common.EQ2Color {
	return p.tmpMountColor
}

// SetTempMountColor sets the temporary mount color
func (p *Player) SetTempMountColor(color *common.EQ2Color) {
	p.tmpMountColor = *color
}

// GetTempMountSaddleColor returns the temporary mount saddle color
func (p *Player) GetTempMountSaddleColor() common.EQ2Color {
	return p.tmpMountSaddleColor
}

// SetTempMountSaddleColor sets the temporary mount saddle color
func (p *Player) SetTempMountSaddleColor(color *common.EQ2Color) {
	p.tmpMountSaddleColor = *color
}

// StopSaveSpellEffects returns whether to stop saving spell effects
func (p *Player) StopSaveSpellEffects() bool {
	return p.stopSaveSpellEffects
}

// SetSaveSpellEffects sets whether to save spell effects
func (p *Player) SetSaveSpellEffects(val bool) {
	p.stopSaveSpellEffects = !val
}

// ResetMentorship checks and resets mentorship if needed
func (p *Player) ResetMentorship() bool {
	mentorshipStatus := p.resetMentorship
	if mentorshipStatus {
		p.SetMentorStats(p.GetLevel(), 0, true)
	}
	p.resetMentorship = false
	return mentorshipStatus
}

// EnableResetMentorship enables mentorship reset
func (p *Player) EnableResetMentorship() {
	p.resetMentorship = true
}

// StopCombat stops all combat based on type
func (p *Player) StopCombat(combatType int8) {
	switch combatType {
	case 2:
		p.SetRangeAttack(false)
		p.InCombat(false, true)
	default:
		p.InCombat(false, false)
		p.InCombat(false, true)
		p.SetRangeAttack(false)
	}
}

// GetPVPAlignment returns the player's PVP alignment
func (p *Player) GetPVPAlignment() int {
	// TODO: Implement PVP alignment logic
	return 0
}

// InitXPTable initializes the global XP requirements table
func InitXPTable() {
	xpTableOnce.Do(func() {
		levelXPReq = make(map[int8]int32)
		// TODO: Load XP requirements from database or config
		// For now, using placeholder values
		for i := int8(1); i <= 100; i++ {
			levelXPReq[i] = int32(i * 1000)
		}
	})
}

// GetNeededXPByLevel returns the XP needed for a specific level
func GetNeededXPByLevel(level int8) int32 {
	InitXPTable()
	if xp, exists := levelXPReq[level]; exists {
		return xp
	}
	return 0
}

// Cleanup performs cleanup when the player is being destroyed
func (p *Player) Cleanup() {
	// Set save spell effects
	p.SetSaveSpellEffects(true)

	// Clear spells
	for _, spell := range p.spells {
		spell = nil
	}
	p.spells = nil

	// Clear quickbar
	for _, item := range p.quickbarItems {
		item = nil
	}
	p.quickbarItems = nil

	// Clear quest spawn requirements
	p.playerSpawnQuestsRequiredMutex.Lock()
	for _, list := range p.playerSpawnQuestsRequired {
		list = nil
	}
	p.playerSpawnQuestsRequired = nil
	p.playerSpawnQuestsRequiredMutex.Unlock()

	// Clear history spawn requirements
	p.playerSpawnHistoryRequiredMutex.Lock()
	for _, list := range p.playerSpawnHistoryRequired {
		list = nil
	}
	p.playerSpawnHistoryRequired = nil
	p.playerSpawnHistoryRequiredMutex.Unlock()

	// Clear character history
	for _, typeMap := range p.characterHistory {
		for _, histList := range typeMap {
			for _, hist := range histList {
				hist = nil
			}
		}
	}
	p.characterHistory = nil

	// Clear LUA history
	p.luaHistoryMutex.Lock()
	for _, hist := range p.charLuaHistory {
		hist = nil
	}
	p.charLuaHistory = nil
	p.luaHistoryMutex.Unlock()

	// Clear movement packets
	p.movementPacket = nil
	p.oldMovementPacket = nil
	p.spawnTmpInfoXorPacket = nil
	p.spawnTmpVisXorPacket = nil
	p.spawnTmpPosXorPacket = nil
	p.spellXorPacket = nil
	p.spellOrigPacket = nil
	p.raidOrigPacket = nil
	p.raidXorPacket = nil

	// Destroy quests
	p.DestroyQuests()

	// Write and remove statistics
	p.WritePlayerStatistics()
	p.RemovePlayerStatistics()

	// Delete mail
	p.DeleteMail(false)

	// TODO: Remove from world lotto
	// world.RemoveLottoPlayer(p.GetCharacterID())

	// Clear player info
	p.info = nil

	// Clear spawn maps
	p.indexMutex.Lock()
	p.playerSpawnReverseIDMap = nil
	p.playerSpawnIDMap = nil
	p.indexMutex.Unlock()

	// Clear packet lists
	p.infoMutex.Lock()
	p.spawnInfoPacketList = nil
	p.infoMutex.Unlock()

	p.visMutex.Lock()
	p.spawnVisPacketList = nil
	p.visMutex.Unlock()

	p.posMutex.Lock()
	p.spawnPosPacketList = nil
	p.posMutex.Unlock()

	// Clear packet structures
	p.spawnHeaderStruct = nil
	p.spawnFooterStruct = nil
	p.signFooterStruct = nil
	p.widgetFooterStruct = nil
	p.spawnInfoStruct = nil
	p.spawnVisStruct = nil
	p.spawnPosStruct = nil

	// Clear pending rewards
	p.ClearPendingSelectableItemRewards(0, true)
	p.ClearPendingItemRewards()

	// Clear everything else
	p.ClearEverything()

	// Clear traits
	p.sortedTraitList = nil
	p.classTraining = nil
	p.raceTraits = nil
	p.innateRaceTraits = nil
	p.focusEffects = nil

	// Clear language list
	p.playerLanguagesList.Clear()
}

// DestroyQuests cleans up all quest data
func (p *Player) DestroyQuests() {
	p.playerQuestsMutex.Lock()
	defer p.playerQuestsMutex.Unlock()

	// Clear completed quests
	for id, quest := range p.completedQuests {
		delete(p.completedQuests, id)
		_ = quest
	}

	// Clear active quests
	for id, quest := range p.playerQuests {
		delete(p.playerQuests, id)
		_ = quest
	}

	// Clear pending quests
	for id, quest := range p.pendingQuests {
		delete(p.pendingQuests, id)
		_ = quest
	}
}

// WritePlayerStatistics saves player statistics to database
func (p *Player) WritePlayerStatistics() {
	// TODO: Implement database write
}

// RemovePlayerStatistics removes all player statistics
func (p *Player) RemovePlayerStatistics() {
	for id := range p.statistics {
		delete(p.statistics, id)
	}
}

// DeleteMail deletes all mail
func (p *Player) DeleteMail(fromDatabase bool) {
	p.mailMutex.Lock()
	defer p.mailMutex.Unlock()

	for id, mail := range p.mailList {
		if fromDatabase {
			// TODO: Delete from database
		}
		delete(p.mailList, id)
		_ = mail
	}
}

// ClearPendingSelectableItemRewards clears pending selectable item rewards
func (p *Player) ClearPendingSelectableItemRewards(sourceID int32, all bool) {
	if all {
		for id, items := range p.pendingSelectableItemRewards {
			for _, item := range items {
				_ = item
			}
			delete(p.pendingSelectableItemRewards, id)
		}
	} else if sourceID > 0 {
		if items, exists := p.pendingSelectableItemRewards[sourceID]; exists {
			for _, item := range items {
				_ = item
			}
			delete(p.pendingSelectableItemRewards, sourceID)
		}
	}
}

// ClearPendingItemRewards clears all pending item rewards
func (p *Player) ClearPendingItemRewards() {
	for i := range p.pendingItemRewards {
		p.pendingItemRewards[i] = Item{}
	}
	p.pendingItemRewards = nil
}

// ClearEverything performs final cleanup
func (p *Player) ClearEverything() {
	// TODO: Implement final cleanup logic
}

// GetCharacterInstances returns the character instances manager
func (p *Player) GetCharacterInstances() *CharacterInstances {
	return &p.characterInstances
}

// GetFactions returns the player's faction manager
func (p *Player) GetFactions() *PlayerFaction {
	return &p.factions
}

// SetFactionValue sets a faction value for the player
func (p *Player) SetFactionValue(factionID int32, value int32) {
	p.factions.SetFactionValue(factionID, value)
}

// GetCollectionList returns the player's collection list
func (p *Player) GetCollectionList() *PlayerCollectionList {
	return &p.collectionList
}

// GetRecipeList returns the player's recipe list
func (p *Player) GetRecipeList() *PlayerRecipeList {
	return &p.recipeList
}

// GetRecipeBookList returns the player's recipe book list
func (p *Player) GetRecipeBookList() *PlayerRecipeBookList {
	return &p.recipebookList
}

// GetAchievementList returns the player's achievement list
func (p *Player) GetAchievementList() *PlayerAchievementList {
	return &p.achievementList
}

// GetAchievementUpdateList returns the player's achievement update list
func (p *Player) GetAchievementUpdateList() *PlayerAchievementUpdateList {
	return &p.achievementUpdateList
}

// GetPlayerTitles returns the player's titles list
func (p *Player) GetPlayerTitles() *PlayerTitlesList {
	return &p.playerTitlesList
}

// GetPlayerLanguages returns the player's languages list
func (p *Player) GetPlayerLanguages() *PlayerLanguagesList {
	return &p.playerLanguagesList
}

// GetPlayerItemList returns the player's item list
func (p *Player) GetPlayerItemList() *PlayerItemList {
	return &p.itemList
}

// GetSkills returns the player's skill list
func (p *Player) GetSkills() *PlayerSkillList {
	return &p.skillList
}

// AddGMVisualFilter adds a GM visual filter
func (p *Player) AddGMVisualFilter(filterType, filterValue int32, filterSearchStr string, visualTag int16) {
	filter := GMTagFilter{
		FilterType:  filterType,
		FilterValue: filterValue,
		VisualTag:   visualTag,
	}
	copy(filter.FilterSearchCriteria[:], filterSearchStr)
	p.gmVisualFilters = append(p.gmVisualFilters, filter)
}

// ClearGMVisualFilters clears all GM visual filters
func (p *Player) ClearGMVisualFilters() {
	p.gmVisualFilters = nil
}

// macroIcons map - declared at package level since it was referenced but not in struct
var macroIcons map[int32]int16
