package defs

//go:generate go run gen/registration.go

import (
	"fmt"
	"reflect"
	"sort"
	"strings"
	"sync"

	"git.sharkk.net/EQ2/Protocol/opcodes"
)

// PacketStruct represents a packet structure definition
type PacketStruct struct {
	Name        string
	Version     uint16
	OpcodeName  string
	StructType  reflect.Type
}

// PacketManager manages packet structure lookups for specific client versions
type PacketManager struct {
	version      uint16
	structs      map[string][]*PacketStruct // map[name][]versions
	opcodeToName map[opcodes.EmuOpcode]string
	nameToStruct map[string]*PacketStruct // cached lookups for this version
	mu           sync.RWMutex
}

// NewPacketManager creates a new packet manager for the specified version
func NewPacketManager(version uint16) *PacketManager {
	pm := &PacketManager{
		version:      version,
		structs:      make(map[string][]*PacketStruct),
		opcodeToName: make(map[opcodes.EmuOpcode]string),
		nameToStruct: make(map[string]*PacketStruct),
	}
	pm.initializePacketMappings()
	return pm
}

// initializePacketMappings is generated in registrations_generated.go

// registerStruct adds a packet structure definition
func (pm *PacketManager) registerStruct(name string, version uint16, opcodeName string, structType reflect.Type) {
	ps := &PacketStruct{
		Name:       name,
		Version:    version,
		OpcodeName: opcodeName,
		StructType: structType,
	}

	pm.mu.Lock()
	defer pm.mu.Unlock()

	if _, exists := pm.structs[name]; !exists {
		pm.structs[name] = make([]*PacketStruct, 0)
	}
	pm.structs[name] = append(pm.structs[name], ps)

	// Map opcode to struct name for quick lookup
	// We'll need to add this mapping based on the actual opcode constants
	// For now, we map based on the opcode name string
	pm.mapOpcodeToStruct(opcodeName, name)
}

// mapOpcodeToStruct maps opcode names to struct names
// This should be updated when we have a proper opcode name mapping
func (pm *PacketManager) mapOpcodeToStruct(opcodeName string, structName string) {
	// Map common opcodes - this will be expanded as needed
	switch opcodeName {
	case "OP_CreateCharacterRequestMsg":
		pm.opcodeToName[opcodes.OP_CreateCharacterRequestMsg] = structName
	case "OP_BadLanguageFilter":
		pm.opcodeToName[opcodes.OP_BadLanguageFilter] = structName
	// Login opcodes would need to be handled differently since they use LS_ prefix
	// We'll need to decide how to handle login vs world opcodes
	}
}

// GetStruct returns the best matching packet struct for the given name and version
// It finds the highest version that is <= the requested version (like C++ code)
func (pm *PacketManager) GetStruct(name string, version uint16) *PacketStruct {
	pm.mu.RLock()
	defer pm.mu.RUnlock()

	// Check cache first
	cacheKey := fmt.Sprintf("%s_%d", name, version)
	if cached, ok := pm.nameToStruct[cacheKey]; ok {
		return cached
	}

	versions, exists := pm.structs[name]
	if !exists {
		return nil
	}

	var bestMatch *PacketStruct
	for _, ps := range versions {
		// Find the highest version that's <= requested version
		if ps.Version <= version {
			if bestMatch == nil || ps.Version > bestMatch.Version {
				bestMatch = ps
			}
		}
	}

	if bestMatch != nil {
		// Cache the result
		pm.nameToStruct[cacheKey] = bestMatch
	}

	return bestMatch
}

// GetStructByVersion returns an exact version match (like C++ getStructByVersion)
func (pm *PacketManager) GetStructByVersion(name string, version uint16) *PacketStruct {
	pm.mu.RLock()
	defer pm.mu.RUnlock()

	versions, exists := pm.structs[name]
	if !exists {
		return nil
	}

	for _, ps := range versions {
		if ps.Version == version {
			return ps
		}
	}

	return nil
}

