package npc

import (
	"fmt"
	"math/rand"
	"strings"
	"sync"
	"time"
)

// Manager provides high-level management of the NPC system
type Manager struct {
	npcs              map[int32]*NPC    // NPCs indexed by ID
	npcsByZone        map[int32][]*NPC  // NPCs indexed by zone ID
	npcsByAppearance  map[int32][]*NPC  // NPCs indexed by appearance ID
	database          Database          // Database interface
	logger            Logger            // Logger interface
	spellManager      SpellManager      // Spell system interface
	skillManager      SkillManager      // Skill system interface
	appearanceManager AppearanceManager // Appearance system interface
	mutex             sync.RWMutex      // Thread safety

	// Statistics
	totalNPCs        int64
	npcsInCombat     int64
	spellCastCount   int64
	skillUsageCount  int64
	runbackCount     int64
	aiStrategyCounts map[int8]int64

	// Configuration
	maxNPCs            int32
	defaultAggroRadius float32
	enableAI           bool
}

// NewManager creates a new NPC manager
func NewManager(database Database, logger Logger) *Manager {
	return &Manager{
		npcs:               make(map[int32]*NPC),
		npcsByZone:         make(map[int32][]*NPC),
		npcsByAppearance:   make(map[int32][]*NPC),
		database:           database,
		logger:             logger,
		aiStrategyCounts:   make(map[int8]int64),
		maxNPCs:            10000, // Default limit
		defaultAggroRadius: DefaultAggroRadius,
		enableAI:           true,
	}
}

// Initialize loads NPCs from database and sets up the system
func (m *Manager) Initialize() error {
	if m.logger != nil {
		m.logger.LogInfo("Initializing NPC manager...")
	}

	if m.database == nil {
		if m.logger != nil {
			m.logger.LogWarning("No database provided, starting with empty NPC list")
		}
		return nil
	}

	// Load NPCs from database
	npcs, err := m.database.LoadAllNPCs()
	if err != nil {
		return fmt.Errorf("failed to load NPCs from database: %w", err)
	}

	for _, npc := range npcs {
		if err := m.addNPCInternal(npc); err != nil {
			if m.logger != nil {
				m.logger.LogError("Failed to add NPC %d: %v", npc.GetNPCID(), err)
			}
		}
	}

	if m.logger != nil {
		m.logger.LogInfo("Loaded %d NPCs from database", len(npcs))
	}

	return nil
}

// AddNPC adds a new NPC to the system
func (m *Manager) AddNPC(npc *NPC) error {
	if npc == nil {
		return fmt.Errorf("NPC cannot be nil")
	}

	if !npc.IsValid() {
		return fmt.Errorf("NPC is not valid: %s", npc.String())
	}

	m.mutex.Lock()
	defer m.mutex.Unlock()

	if len(m.npcs) >= int(m.maxNPCs) {
		return fmt.Errorf("maximum NPC limit reached (%d)", m.maxNPCs)
	}

	return m.addNPCInternal(npc)
}

// addNPCInternal adds an NPC without locking (internal use)
func (m *Manager) addNPCInternal(npc *NPC) error {
	npcID := npc.GetNPCID()

	// Check for duplicate ID
	if _, exists := m.npcs[npcID]; exists {
		return fmt.Errorf("NPC with ID %d already exists", npcID)
	}

	// Add to main index
	m.npcs[npcID] = npc

	// Add to zone index
	if npc.Entity != nil {
		zoneID := npc.Entity.GetZoneID()
		m.npcsByZone[zoneID] = append(m.npcsByZone[zoneID], npc)
	}

	// Add to appearance index
	appearanceID := npc.GetAppearanceID()
	m.npcsByAppearance[appearanceID] = append(m.npcsByAppearance[appearanceID], npc)

	// Update statistics
	m.totalNPCs++
	strategy := npc.GetAIStrategy()
	m.aiStrategyCounts[strategy]++

	// Save to database if available
	if m.database != nil {
		if err := m.database.SaveNPC(npc); err != nil {
			// Remove from indexes if database save failed
			delete(m.npcs, npcID)
			m.removeFromZoneIndex(npc)
			m.removeFromAppearanceIndex(npc)
			m.totalNPCs--
			m.aiStrategyCounts[strategy]--
			return fmt.Errorf("failed to save NPC to database: %w", err)
		}
	}

	if m.logger != nil {
		m.logger.LogInfo("Added NPC %d: %s", npcID, npc.String())
	}

	return nil
}

