package factions

import (
	"fmt"
	"sync"
)

// Database interface for faction persistence
type Database interface {
	LoadAllFactions() ([]*Faction, error)
	SaveFaction(faction *Faction) error
	DeleteFaction(factionID int32) error
	LoadHostileFactionRelations() ([]*FactionRelation, error)
	LoadFriendlyFactionRelations() ([]*FactionRelation, error)
	SaveFactionRelation(relation *FactionRelation) error
	DeleteFactionRelation(factionID, relatedFactionID int32, isHostile bool) error
}

// Logger interface for faction logging
type Logger interface {
	LogInfo(message string, args ...interface{})
	LogError(message string, args ...interface{})
	LogDebug(message string, args ...interface{})
	LogWarning(message string, args ...interface{})
}

// FactionRelation represents a relationship between two factions
type FactionRelation struct {
	FactionID         int32 // Primary faction ID
	HostileFactionID  int32 // Hostile faction ID (if this is a hostile relation)
	FriendlyFactionID int32 // Friendly faction ID (if this is a friendly relation)
}

// Client interface for faction-related client operations
type Client interface {
	GetVersion() int16
	SendFactionUpdate(factionData []byte) error
	GetCharacterID() int32
}

// Player interface for faction-related player operations
type Player interface {
	GetFactionSystem() *PlayerFaction
	GetCharacterID() int32
	SendMessage(message string)
}

// FactionAware interface for entities that interact with factions
type FactionAware interface {
	GetFactionID() int32
	SetFactionID(factionID int32)
	GetFactionStanding(playerFaction *PlayerFaction) int8
	ShouldAttackPlayer(playerFaction *PlayerFaction) bool
}

// FactionProvider interface for systems that provide faction information
type FactionProvider interface {
	GetMasterFactionList() *MasterFactionList
	GetFaction(factionID int32) *Faction
	GetFactionByName(name string) *Faction
	CreatePlayerFaction() *PlayerFaction
}

// EntityFactionAdapter provides faction functionality for entities
type EntityFactionAdapter struct {
	entity    Entity
	factionID int32
	manager   *Manager
	logger    Logger
	mutex     sync.RWMutex
}

// Entity interface for things that can have faction affiliations
type Entity interface {
	GetID() int32
	GetName() string
	GetDatabaseID() int32
}

// NewEntityFactionAdapter creates a new entity faction adapter
func NewEntityFactionAdapter(entity Entity, manager *Manager, logger Logger) *EntityFactionAdapter {
	return &EntityFactionAdapter{
		entity:    entity,
		factionID: 0,
		manager:   manager,
		logger:    logger,
	}
}

// GetFactionID returns the entity's faction ID
func (efa *EntityFactionAdapter) GetFactionID() int32 {
	efa.mutex.RLock()
	defer efa.mutex.RUnlock()

	return efa.factionID
}

// SetFactionID sets the entity's faction ID
func (efa *EntityFactionAdapter) SetFactionID(factionID int32) {
	efa.mutex.Lock()
	defer efa.mutex.Unlock()

	efa.factionID = factionID

	if efa.logger != nil {
		efa.logger.LogDebug("Entity %d (%s): Set faction ID to %d",
			efa.entity.GetID(), efa.entity.GetName(), factionID)
	}
}

// GetFaction returns the entity's faction object
func (efa *EntityFactionAdapter) GetFaction() *Faction {
	factionID := efa.GetFactionID()
	if factionID == 0 {
		return nil
	}

	if efa.manager == nil {
		if efa.logger != nil {
			efa.logger.LogError("Entity %d (%s): No faction manager available",
				efa.entity.GetID(), efa.entity.GetName())
		}
		return nil
	}

	return efa.manager.GetFaction(factionID)
}

// GetFactionStanding returns the consideration level with a player
func (efa *EntityFactionAdapter) GetFactionStanding(playerFaction *PlayerFaction) int8 {
	factionID := efa.GetFactionID()
	if factionID == 0 || playerFaction == nil {
		return ConIndiff // Indifferent if no faction or player faction
	}

	return playerFaction.GetCon(factionID)
}

// ShouldAttackPlayer returns true if the entity should attack the player based on faction
func (efa *EntityFactionAdapter) ShouldAttackPlayer(playerFaction *PlayerFaction) bool {
	factionID := efa.GetFactionID()
	if factionID == 0 || playerFaction == nil {
		return false // Don't attack if no faction
	}

	return playerFaction.ShouldAttack(factionID)
}

// GetFactionName returns the name of the entity's faction
func (efa *EntityFactionAdapter) GetFactionName() string {
	faction := efa.GetFaction()
	if faction == nil {
		return ""
	}

	return faction.Name
}

// IsHostileToFaction returns true if this entity's faction is hostile to another faction
func (efa *EntityFactionAdapter) IsHostileToFaction(otherFactionID int32) bool {
	factionID := efa.GetFactionID()
	if factionID == 0 || efa.manager == nil {
		return false
	}

	hostileFactions := efa.manager.GetMasterFactionList().GetHostileFactions(factionID)

	for _, hostileID := range hostileFactions {
		if hostileID == otherFactionID {
			return true
		}
	}

	return false
}

// IsFriendlyToFaction returns true if this entity's faction is friendly to another faction
func (efa *EntityFactionAdapter) IsFriendlyToFaction(otherFactionID int32) bool {
	factionID := efa.GetFactionID()
	if factionID == 0 || efa.manager == nil {
		return false
	}

	friendlyFactions := efa.manager.GetMasterFactionList().GetFriendlyFactions(factionID)

	for _, friendlyID := range friendlyFactions {
		if friendlyID == otherFactionID {
			return true
		}
	}

	return false
}

