package traits

import (
	"fmt"
	"time"
)

// SpellManager defines the interface for spell-related operations needed by traits.
type SpellManager interface {
	// GetSpell retrieves spell information by ID and tier
	GetSpell(spellID uint32, tier int8) (Spell, error)

	// GetPlayerSpells gets all spells known by a player
	GetPlayerSpells(playerID uint32) ([]uint32, error)

	// PlayerHasSpell checks if a player has a specific spell
	PlayerHasSpell(playerID uint32, spellID uint32, tier int8) (bool, error)

	// GetSpellsBySkill gets spells associated with a skill
	GetSpellsBySkill(playerID uint32, skillID uint32) ([]uint32, error)
}

// ItemManager defines the interface for item-related operations needed by traits.
type ItemManager interface {
	// GetItem retrieves item information by ID
	GetItem(itemID uint32) (Item, error)

	// GetPlayerItems gets items from a player's inventory
	GetPlayerItems(playerID uint32) ([]Item, error)

	// GiveItemToPlayer adds an item to a player's inventory
	GiveItemToPlayer(playerID uint32, itemID uint32, quantity int16) error
}

// PlayerManager defines the interface for player-related operations needed by traits.
type PlayerManager interface {
	// GetPlayer retrieves player information by ID
	GetPlayer(playerID uint32) (Player, error)

	// GetPlayerLevel gets a player's current level
	GetPlayerLevel(playerID uint32) (int16, error)

	// GetPlayerClass gets a player's adventure class
	GetPlayerClass(playerID uint32) (int8, error)

	// GetPlayerRace gets a player's race
	GetPlayerRace(playerID uint32) (int8, error)

	// SendMessageToPlayer sends a message to a player
	SendMessageToPlayer(playerID uint32, channel int8, message string) error
}

// PacketManager defines the interface for packet-related operations.
type PacketManager interface {
	// SendPacketToPlayer sends a packet to a specific player
	SendPacketToPlayer(playerID uint32, packetData []byte) error

	// QueuePacketForPlayer queues a packet for delayed sending
	QueuePacketForPlayer(playerID uint32, packetData []byte) error

	// GetClientVersion gets the client version for a player
	GetClientVersion(playerID uint32) (int16, error)
}

// RuleManager defines the interface for rules/configuration access.
type RuleManager interface {
	// GetBool retrieves a boolean rule value
	GetBool(category, rule string) bool

	// GetInt32 retrieves an int32 rule value
	GetInt32(category, rule string) int32

	// GetFloat retrieves a float rule value
	GetFloat(category, rule string) float32
}

// DatabaseService defines the interface for trait persistence operations.
type DatabaseService interface {
	// LoadTraits loads all traits from the database
	LoadTraits(masterList *MasterTraitList) error

	// SaveTrait saves a trait to the database
	SaveTrait(trait *TraitData) error

	// DeleteTrait removes a trait from the database
	DeleteTrait(spellID uint32) error

	// LoadPlayerTraits loads a player's selected traits
	LoadPlayerTraits(playerID uint32) (map[uint32]bool, error)

	// SavePlayerTraits saves a player's selected traits
	SavePlayerTraits(playerID uint32, selectedTraits map[uint32]bool) error
}

// Data structures used by the interfaces

// Spell represents a spell in the game.
type Spell interface {
	GetID() uint32
	GetName() string
	GetIcon() uint32
	GetIconBackdrop() uint32
	GetTier() int8
}

// Item represents an item in the game.
type Item interface {
	GetID() uint32
	GetName() string
	GetIcon() uint32
	GetCount() int16
}

// Player represents a player in the game.
type Player interface {
	GetID() uint32
	GetName() string
	GetLevel() int16
	GetAdventureClass() int8
	GetRace() int8
}

// TraitSystemAdapter provides a high-level interface to the complete trait system.
type TraitSystemAdapter struct {
	masterList      *MasterTraitList
	traitManager    *TraitManager
	packetBuilder   TraitPacketBuilder
	spellManager    SpellManager
	itemManager     ItemManager
	playerManager   PlayerManager
	packetManager   PacketManager
	ruleManager     RuleManager
	databaseService DatabaseService
	config          *TraitSystemConfig
}