// GetNPC retrieves an NPC by ID
func (m *Manager) GetNPC(id int32) *NPC {
	m.mutex.RLock()
	defer m.mutex.RUnlock()

	return m.npcs[id]
}

// GetNPCsByZone retrieves all NPCs in a specific zone
func (m *Manager) GetNPCsByZone(zoneID int32) []*NPC {
	m.mutex.RLock()
	defer m.mutex.RUnlock()

	npcs := m.npcsByZone[zoneID]
	result := make([]*NPC, len(npcs))
	copy(result, npcs)
	return result
}

// GetNPCsByAppearance retrieves all NPCs with a specific appearance
func (m *Manager) GetNPCsByAppearance(appearanceID int32) []*NPC {
	m.mutex.RLock()
	defer m.mutex.RUnlock()

	npcs := m.npcsByAppearance[appearanceID]
	result := make([]*NPC, len(npcs))
	copy(result, npcs)
	return result
}

// RemoveNPC removes an NPC from the system
func (m *Manager) RemoveNPC(id int32) error {
	m.mutex.Lock()
	defer m.mutex.Unlock()

	npc, exists := m.npcs[id]
	if !exists {
		return fmt.Errorf("NPC with ID %d does not exist", id)
	}

	// Remove from database first if available
	if m.database != nil {
		if err := m.database.DeleteNPC(id); err != nil {
			return fmt.Errorf("failed to delete NPC from database: %w", err)
		}
	}

	// Remove from all indexes
	delete(m.npcs, id)
	m.removeFromZoneIndex(npc)
	m.removeFromAppearanceIndex(npc)

	// Update statistics
	m.totalNPCs--
	strategy := npc.GetAIStrategy()
	if count := m.aiStrategyCounts[strategy]; count > 0 {
		m.aiStrategyCounts[strategy]--
	}

	if m.logger != nil {
		m.logger.LogInfo("Removed NPC %d", id)
	}

	return nil
}

// UpdateNPC updates an existing NPC
func (m *Manager) UpdateNPC(npc *NPC) error {
	if npc == nil {
		return fmt.Errorf("NPC cannot be nil")
	}

	if !npc.IsValid() {
		return fmt.Errorf("NPC is not valid: %s", npc.String())
	}

	m.mutex.Lock()
	defer m.mutex.Unlock()

	npcID := npc.GetNPCID()
	oldNPC, exists := m.npcs[npcID]
	if !exists {
		return fmt.Errorf("NPC with ID %d does not exist", npcID)
	}

	// Update indexes if zone or appearance changed
	if npc.Entity != nil && oldNPC.Entity != nil {
		if npc.Entity.GetZoneID() != oldNPC.Entity.GetZoneID() {
			m.removeFromZoneIndex(oldNPC)
			zoneID := npc.Entity.GetZoneID()
			m.npcsByZone[zoneID] = append(m.npcsByZone[zoneID], npc)
		}
	}

	if npc.GetAppearanceID() != oldNPC.GetAppearanceID() {
		m.removeFromAppearanceIndex(oldNPC)
		appearanceID := npc.GetAppearanceID()
		m.npcsByAppearance[appearanceID] = append(m.npcsByAppearance[appearanceID], npc)
	}

	// Update AI strategy statistics
	oldStrategy := oldNPC.GetAIStrategy()
	newStrategy := npc.GetAIStrategy()
	if oldStrategy != newStrategy {
		if count := m.aiStrategyCounts[oldStrategy]; count > 0 {
			m.aiStrategyCounts[oldStrategy]--
		}
		m.aiStrategyCounts[newStrategy]++
	}

	// Update main index
	m.npcs[npcID] = npc

	// Save to database if available
	if m.database != nil {
		if err := m.database.SaveNPC(npc); err != nil {
			return fmt.Errorf("failed to save NPC to database: %w", err)
		}
	}

	if m.logger != nil {
		m.logger.LogInfo("Updated NPC %d: %s", npcID, npc.String())
	}

	return nil
}

