package zone

import (
	"fmt"
	"log"
	"sync"
	"time"

	"eq2emu/internal/database"
)

// ZoneManager manages all active zones in the server
type ZoneManager struct {
	zones          map[int32]*ZoneServer
	zonesByName    map[string]*ZoneServer
	instanceZones  map[int32]*ZoneServer
	db             *database.Database
	config         *ZoneManagerConfig
	shutdownSignal chan struct{}
	isShuttingDown bool
	mutex          sync.RWMutex
	processTimer   *time.Ticker
	cleanupTimer   *time.Ticker
}

// ZoneManagerConfig holds configuration for the zone manager
type ZoneManagerConfig struct {
	MaxZones           int32
	MaxInstanceZones   int32
	ProcessInterval    time.Duration
	CleanupInterval    time.Duration
	DatabasePath       string
	DefaultMapPath     string
	EnableWeather      bool
	EnablePathfinding  bool
	EnableCombat       bool
	EnableSpellProcess bool
	AutoSaveInterval   time.Duration
}

// NewZoneManager creates a new zone manager
func NewZoneManager(config *ZoneManagerConfig, db *database.Database) *ZoneManager {
	if config.ProcessInterval == 0 {
		config.ProcessInterval = time.Millisecond * 100 // 10 FPS default
	}
	if config.CleanupInterval == 0 {
		config.CleanupInterval = time.Minute * 5 // 5 minutes default
	}
	if config.MaxZones == 0 {
		config.MaxZones = 100
	}
	if config.MaxInstanceZones == 0 {
		config.MaxInstanceZones = 1000
	}

	zm := &ZoneManager{
		zones:          make(map[int32]*ZoneServer),
		zonesByName:    make(map[string]*ZoneServer),
		instanceZones:  make(map[int32]*ZoneServer),
		db:             db,
		config:         config,
		shutdownSignal: make(chan struct{}),
	}

	return zm
}

// Start starts the zone manager and its processing loops
func (zm *ZoneManager) Start() error {
	zm.mutex.Lock()
	defer zm.mutex.Unlock()

	if zm.processTimer != nil {
		return fmt.Errorf("zone manager already started")
	}

	// Start processing timers
	zm.processTimer = time.NewTicker(zm.config.ProcessInterval)
	zm.cleanupTimer = time.NewTicker(zm.config.CleanupInterval)

	// Start processing goroutines
	go zm.processLoop()
	go zm.cleanupLoop()

	log.Printf("%s Zone manager started", LogPrefixZone)
	return nil
}

// Stop stops the zone manager and all zones
func (zm *ZoneManager) Stop() error {
	zm.mutex.Lock()
	defer zm.mutex.Unlock()

	if zm.isShuttingDown {
		return fmt.Errorf("zone manager already shutting down")
	}

	zm.isShuttingDown = true
	close(zm.shutdownSignal)

	// Stop timers
	if zm.processTimer != nil {
		zm.processTimer.Stop()
		zm.processTimer = nil
	}
	if zm.cleanupTimer != nil {
		zm.cleanupTimer.Stop()
		zm.cleanupTimer = nil
	}

	// Shutdown all zones
	for _, zone := range zm.zones {
		zone.Shutdown()
	}
	for _, zone := range zm.instanceZones {
		zone.Shutdown()
	}

	log.Printf("%s Zone manager stopped", LogPrefixZone)
	return nil
}