// NewTraitSystemAdapter creates a new trait system adapter with all dependencies.
func NewTraitSystemAdapter(
	masterList *MasterTraitList,
	traitManager *TraitManager,
	packetBuilder TraitPacketBuilder,
	spellManager SpellManager,
	itemManager ItemManager,
	playerManager PlayerManager,
	packetManager PacketManager,
	ruleManager RuleManager,
	databaseService DatabaseService,
) *TraitSystemAdapter {
	config := &TraitSystemConfig{
		TieringSelection:     ruleManager.GetBool("Player", RuleTraitTieringSelection),
		UseClassicLevelTable: ruleManager.GetBool("Player", RuleClassicTraitLevelTable),
		FocusSelectLevel:     ruleManager.GetInt32("Player", RuleTraitFocusSelectLevel),
		TrainingSelectLevel:  ruleManager.GetInt32("Player", RuleTraitTrainingSelectLevel),
		RaceSelectLevel:      ruleManager.GetInt32("Player", RuleTraitRaceSelectLevel),
		CharacterSelectLevel: ruleManager.GetInt32("Player", RuleTraitCharacterSelectLevel),
	}

	return &TraitSystemAdapter{
		masterList:      masterList,
		traitManager:    traitManager,
		packetBuilder:   packetBuilder,
		spellManager:    spellManager,
		itemManager:     itemManager,
		playerManager:   playerManager,
		packetManager:   packetManager,
		ruleManager:     ruleManager,
		databaseService: databaseService,
		config:          config,
	}
}

// Initialize sets up the trait system (loads traits from database, etc.).
func (tsa *TraitSystemAdapter) Initialize() error {
	// Load traits from database
	err := tsa.databaseService.LoadTraits(tsa.masterList)
	if err != nil {
		return err
	}

	return nil
}

// GetTraitListPacket builds and returns the trait list packet for a player.
func (tsa *TraitSystemAdapter) GetTraitListPacket(playerID uint32) ([]byte, error) {
	// Get player information
	player, err := tsa.playerManager.GetPlayer(playerID)
	if err != nil {
		return nil, err
	}

	// Get or create player trait state
	playerState := tsa.traitManager.GetPlayerState(
		playerID,
		player.GetLevel(),
		player.GetAdventureClass(),
		player.GetRace(),
	)

	// Load player's selected traits if needed
	if len(playerState.SelectedTraits) == 0 {
		selectedTraits, err := tsa.databaseService.LoadPlayerTraits(playerID)
		if err == nil {
			playerState.SelectedTraits = selectedTraits
		}
	}

	// Generate trait lists
	if !tsa.masterList.GenerateTraitLists(playerState, playerState.Level, UnassignedGroupID) {
		return nil, fmt.Errorf("failed to generate trait lists for player %d", playerID)
	}

	// Get client version
	clientVersion, err := tsa.packetManager.GetClientVersion(playerID)
	if err != nil {
		clientVersion = 1200 // Default to a modern version
	}

	// Build packet data
	packetData, err := tsa.packetBuilder.BuildTraitListPacket(playerState, clientVersion)
	if err != nil {
		return nil, err
	}

	// Convert to actual packet bytes (this would be implemented by the packet system)
	// For now, return a placeholder
	return tsa.serializeTraitPacket(packetData, clientVersion)
}