// CreateNPCFromTemplate creates a new NPC from an existing template
func (m *Manager) CreateNPCFromTemplate(templateID, newID int32) (*NPC, error) {
	template := m.GetNPC(templateID)
	if template == nil {
		return nil, fmt.Errorf("template NPC with ID %d not found", templateID)
	}

	// Create new NPC from template
	newNPC := NewNPCFromExisting(template)
	newNPC.SetNPCID(newID)

	// Add to system
	if err := m.AddNPC(newNPC); err != nil {
		return nil, fmt.Errorf("failed to add new NPC: %w", err)
	}

	return newNPC, nil
}

// GetRandomNPCByAppearance returns a random NPC with the specified appearance
func (m *Manager) GetRandomNPCByAppearance(appearanceID int32) *NPC {
	npcs := m.GetNPCsByAppearance(appearanceID)
	if len(npcs) == 0 {
		return nil
	}

	return npcs[rand.Intn(len(npcs))]
}

// ProcessCombat handles combat processing for all NPCs
func (m *Manager) ProcessCombat() {
	m.mutex.RLock()
	npcs := make([]*NPC, 0, len(m.npcs))
	for _, npc := range m.npcs {
		if npc.Entity != nil && npc.Entity.GetInCombat() {
			npcs = append(npcs, npc)
		}
	}
	m.mutex.RUnlock()

	// Process combat for each NPC in combat
	for _, npc := range npcs {
		npc.ProcessCombat()
	}

	// Update combat statistics
	m.mutex.Lock()
	m.npcsInCombat = int64(len(npcs))
	m.mutex.Unlock()
}

// ProcessAI handles AI processing for all NPCs
func (m *Manager) ProcessAI() {
	if !m.enableAI {
		return
	}

	m.mutex.RLock()
	npcs := make([]*NPC, 0, len(m.npcs))
	for _, npc := range m.npcs {
		npcs = append(npcs, npc)
	}
	m.mutex.RUnlock()

	// Process AI for each NPC
	for _, npc := range npcs {
		if brain := npc.GetBrain(); brain != nil && brain.IsActive() {
			if err := brain.Think(); err != nil && m.logger != nil {
				m.logger.LogError("AI brain error for NPC %d: %v", npc.GetNPCID(), err)
			}
		}
	}
}

// ProcessMovement handles movement processing for all NPCs
func (m *Manager) ProcessMovement() {
	m.mutex.RLock()
	npcs := make([]*NPC, 0, len(m.npcs))
	for _, npc := range m.npcs {
		npcs = append(npcs, npc)
	}
	m.mutex.RUnlock()

	// Process movement for each NPC
	for _, npc := range npcs {
		// Check pause timer
		if npc.IsPauseMovementTimerActive() {
			continue
		}

		// Handle runback if needed
		if npc.callRunback && npc.GetRunbackLocation() != nil {
			npc.callRunback = false
			npc.Runback(0, true)
			m.mutex.Lock()
			m.runbackCount++
			m.mutex.Unlock()
		}
	}
}