// LoadZone loads a zone by ID
func (zm *ZoneManager) LoadZone(zoneID int32) (*ZoneServer, error) {
	zm.mutex.Lock()
	defer zm.mutex.Unlock()

	// Check if zone is already loaded
	if zone, exists := zm.zones[zoneID]; exists {
		return zone, nil
	}

	// Check zone limit
	if int32(len(zm.zones)) >= zm.config.MaxZones {
		return nil, fmt.Errorf("maximum zones reached (%d)", zm.config.MaxZones)
	}

	// Load zone data from database
	zoneDB := NewZoneDatabase(zm.db.DB)
	zoneData, err := zoneDB.LoadZoneData(zoneID)
	if err != nil {
		return nil, fmt.Errorf("failed to load zone data: %v", err)
	}

	// Create zone server
	zoneServer := NewZoneServer(zoneData.Configuration.Name)

	// Configure zone server
	config := &ZoneServerConfig{
		ZoneName:          zoneData.Configuration.Name,
		ZoneFile:          zoneData.Configuration.File,
		ZoneDescription:   zoneData.Configuration.Description,
		ZoneID:            zoneID,
		InstanceID:        0, // Not an instance
		InstanceType:      InstanceTypeNone,
		DatabasePath:      zm.config.DatabasePath,
		MaxPlayers:        zoneData.Configuration.MaxPlayers,
		MinLevel:          zoneData.Configuration.MinLevel,
		MaxLevel:          zoneData.Configuration.MaxLevel,
		SafeX:             zoneData.Configuration.SafeX,
		SafeY:             zoneData.Configuration.SafeY,
		SafeZ:             zoneData.Configuration.SafeZ,
		SafeHeading:       zoneData.Configuration.SafeHeading,
		LoadMaps:          true,
		EnableWeather:     zm.config.EnableWeather && zoneData.Configuration.WeatherAllowed,
		EnablePathfinding: zm.config.EnablePathfinding,
	}

	// Initialize zone server
	if err := zoneServer.Initialize(config); err != nil {
		return nil, fmt.Errorf("failed to initialize zone server: %v", err)
	}

	// Add to manager
	zm.zones[zoneID] = zoneServer
	zm.zonesByName[zoneData.Configuration.Name] = zoneServer

	log.Printf("%s Loaded zone '%s' (ID: %d)", LogPrefixZone, zoneData.Configuration.Name, zoneID)
	return zoneServer, nil
}

// UnloadZone unloads a zone by ID
func (zm *ZoneManager) UnloadZone(zoneID int32) error {
	zm.mutex.Lock()
	defer zm.mutex.Unlock()

	zone, exists := zm.zones[zoneID]
	if !exists {
		return fmt.Errorf("zone %d not found", zoneID)
	}

	// Check if zone has players
	if zone.GetNumPlayers() > 0 {
		return fmt.Errorf("cannot unload zone with active players")
	}

	// Shutdown zone
	zone.Shutdown()

	// Wait for shutdown to complete
	timeout := time.After(time.Second * 30)
	ticker := time.NewTicker(time.Millisecond * 100)
	defer ticker.Stop()

	for {
		select {
		case <-timeout:
			log.Printf("%s Warning: zone %d shutdown timed out", LogPrefixZone, zoneID)
			break
		case <-ticker.C:
			if zone.IsShuttingDown() {
				break
			}
		}
		break
	}

	// Remove from manager
	delete(zm.zones, zoneID)
	delete(zm.zonesByName, zone.GetZoneName())

	log.Printf("%s Unloaded zone '%s' (ID: %d)", LogPrefixZone, zone.GetZoneName(), zoneID)
	return nil
}