// GetStructByOpcode returns the packet struct for a given opcode
func (pm *PacketManager) GetStructByOpcode(opcode opcodes.EmuOpcode) *PacketStruct {
	pm.mu.RLock()
	defer pm.mu.RUnlock()

	name, exists := pm.opcodeToName[opcode]
	if !exists {
		return nil
	}

	return pm.GetStruct(name, pm.version)
}

// CreateInstance creates a new instance of the packet struct
func (pm *PacketManager) CreateInstance(name string) interface{} {
	ps := pm.GetStruct(name, pm.version)
	if ps == nil {
		return nil
	}

	return reflect.New(ps.StructType).Interface()
}

// CreateInstanceByOpcode creates a new instance by opcode
func (pm *PacketManager) CreateInstanceByOpcode(opcode opcodes.EmuOpcode) interface{} {
	ps := pm.GetStructByOpcode(opcode)
	if ps == nil {
		return nil
	}

	return reflect.New(ps.StructType).Interface()
}

// GetAvailableVersions returns all versions available for a struct name
func (pm *PacketManager) GetAvailableVersions(name string) []uint16 {
	pm.mu.RLock()
	defer pm.mu.RUnlock()

	versions, exists := pm.structs[name]
	if !exists {
		return nil
	}

	var result []uint16
	for _, ps := range versions {
		result = append(result, ps.Version)
	}

	sort.Slice(result, func(i, j int) bool {
		return result[i] < result[j]
	})

	return result
}

// GetAllStructNames returns all registered struct names
func (pm *PacketManager) GetAllStructNames() []string {
	pm.mu.RLock()
	defer pm.mu.RUnlock()

	var names []string
	for name := range pm.structs {
		names = append(names, name)
	}

	sort.Strings(names)
	return names
}

// DumpStructs returns a formatted string of all loaded structs for debugging
func (pm *PacketManager) DumpStructs() string {
	pm.mu.RLock()
	defer pm.mu.RUnlock()

	var sb strings.Builder
	sb.WriteString(fmt.Sprintf("Packet Manager for version %d:\n", pm.version))
	sb.WriteString("=====================================\n")

	names := pm.GetAllStructNames()
	for _, name := range names {
		versions := pm.GetAvailableVersions(name)
		sb.WriteString(fmt.Sprintf("%s: versions %v\n", name, versions))
		
		// Show which version would be selected
		if ps := pm.GetStruct(name, pm.version); ps != nil {
			sb.WriteString(fmt.Sprintf("  -> Selected version %d for client %d\n", ps.Version, pm.version))
		}
	}

	return sb.String()
}

// GetStats returns statistics about loaded packet structs
func (pm *PacketManager) GetStats() map[string]int {
	pm.mu.RLock()
	defer pm.mu.RUnlock()

	totalStructs := 0
	for _, versions := range pm.structs {
		totalStructs += len(versions)
	}

	return map[string]int{
		"version":        int(pm.version),
		"unique_structs": len(pm.structs),
		"total_versions": totalStructs,
		"cached_lookups": len(pm.nameToStruct),
	}
}

// Global managers cache
var (
	managers   = make(map[uint16]*PacketManager)
	managersMu sync.RWMutex
)

// GetManager returns packet manager for a version
func GetManager(version uint16) *PacketManager {
	managersMu.RLock()
	if mgr, ok := managers[version]; ok {
		managersMu.RUnlock()
		return mgr
	}
	managersMu.RUnlock()

	managersMu.Lock()
	defer managersMu.Unlock()

	// Double-check after acquiring write lock
	if mgr, ok := managers[version]; ok {
		return mgr
	}

	mgr := NewPacketManager(version)
	managers[version] = mgr
	return mgr
}

// ClearCache clears the cached managers (useful for testing)
func ClearCache() {
	managersMu.Lock()
	defer managersMu.Unlock()
	managers = make(map[uint16]*PacketManager)
}

// GetLoadedVersions returns all versions that have managers loaded
func GetLoadedVersions() []uint16 {
	managersMu.RLock()
	defer managersMu.RUnlock()

	var versions []uint16
	for v := range managers {
		versions = append(versions, v)
	}

	sort.Slice(versions, func(i, j int) bool {
		return versions[i] < versions[j]
	})

	return versions
}