// ValidateFaction validates that the entity's faction exists and is valid
func (efa *EntityFactionAdapter) ValidateFaction() error {
	factionID := efa.GetFactionID()
	if factionID == 0 {
		return nil // No faction is valid
	}

	faction := efa.GetFaction()
	if faction == nil {
		return fmt.Errorf("faction ID %d not found", factionID)
	}

	if !faction.IsValid() {
		return fmt.Errorf("faction ID %d is invalid", factionID)
	}

	return nil
}

// PlayerFactionManager handles faction interactions for a player
type PlayerFactionManager struct {
	playerFaction *PlayerFaction
	manager       *Manager
	player        Player
	logger        Logger
	mutex         sync.RWMutex
}

// NewPlayerFactionManager creates a new player faction manager
func NewPlayerFactionManager(player Player, manager *Manager, logger Logger) *PlayerFactionManager {
	return &PlayerFactionManager{
		playerFaction: manager.CreatePlayerFaction(),
		manager:       manager,
		player:        player,
		logger:        logger,
	}
}

// GetPlayerFaction returns the player's faction system
func (pfm *PlayerFactionManager) GetPlayerFaction() *PlayerFaction {
	return pfm.playerFaction
}

// IncreaseFaction increases a faction and records statistics
func (pfm *PlayerFactionManager) IncreaseFaction(factionID int32, amount int32) bool {
	result := pfm.playerFaction.IncreaseFaction(factionID, amount)

	if result {
		pfm.manager.RecordFactionIncrease(factionID)

		if pfm.logger != nil {
			pfm.logger.LogDebug("Player %d: Increased faction %d by %d",
				pfm.player.GetCharacterID(), factionID, amount)
		}
	}

	return result
}

// DecreaseFaction decreases a faction and records statistics
func (pfm *PlayerFactionManager) DecreaseFaction(factionID int32, amount int32) bool {
	result := pfm.playerFaction.DecreaseFaction(factionID, amount)

	if result {
		pfm.manager.RecordFactionDecrease(factionID)

		if pfm.logger != nil {
			pfm.logger.LogDebug("Player %d: Decreased faction %d by %d",
				pfm.player.GetCharacterID(), factionID, amount)
		}
	}

	return result
}

// SetFactionValue sets a faction to a specific value
func (pfm *PlayerFactionManager) SetFactionValue(factionID int32, value int32) bool {
	result := pfm.playerFaction.SetFactionValue(factionID, value)

	if pfm.logger != nil {
		pfm.logger.LogDebug("Player %d: Set faction %d to %d",
			pfm.player.GetCharacterID(), factionID, value)
	}

	return result
}

// SendFactionUpdates sends pending faction updates to the client
func (pfm *PlayerFactionManager) SendFactionUpdates(client Client) error {
	if client == nil {
		return fmt.Errorf("client is nil")
	}

	if !pfm.playerFaction.HasPendingUpdates() {
		return nil // No updates needed
	}

	packet, err := pfm.playerFaction.FactionUpdate(client.GetVersion())
	if err != nil {
		return fmt.Errorf("failed to build faction update packet: %w", err)
	}

	if packet != nil {
		if err := client.SendFactionUpdate(packet); err != nil {
			return fmt.Errorf("failed to send faction update: %w", err)
		}

		if pfm.logger != nil {
			pfm.logger.LogDebug("Player %d: Sent faction updates to client",
				pfm.player.GetCharacterID())
		}
	}

	return nil
}

// GetFactionStanding returns the player's standing with a faction
func (pfm *PlayerFactionManager) GetFactionStanding(factionID int32) int8 {
	return pfm.playerFaction.GetCon(factionID)
}

// GetFactionValue returns the player's value with a faction
func (pfm *PlayerFactionManager) GetFactionValue(factionID int32) int32 {
	return pfm.playerFaction.GetFactionValue(factionID)
}

// ShouldAttackFaction returns true if the player should attack entities of a faction
func (pfm *PlayerFactionManager) ShouldAttackFaction(factionID int32) bool {
	return pfm.playerFaction.ShouldAttack(factionID)
}

// LoadPlayerFactions loads faction data from database
func (pfm *PlayerFactionManager) LoadPlayerFactions(database Database) error {
	if database == nil {
		return fmt.Errorf("database is nil")
	}

	// Load player faction data from database
	if dbAdapter, ok := database.(*DatabaseAdapter); ok {
		factionData, err := dbAdapter.LoadPlayerFactions(pfm.player.GetCharacterID())
		if err != nil {
			return fmt.Errorf("failed to load player factions: %w", err)
		}

		for factionID, value := range factionData {
			pfm.playerFaction.SetFactionValue(factionID, value)
		}
	}

	if pfm.logger != nil {
		pfm.logger.LogInfo("Player %d: Loaded faction data from database",
			pfm.player.GetCharacterID())
	}

	return nil
}

// SavePlayerFactions saves faction data to database
func (pfm *PlayerFactionManager) SavePlayerFactions(database Database) error {
	if database == nil {
		return fmt.Errorf("database is nil")
	}

	factionValues := pfm.playerFaction.GetFactionValues()

	// Save player faction data to database
	if dbAdapter, ok := database.(*DatabaseAdapter); ok {
		if err := dbAdapter.SaveAllPlayerFactions(pfm.player.GetCharacterID(), factionValues); err != nil {
			return fmt.Errorf("failed to save player factions: %w", err)
		}
	}

	if pfm.logger != nil {
		pfm.logger.LogInfo("Player %d: Saved %d faction values to database",
			pfm.player.GetCharacterID(), len(factionValues))
	}

	return nil
}