// CreateInstance creates a new instance zone
func (zm *ZoneManager) CreateInstance(baseZoneID int32, instanceType InstanceType, creatorID uint32) (*ZoneServer, error) {
	zm.mutex.Lock()
	defer zm.mutex.Unlock()

	// Check instance limit
	if int32(len(zm.instanceZones)) >= zm.config.MaxInstanceZones {
		return nil, fmt.Errorf("maximum instance zones reached (%d)", zm.config.MaxInstanceZones)
	}

	// Load base zone data
	zoneDB := NewZoneDatabase(zm.db.DB)
	zoneData, err := zoneDB.LoadZoneData(baseZoneID)
	if err != nil {
		return nil, fmt.Errorf("failed to load base zone data: %v", err)
	}

	// Generate instance ID
	instanceID := zm.generateInstanceID()

	// Create instance zone
	instanceName := fmt.Sprintf("%s_instance_%d", zoneData.Configuration.Name, instanceID)
	zoneServer := NewZoneServer(instanceName)

	// Configure instance zone
	config := &ZoneServerConfig{
		ZoneName:          instanceName,
		ZoneFile:          zoneData.Configuration.File,
		ZoneDescription:   zoneData.Configuration.Description,
		ZoneID:            baseZoneID,
		InstanceID:        instanceID,
		InstanceType:      instanceType,
		DatabasePath:      zm.config.DatabasePath,
		MaxPlayers:        zm.getInstanceMaxPlayers(instanceType),
		MinLevel:          zoneData.Configuration.MinLevel,
		MaxLevel:          zoneData.Configuration.MaxLevel,
		SafeX:             zoneData.Configuration.SafeX,
		SafeY:             zoneData.Configuration.SafeY,
		SafeZ:             zoneData.Configuration.SafeZ,
		SafeHeading:       zoneData.Configuration.SafeHeading,
		LoadMaps:          true,
		EnableWeather:     zm.config.EnableWeather && zoneData.Configuration.WeatherAllowed,
		EnablePathfinding: zm.config.EnablePathfinding,
	}

	// Initialize instance zone
	if err := zoneServer.Initialize(config); err != nil {
		return nil, fmt.Errorf("failed to initialize instance zone: %v", err)
	}

	// Add to manager
	zm.instanceZones[instanceID] = zoneServer

	log.Printf("%s Created instance zone '%s' (ID: %d, Type: %s)",
		LogPrefixZone, instanceName, instanceID, instanceType.String())

	return zoneServer, nil
}

// DestroyInstance destroys an instance zone
func (zm *ZoneManager) DestroyInstance(instanceID int32) error {
	zm.mutex.Lock()
	defer zm.mutex.Unlock()

	zone, exists := zm.instanceZones[instanceID]
	if !exists {
		return fmt.Errorf("instance %d not found", instanceID)
	}

	// Shutdown instance
	zone.Shutdown()

	// Remove from manager
	delete(zm.instanceZones, instanceID)

	log.Printf("%s Destroyed instance zone '%s' (ID: %d)",
		LogPrefixZone, zone.GetZoneName(), instanceID)

	return nil
}

// GetZone retrieves a zone by ID
func (zm *ZoneManager) GetZone(zoneID int32) *ZoneServer {
	zm.mutex.RLock()
	defer zm.mutex.RUnlock()

	return zm.zones[zoneID]
}

// GetZoneByName retrieves a zone by name
func (zm *ZoneManager) GetZoneByName(name string) *ZoneServer {
	zm.mutex.RLock()
	defer zm.mutex.RUnlock()

	return zm.zonesByName[name]
}

// GetInstance retrieves an instance zone by ID
func (zm *ZoneManager) GetInstance(instanceID int32) *ZoneServer {
	zm.mutex.RLock()
	defer zm.mutex.RUnlock()

	return zm.instanceZones[instanceID]
}

// GetAllZones returns a list of all active zones
func (zm *ZoneManager) GetAllZones() []*ZoneServer {
	zm.mutex.RLock()
	defer zm.mutex.RUnlock()

	zones := make([]*ZoneServer, 0, len(zm.zones))
	for _, zone := range zm.zones {
		zones = append(zones, zone)
	}

	return zones
}

// GetAllInstances returns a list of all active instances
func (zm *ZoneManager) GetAllInstances() []*ZoneServer {
	zm.mutex.RLock()
	defer zm.mutex.RUnlock()

	instances := make([]*ZoneServer, 0, len(zm.instanceZones))
	for _, instance := range zm.instanceZones {
		instances = append(instances, instance)
	}

	return instances
}