// GetStatistics returns NPC system statistics
func (m *Manager) GetStatistics() *NPCStatistics {
	m.mutex.RLock()
	defer m.mutex.RUnlock()

	// Create AI strategy counts by name
	aiCounts := make(map[string]int)
	for strategy, count := range m.aiStrategyCounts {
		switch strategy {
		case AIStrategyBalanced:
			aiCounts["balanced"] = int(count)
		case AIStrategyOffensive:
			aiCounts["offensive"] = int(count)
		case AIStrategyDefensive:
			aiCounts["defensive"] = int(count)
		default:
			aiCounts[fmt.Sprintf("unknown_%d", strategy)] = int(count)
		}
	}

	// Calculate average aggro radius
	var totalAggro float32
	npcCount := 0
	for _, npc := range m.npcs {
		totalAggro += npc.GetAggroRadius()
		npcCount++
	}
	var avgAggro float32
	if npcCount > 0 {
		avgAggro = totalAggro / float32(npcCount)
	}

	// Count NPCs with spells and skills
	npcsWithSpells := 0
	npcsWithSkills := 0
	for _, npc := range m.npcs {
		if npc.HasSpells() {
			npcsWithSpells++
		}
		if len(npc.skills) > 0 {
			npcsWithSkills++
		}
	}

	return &NPCStatistics{
		TotalNPCs:          int(m.totalNPCs),
		NPCsInCombat:       int(m.npcsInCombat),
		NPCsWithSpells:     npcsWithSpells,
		NPCsWithSkills:     npcsWithSkills,
		AIStrategyCounts:   aiCounts,
		SpellCastCount:     m.spellCastCount,
		SkillUsageCount:    m.skillUsageCount,
		RunbackCount:       m.runbackCount,
		AverageAggroRadius: avgAggro,
	}
}

// ValidateAllNPCs validates all NPCs in the system
func (m *Manager) ValidateAllNPCs() []string {
	m.mutex.RLock()
	npcs := make([]*NPC, 0, len(m.npcs))
	for _, npc := range m.npcs {
		npcs = append(npcs, npc)
	}
	m.mutex.RUnlock()

	var issues []string
	for _, npc := range npcs {
		if !npc.IsValid() {
			issues = append(issues, fmt.Sprintf("NPC %d is invalid: %s", npc.GetNPCID(), npc.String()))
		}
	}

	return issues
}

// ProcessCommand handles NPC-related commands
func (m *Manager) ProcessCommand(command string, args []string) (string, error) {
	switch command {
	case "stats":
		return m.handleStatsCommand(args)
	case "validate":
		return m.handleValidateCommand(args)
	case "list":
		return m.handleListCommand(args)
	case "info":
		return m.handleInfoCommand(args)
	case "create":
		return m.handleCreateCommand(args)
	case "remove":
		return m.handleRemoveCommand(args)
	case "search":
		return m.handleSearchCommand(args)
	case "combat":
		return m.handleCombatCommand(args)
	default:
		return "", fmt.Errorf("unknown NPC command: %s", command)
	}
}

// Command handlers
func (m *Manager) handleStatsCommand(args []string) (string, error) {
	stats := m.GetStatistics()

	result := "NPC System Statistics:\n"
	result += fmt.Sprintf("Total NPCs: %d\n", stats.TotalNPCs)
	result += fmt.Sprintf("NPCs in Combat: %d\n", stats.NPCsInCombat)
	result += fmt.Sprintf("NPCs with Spells: %d\n", stats.NPCsWithSpells)
	result += fmt.Sprintf("NPCs with Skills: %d\n", stats.NPCsWithSkills)
	result += fmt.Sprintf("Average Aggro Radius: %.2f\n", stats.AverageAggroRadius)
	result += fmt.Sprintf("Spell Casts: %d\n", stats.SpellCastCount)
	result += fmt.Sprintf("Skill Uses: %d\n", stats.SkillUsageCount)
	result += fmt.Sprintf("Runbacks: %d\n", stats.RunbackCount)

	if len(stats.AIStrategyCounts) > 0 {
		result += "\nAI Strategy Distribution:\n"
		for strategy, count := range stats.AIStrategyCounts {
			result += fmt.Sprintf("  %s: %d\n", strategy, count)
		}
	}

	return result, nil
}

func (m *Manager) handleValidateCommand(args []string) (string, error) {
	issues := m.ValidateAllNPCs()

	if len(issues) == 0 {
		return "All NPCs are valid.", nil
	}

	result := fmt.Sprintf("Found %d issues with NPCs:\n", len(issues))
	for i, issue := range issues {
		if i >= 10 { // Limit output
			result += "... (and more)\n"
			break
		}
		result += fmt.Sprintf("%d. %s\n", i+1, issue)
	}

	return result, nil
}