// ChooseNextTrait handles automatic trait selection for level-up rewards.
func (tsa *TraitSystemAdapter) ChooseNextTrait(playerID uint32) error {
	// Get player information
	player, err := tsa.playerManager.GetPlayer(playerID)
	if err != nil {
		return err
	}

	// Get player trait state
	playerState := tsa.traitManager.GetPlayerState(
		playerID,
		player.GetLevel(),
		player.GetAdventureClass(),
		player.GetRace(),
	)

	// Load player's selected traits
	selectedTraits, err := tsa.databaseService.LoadPlayerTraits(playerID)
	if err == nil {
		playerState.SelectedTraits = selectedTraits
	}

	// Get available trait choices
	availableTraits, err := tsa.traitManager.GetAvailableTraits(
		playerID,
		player.GetLevel(),
		player.GetAdventureClass(),
		player.GetRace(),
	)
	if err != nil {
		return err
	}

	if len(availableTraits) == 0 {
		return nil // No traits available for selection
	}

	// Determine packet type based on trait types
	packetType := tsa.determinePacketType(availableTraits, player.GetAdventureClass(), player.GetRace())

	// Build trait reward packet
	rewardPacket, err := tsa.packetBuilder.BuildTraitRewardPacket(availableTraits, packetType)
	if err != nil {
		return err
	}

	// Send reward selection packet to player
	return tsa.sendTraitRewardPacket(playerID, rewardPacket)
}

// SelectTraits processes a player's trait selections.
func (tsa *TraitSystemAdapter) SelectTraits(playerID uint32, selectedSpells []uint32) error {
	// Get player information
	player, err := tsa.playerManager.GetPlayer(playerID)
	if err != nil {
		return err
	}

	// Create selection request
	request := &TraitSelectionRequest{
		PlayerID:    playerID,
		TraitSpells: selectedSpells,
		PacketType:  0, // Will be determined based on traits
	}

	// Process the selection
	err = tsa.traitManager.SelectTraits(
		request,
		player.GetLevel(),
		player.GetAdventureClass(),
		player.GetRace(),
	)
	if err != nil {
		return err
	}

	// Get updated player state
	playerState := tsa.traitManager.GetPlayerState(
		playerID,
		player.GetLevel(),
		player.GetAdventureClass(),
		player.GetRace(),
	)

	// Save player's trait selections
	err = tsa.databaseService.SavePlayerTraits(playerID, playerState.SelectedTraits)
	if err != nil {
		return err
	}

	// Send confirmation message
	message := fmt.Sprintf("You have learned %d new trait(s).", len(selectedSpells))
	return tsa.playerManager.SendMessageToPlayer(playerID, 4, message) // CHANNEL_NARRATIVE
}

// IsPlayerAllowedTrait checks if a player is allowed to select a specific trait.
func (tsa *TraitSystemAdapter) IsPlayerAllowedTrait(playerID uint32, spellID uint32) (bool, error) {
	// Get trait
	trait := tsa.masterList.GetTrait(spellID)
	if trait == nil {
		return false, ErrTraitNotFound
	}

	// Get player information
	player, err := tsa.playerManager.GetPlayer(playerID)
	if err != nil {
		return false, err
	}

	// Get player trait state
	playerState := tsa.traitManager.GetPlayerState(
		playerID,
		player.GetLevel(),
		player.GetAdventureClass(),
		player.GetRace(),
	)

	// Check if allowed
	return tsa.masterList.IsPlayerAllowedTrait(playerState, trait, tsa.config), nil
}