// GetZoneCount returns the number of active zones
func (zm *ZoneManager) GetZoneCount() int {
	zm.mutex.RLock()
	defer zm.mutex.RUnlock()

	return len(zm.zones)
}

// GetInstanceCount returns the number of active instances
func (zm *ZoneManager) GetInstanceCount() int {
	zm.mutex.RLock()
	defer zm.mutex.RUnlock()

	return len(zm.instanceZones)
}

// GetTotalPlayerCount returns the total number of players across all zones
func (zm *ZoneManager) GetTotalPlayerCount() int32 {
	zm.mutex.RLock()
	defer zm.mutex.RUnlock()

	var total int32
	for _, zone := range zm.zones {
		total += zone.GetNumPlayers()
	}
	for _, instance := range zm.instanceZones {
		total += instance.GetNumPlayers()
	}

	return total
}

// BroadcastMessage sends a message to all players in all zones
func (zm *ZoneManager) BroadcastMessage(message string) {
	zm.mutex.RLock()
	zones := make([]*ZoneServer, 0, len(zm.zones)+len(zm.instanceZones))
	for _, zone := range zm.zones {
		zones = append(zones, zone)
	}
	for _, instance := range zm.instanceZones {
		zones = append(zones, instance)
	}
	zm.mutex.RUnlock()

	for _, zone := range zones {
		// TODO: Implement broadcast message to zone
		_ = zone
		_ = message
	}
}

// GetStatistics returns zone manager statistics
func (zm *ZoneManager) GetStatistics() *ZoneManagerStatistics {
	zm.mutex.RLock()
	defer zm.mutex.RUnlock()

	stats := &ZoneManagerStatistics{
		TotalZones:      int32(len(zm.zones)),
		TotalInstances:  int32(len(zm.instanceZones)),
		TotalPlayers:    zm.GetTotalPlayerCount(),
		MaxZones:        zm.config.MaxZones,
		MaxInstances:    zm.config.MaxInstanceZones,
		ZoneDetails:     make([]*ZoneStatistics, 0),
		InstanceDetails: make([]*ZoneStatistics, 0),
	}

	for _, zone := range zm.zones {
		zoneStats := &ZoneStatistics{
			ZoneID:       zone.GetZoneID(),
			ZoneName:     zone.GetZoneName(),
			PlayerCount:  zone.GetNumPlayers(),
			MaxPlayers:   zone.GetMaxPlayers(),
			IsLocked:     zone.IsLocked(),
			WatchdogTime: zone.GetWatchdogTime(),
		}
		stats.ZoneDetails = append(stats.ZoneDetails, zoneStats)
	}

	for _, instance := range zm.instanceZones {
		instanceStats := &ZoneStatistics{
			ZoneID:       instance.GetZoneID(),
			InstanceID:   instance.GetInstanceID(),
			ZoneName:     instance.GetZoneName(),
			PlayerCount:  instance.GetNumPlayers(),
			MaxPlayers:   instance.GetMaxPlayers(),
			IsLocked:     instance.IsLocked(),
			WatchdogTime: instance.GetWatchdogTime(),
		}
		stats.InstanceDetails = append(stats.InstanceDetails, instanceStats)
	}

	return stats
}

// Private methods

func (zm *ZoneManager) processLoop() {
	defer func() {
		if r := recover(); r != nil {
			log.Printf("%s Zone manager process loop panic: %v", LogPrefixZone, r)
		}
	}()

	for {
		select {
		case <-zm.shutdownSignal:
			return
		case <-zm.processTimer.C:
			zm.processAllZones()
		}
	}
}

func (zm *ZoneManager) cleanupLoop() {
	defer func() {
		if r := recover(); r != nil {
			log.Printf("%s Zone manager cleanup loop panic: %v", LogPrefixZone, r)
		}
	}()

	for {
		select {
		case <-zm.shutdownSignal:
			return
		case <-zm.cleanupTimer.C:
			zm.cleanupInactiveInstances()
		}
	}
}