func (m *Manager) handleListCommand(args []string) (string, error) {
	m.mutex.RLock()
	npcs := make([]*NPC, 0, len(m.npcs))
	for _, npc := range m.npcs {
		npcs = append(npcs, npc)
	}
	m.mutex.RUnlock()

	if len(npcs) == 0 {
		return "No NPCs loaded.", nil
	}

	result := fmt.Sprintf("NPCs (%d):\n", len(npcs))
	count := 0
	for _, npc := range npcs {
		if count >= 20 { // Limit output
			result += "... (and more)\n"
			break
		}
		result += fmt.Sprintf("  %d: %s\n", npc.GetNPCID(), npc.String())
		count++
	}

	return result, nil
}

func (m *Manager) handleInfoCommand(args []string) (string, error) {
	if len(args) == 0 {
		return "", fmt.Errorf("NPC ID required")
	}

	var npcID int32
	if _, err := fmt.Sscanf(args[0], "%d", &npcID); err != nil {
		return "", fmt.Errorf("invalid NPC ID: %s", args[0])
	}

	npc := m.GetNPC(npcID)
	if npc == nil {
		return fmt.Sprintf("NPC %d not found.", npcID), nil
	}

	result := fmt.Sprintf("NPC Information:\n")
	result += fmt.Sprintf("ID: %d\n", npc.GetNPCID())
	result += fmt.Sprintf("Appearance ID: %d\n", npc.GetAppearanceID())
	result += fmt.Sprintf("AI Strategy: %d\n", npc.GetAIStrategy())
	result += fmt.Sprintf("Cast Percentage: %d%%\n", npc.GetCastPercentage())
	result += fmt.Sprintf("Aggro Radius: %.2f\n", npc.GetAggroRadius())
	result += fmt.Sprintf("Has Spells: %v\n", npc.HasSpells())
	result += fmt.Sprintf("Running Back: %v\n", npc.IsRunningBack())

	if npc.Entity != nil {
		result += fmt.Sprintf("Name: %s\n", npc.Entity.GetName())
		result += fmt.Sprintf("Level: %d\n", npc.Entity.GetLevel())
		result += fmt.Sprintf("Zone: %d\n", npc.Entity.GetZoneID())
		result += fmt.Sprintf("In Combat: %v\n", npc.Entity.GetInCombat())
	}

	return result, nil
}

func (m *Manager) handleCreateCommand(args []string) (string, error) {
	if len(args) < 2 {
		return "", fmt.Errorf("usage: create <template_id> <new_id>")
	}

	var templateID, newID int32
	if _, err := fmt.Sscanf(args[0], "%d", &templateID); err != nil {
		return "", fmt.Errorf("invalid template ID: %s", args[0])
	}
	if _, err := fmt.Sscanf(args[1], "%d", &newID); err != nil {
		return "", fmt.Errorf("invalid new ID: %s", args[1])
	}

	npc, err := m.CreateNPCFromTemplate(templateID, newID)
	if err != nil {
		return "", fmt.Errorf("failed to create NPC: %w", err)
	}

	return fmt.Sprintf("Successfully created NPC %d from template %d", newID, templateID), nil
}

func (m *Manager) handleRemoveCommand(args []string) (string, error) {
	if len(args) == 0 {
		return "", fmt.Errorf("NPC ID required")
	}

	var npcID int32
	if _, err := fmt.Sscanf(args[0], "%d", &npcID); err != nil {
		return "", fmt.Errorf("invalid NPC ID: %s", args[0])
	}

	if err := m.RemoveNPC(npcID); err != nil {
		return "", fmt.Errorf("failed to remove NPC: %w", err)
	}

	return fmt.Sprintf("Successfully removed NPC %d", npcID), nil
}