// GetPlayerTraitStats returns statistics about a player's trait selections.
func (tsa *TraitSystemAdapter) GetPlayerTraitStats(playerID uint32) (map[string]interface{}, error) {
	// Get player information
	player, err := tsa.playerManager.GetPlayer(playerID)
	if err != nil {
		return nil, err
	}

	// Get player trait state
	playerState := tsa.traitManager.GetPlayerState(
		playerID,
		player.GetLevel(),
		player.GetAdventureClass(),
		player.GetRace(),
	)

	// Load selected traits if needed
	if len(playerState.SelectedTraits) == 0 {
		selectedTraits, err := tsa.databaseService.LoadPlayerTraits(playerID)
		if err == nil {
			playerState.SelectedTraits = selectedTraits
		}
	}

	// Generate trait lists for counting
	tsa.masterList.GenerateTraitLists(playerState, playerState.Level, UnassignedGroupID)

	// Count trait selections by type
	characterTraits := tsa.masterList.getSpellCount(playerState, playerState.TraitLists.SortedTraitList, true)
	classTraining := tsa.masterList.getSpellCount(playerState, playerState.TraitLists.ClassTraining, false)
	racialTraits := tsa.masterList.getSpellCount(playerState, playerState.TraitLists.RaceTraits, false) +
		tsa.masterList.getSpellCount(playerState, playerState.TraitLists.InnateRaceTraits, false)
	focusEffects := tsa.masterList.getSpellCount(playerState, playerState.TraitLists.FocusEffects, false)

	return map[string]interface{}{
		"player_id":        playerID,
		"level":            playerState.Level,
		"character_traits": characterTraits,
		"class_training":   classTraining,
		"racial_traits":    racialTraits,
		"focus_effects":    focusEffects,
		"total_selected":   len(playerState.SelectedTraits),
		"last_update":      time.Now(),
	}, nil
}

// GetSystemStats returns comprehensive statistics about the trait system.
func (tsa *TraitSystemAdapter) GetSystemStats() map[string]interface{} {
	masterStats := tsa.masterList.GetStats()
	managerStats := tsa.traitManager.GetManagerStats()

	return map[string]interface{}{
		"total_traits":        masterStats.TotalTraits,
		"traits_by_type":      masterStats.TraitsByType,
		"traits_by_group":     masterStats.TraitsByGroup,
		"traits_by_level":     masterStats.TraitsByLevel,
		"players_with_traits": managerStats.PlayersWithTraits,
		"config":              tsa.config,
		"last_update":         time.Now(),
	}
}

// RefreshConfiguration reloads configuration from rules.
func (tsa *TraitSystemAdapter) RefreshConfiguration() {
	tsa.config.TieringSelection = tsa.ruleManager.GetBool("Player", RuleTraitTieringSelection)
	tsa.config.UseClassicLevelTable = tsa.ruleManager.GetBool("Player", RuleClassicTraitLevelTable)
	tsa.config.FocusSelectLevel = tsa.ruleManager.GetInt32("Player", RuleTraitFocusSelectLevel)
	tsa.config.TrainingSelectLevel = tsa.ruleManager.GetInt32("Player", RuleTraitTrainingSelectLevel)
	tsa.config.RaceSelectLevel = tsa.ruleManager.GetInt32("Player", RuleTraitRaceSelectLevel)
	tsa.config.CharacterSelectLevel = tsa.ruleManager.GetInt32("Player", RuleTraitCharacterSelectLevel)

	// Update trait manager config
	tsa.traitManager.config = tsa.config
}

// ClearPlayerData removes cached data for a player (e.g., when they log out).
func (tsa *TraitSystemAdapter) ClearPlayerData(playerID uint32) {
	tsa.traitManager.ClearPlayerState(playerID)
}

// AddTrait adds a new trait to the master list.
func (tsa *TraitSystemAdapter) AddTrait(trait *TraitData) error {
	err := tsa.masterList.AddTrait(trait)
	if err != nil {
		return err
	}

	// Save to database
	return tsa.databaseService.SaveTrait(trait)
}

// RemoveTrait removes a trait from the master list.
func (tsa *TraitSystemAdapter) RemoveTrait(spellID uint32) error {
	// Remove from database first
	err := tsa.databaseService.DeleteTrait(spellID)
	if err != nil {
		return err
	}

	// Reload traits from database to update master list
	tsa.masterList.DestroyTraits()
	return tsa.databaseService.LoadTraits(tsa.masterList)
}

// Helper methods

// determinePacketType determines the appropriate packet type for a list of traits.
func (tsa *TraitSystemAdapter) determinePacketType(traits []*TraitData, playerClass, playerRace int8) int8 {
	if len(traits) == 0 {
		return PacketTypeCharacterTrait
	}

	// Use the first trait to determine packet type
	trait := traits[0]

	if trait.IsUniversalTrait() {
		return PacketTypeCharacterTrait
	}

	if trait.ClassReq == playerClass && trait.IsTraining {
		return PacketTypeSpecializedTraining
	}

	if trait.RaceReq == playerRace {
		return PacketTypeRacialTradition
	}

	return PacketTypeEnemyMastery
}