func (zm *ZoneManager) processAllZones() {
	// Get all zones to process
	zm.mutex.RLock()
	zones := make([]*ZoneServer, 0, len(zm.zones)+len(zm.instanceZones))
	for _, zone := range zm.zones {
		zones = append(zones, zone)
	}
	for _, instance := range zm.instanceZones {
		zones = append(zones, instance)
	}
	zm.mutex.RUnlock()

	// Process each zone
	for _, zone := range zones {
		if zone.IsShuttingDown() {
			continue
		}

		// Process main zone logic
		if err := zone.Process(); err != nil {
			log.Printf("%s Error processing zone '%s': %v", LogPrefixZone, zone.GetZoneName(), err)
		}

		// Process spawn logic
		if err := zone.SpawnProcess(); err != nil {
			log.Printf("%s Error processing spawns in zone '%s': %v", LogPrefixZone, zone.GetZoneName(), err)
		}
	}
}

func (zm *ZoneManager) cleanupInactiveInstances() {
	zm.mutex.Lock()
	defer zm.mutex.Unlock()

	instancesToRemove := make([]int32, 0)

	for instanceID, instance := range zm.instanceZones {
		// Remove instances with no players that have been inactive
		if instance.GetNumPlayers() == 0 {
			// Check how long instance has been empty
			// TODO: Track instance creation/last activity time
			instancesToRemove = append(instancesToRemove, instanceID)
		}
	}

	// Remove inactive instances
	for _, instanceID := range instancesToRemove {
		if instance, exists := zm.instanceZones[instanceID]; exists {
			instance.Shutdown()
			delete(zm.instanceZones, instanceID)
			log.Printf("%s Cleaned up inactive instance %d", LogPrefixZone, instanceID)
		}
	}
}

func (zm *ZoneManager) generateInstanceID() int32 {
	// Simple instance ID generation - start from 1000 and increment
	// In production, this should be more sophisticated
	instanceID := int32(1000)
	for {
		if _, exists := zm.instanceZones[instanceID]; !exists {
			return instanceID
		}
		instanceID++
		if instanceID > 999999 { // Wrap around to prevent overflow
			instanceID = 1000
		}
	}
}

func (zm *ZoneManager) getInstanceMaxPlayers(instanceType InstanceType) int32 {
	switch instanceType {
	case InstanceTypeGroupLockout, InstanceTypeGroupPersist:
		return 6
	case InstanceTypeRaidLockout, InstanceTypeRaidPersist:
		return 24
	case InstanceTypeSoloLockout, InstanceTypeSoloPersist:
		return 1
	case InstanceTypeTradeskill, InstanceTypePublic:
		return 50
	case InstanceTypePersonalHouse:
		return 10
	case InstanceTypeGuildHouse:
		return 50
	case InstanceTypeQuest:
		return 6
	default:
		return 6
	}
}

// ZoneManagerStatistics holds statistics about the zone manager
type ZoneManagerStatistics struct {
	TotalZones      int32             `json:"total_zones"`
	TotalInstances  int32             `json:"total_instances"`
	TotalPlayers    int32             `json:"total_players"`
	MaxZones        int32             `json:"max_zones"`
	MaxInstances    int32             `json:"max_instances"`
	ZoneDetails     []*ZoneStatistics `json:"zone_details"`
	InstanceDetails []*ZoneStatistics `json:"instance_details"`
}

// ZoneStatistics holds statistics about a specific zone
type ZoneStatistics struct {
	ZoneID       int32  `json:"zone_id"`
	InstanceID   int32  `json:"instance_id,omitempty"`
	ZoneName     string `json:"zone_name"`
	PlayerCount  int32  `json:"player_count"`
	MaxPlayers   int32  `json:"max_players"`
	IsLocked     bool   `json:"is_locked"`
	WatchdogTime int32  `json:"watchdog_time"`
}