func (m *Manager) handleSearchCommand(args []string) (string, error) {
	if len(args) == 0 {
		return "", fmt.Errorf("search term required")
	}

	searchTerm := strings.ToLower(args[0])

	m.mutex.RLock()
	var results []*NPC
	for _, npc := range m.npcs {
		if npc.Entity != nil {
			name := strings.ToLower(npc.Entity.GetName())
			if strings.Contains(name, searchTerm) {
				results = append(results, npc)
			}
		}
	}
	m.mutex.RUnlock()

	if len(results) == 0 {
		return fmt.Sprintf("No NPCs found matching '%s'.", args[0]), nil
	}

	result := fmt.Sprintf("Found %d NPCs matching '%s':\n", len(results), args[0])
	for i, npc := range results {
		if i >= 20 { // Limit output
			result += "... (and more)\n"
			break
		}
		result += fmt.Sprintf("  %d: %s\n", npc.GetNPCID(), npc.String())
	}

	return result, nil
}

func (m *Manager) handleCombatCommand(args []string) (string, error) {
	result := "Combat Processing Status:\n"
	result += fmt.Sprintf("NPCs in Combat: %d\n", m.npcsInCombat)
	result += fmt.Sprintf("Total Spell Casts: %d\n", m.spellCastCount)
	result += fmt.Sprintf("Total Runbacks: %d\n", m.runbackCount)

	return result, nil
}

// Helper methods
func (m *Manager) removeFromZoneIndex(npc *NPC) {
	if npc.Entity == nil {
		return
	}

	zoneID := npc.Entity.GetZoneID()
	npcs := m.npcsByZone[zoneID]

	for i, n := range npcs {
		if n == npc {
			// Remove from slice
			m.npcsByZone[zoneID] = append(npcs[:i], npcs[i+1:]...)
			break
		}
	}

	// Clean up empty slices
	if len(m.npcsByZone[zoneID]) == 0 {
		delete(m.npcsByZone, zoneID)
	}
}

func (m *Manager) removeFromAppearanceIndex(npc *NPC) {
	appearanceID := npc.GetAppearanceID()
	npcs := m.npcsByAppearance[appearanceID]

	for i, n := range npcs {
		if n == npc {
			// Remove from slice
			m.npcsByAppearance[appearanceID] = append(npcs[:i], npcs[i+1:]...)
			break
		}
	}

	// Clean up empty slices
	if len(m.npcsByAppearance[appearanceID]) == 0 {
		delete(m.npcsByAppearance, appearanceID)
	}
}

// SetManagers sets the external system managers
func (m *Manager) SetManagers(spellMgr SpellManager, skillMgr SkillManager, appearanceMgr AppearanceManager) {
	m.mutex.Lock()
	defer m.mutex.Unlock()

	m.spellManager = spellMgr
	m.skillManager = skillMgr
	m.appearanceManager = appearanceMgr
}

// Configuration methods
func (m *Manager) SetMaxNPCs(max int32) {
	m.mutex.Lock()
	defer m.mutex.Unlock()
	m.maxNPCs = max
}

func (m *Manager) SetDefaultAggroRadius(radius float32) {
	m.mutex.Lock()
	defer m.mutex.Unlock()
	m.defaultAggroRadius = radius
}

func (m *Manager) SetAIEnabled(enabled bool) {
	m.mutex.Lock()
	defer m.mutex.Unlock()
	m.enableAI = enabled
}

// GetNPCCount returns the total number of NPCs
func (m *Manager) GetNPCCount() int32 {
	m.mutex.RLock()
	defer m.mutex.RUnlock()
	return int32(len(m.npcs))
}

// Shutdown gracefully shuts down the manager
func (m *Manager) Shutdown() {
	if m.logger != nil {
		m.logger.LogInfo("Shutting down NPC manager...")
	}

	// Stop all AI brains
	m.mutex.Lock()
	for _, npc := range m.npcs {
		if brain := npc.GetBrain(); brain != nil {
			brain.SetActive(false)
		}
	}

	// Clear all data
	m.npcs = make(map[int32]*NPC)
	m.npcsByZone = make(map[int32][]*NPC)
	m.npcsByAppearance = make(map[int32][]*NPC)
	m.mutex.Unlock()
}