// serializeTraitPacket converts trait packet data to byte array.
func (tsa *TraitSystemAdapter) serializeTraitPacket(packetData *TraitPacketData, clientVersion int16) ([]byte, error) {
	// This would be implemented by the actual packet system
	// For now, return a placeholder indicating successful packet creation
	return []byte("TRAIT_PACKET_PLACEHOLDER"), nil
}

// sendTraitRewardPacket sends a trait reward packet to a player.
func (tsa *TraitSystemAdapter) sendTraitRewardPacket(playerID uint32, rewardPacket *TraitRewardPacket) error {
	// This would serialize the reward packet and send it via the packet manager
	// For now, return success
	return nil
}

// TraitEventHandler handles trait-related events.
type TraitEventHandler struct {
	adapter *TraitSystemAdapter
}

// NewTraitEventHandler creates a new trait event handler.
func NewTraitEventHandler(adapter *TraitSystemAdapter) *TraitEventHandler {
	return &TraitEventHandler{
		adapter: adapter,
	}
}

// OnPlayerLevelUp handles player level up events to check for new trait availability.
func (teh *TraitEventHandler) OnPlayerLevelUp(playerID uint32, newLevel int16) error {
	// Check if player should get trait selection opportunity
	return teh.adapter.ChooseNextTrait(playerID)
}

// OnPlayerLogin handles player login events to refresh trait data.
func (teh *TraitEventHandler) OnPlayerLogin(playerID uint32) error {
	// Refresh player's trait packet
	_, err := teh.adapter.GetTraitListPacket(playerID)
	return err
}

// OnPlayerLogout handles player logout events to clean up cached data.
func (teh *TraitEventHandler) OnPlayerLogout(playerID uint32) {
	teh.adapter.ClearPlayerData(playerID)
}

// MockImplementations for testing

// MockSpellManager is a mock implementation of SpellManager for testing.
type MockSpellManager struct {
	spells map[uint32]MockSpell
}

// MockSpell is a mock implementation of Spell for testing.
type MockSpell struct {
	id           uint32
	name         string
	icon         uint32
	iconBackdrop uint32
	tier         int8
}

func (ms MockSpell) GetID() uint32           { return ms.id }
func (ms MockSpell) GetName() string         { return ms.name }
func (ms MockSpell) GetIcon() uint32         { return ms.icon }
func (ms MockSpell) GetIconBackdrop() uint32 { return ms.iconBackdrop }
func (ms MockSpell) GetTier() int8           { return ms.tier }

func (msm *MockSpellManager) GetSpell(spellID uint32, tier int8) (Spell, error) {
	if spell, exists := msm.spells[spellID]; exists {
		return spell, nil
	}
	return nil, fmt.Errorf("spell not found: %d", spellID)
}

func (msm *MockSpellManager) GetPlayerSpells(playerID uint32) ([]uint32, error) {
	return []uint32{}, nil
}

func (msm *MockSpellManager) PlayerHasSpell(playerID uint32, spellID uint32, tier int8) (bool, error) {
	return false, nil
}

func (msm *MockSpellManager) GetSpellsBySkill(playerID uint32, skillID uint32) ([]uint32, error) {
	return []uint32{}, nil
}

// NewMockSpellManager creates a new mock spell manager.
func NewMockSpellManager() *MockSpellManager {
	return &MockSpellManager{
		spells: make(map[uint32]MockSpell),
	}
}

// AddMockSpell adds a mock spell for testing.
func (msm *MockSpellManager) AddMockSpell(id uint32, name string, icon uint32, tier int8) {
	msm.spells[id] = MockSpell{
		id:           id,
		name:         name,
		icon:         icon,
		iconBackdrop: icon + 1000,
		tier:         tier,
	}
}
