package rules

import (
	"fmt"
	"strconv"
	"sync"
)

// Rule represents a single rule with category, type, and value
// Converted from C++ Rule class
type Rule struct {
	category RuleCategory // Rule category (Client, Player, etc.)
	ruleType RuleType     // Rule type within category
	value    string       // Rule value as string
	combined string       // Combined category:type string
	mutex    sync.RWMutex // Thread safety
}

// NewRule creates a new rule with default values
func NewRule() *Rule {
	return &Rule{
		category: 0,
		ruleType: 0,
		value:    "",
		combined: "NONE",
	}
}

// NewRuleWithValues creates a new rule with specified values
func NewRuleWithValues(category RuleCategory, ruleType RuleType, value string, combined string) *Rule {
	return &Rule{
		category: category,
		ruleType: ruleType,
		value:    value,
		combined: combined,
	}
}

// NewRuleFromRule creates a copy of another rule
func NewRuleFromRule(source *Rule) *Rule {
	if source == nil {
		return NewRule()
	}

	source.mutex.RLock()
	defer source.mutex.RUnlock()

	return &Rule{
		category: source.category,
		ruleType: source.ruleType,
		value:    source.value,
		combined: source.combined,
	}
}

// SetValue sets the rule value
func (r *Rule) SetValue(value string) {
	r.mutex.Lock()
	defer r.mutex.Unlock()
	r.value = value
}

// GetCategory returns the rule category
func (r *Rule) GetCategory() RuleCategory {
	r.mutex.RLock()
	defer r.mutex.RUnlock()
	return r.category
}

// GetType returns the rule type
func (r *Rule) GetType() RuleType {
	r.mutex.RLock()
	defer r.mutex.RUnlock()
	return r.ruleType
}

// GetValue returns the rule value as string
func (r *Rule) GetValue() string {
	r.mutex.RLock()
	defer r.mutex.RUnlock()
	return r.value
}

// GetCombined returns the combined category:type string
func (r *Rule) GetCombined() string {
	r.mutex.RLock()
	defer r.mutex.RUnlock()
	return r.combined
}

// Type conversion methods matching C++ implementation

// GetInt8 returns the rule value as int8
func (r *Rule) GetInt8() int8 {
	r.mutex.RLock()
	defer r.mutex.RUnlock()
	if val, err := strconv.ParseInt(r.value, 10, 8); err == nil {
		return int8(val)
	}
	return 0
}

// GetInt16 returns the rule value as int16
func (r *Rule) GetInt16() int16 {
	r.mutex.RLock()
	defer r.mutex.RUnlock()
	if val, err := strconv.ParseInt(r.value, 10, 16); err == nil {
		return int16(val)
	}
	return 0
}

// GetInt32 returns the rule value as int32
func (r *Rule) GetInt32() int32 {
	r.mutex.RLock()
	defer r.mutex.RUnlock()
	if val, err := strconv.ParseInt(r.value, 10, 32); err == nil {
		return int32(val)
	}
	return 0
}

// GetInt64 returns the rule value as int64
func (r *Rule) GetInt64() int64 {
	r.mutex.RLock()
	defer r.mutex.RUnlock()
	if val, err := strconv.ParseInt(r.value, 10, 64); err == nil {
		return val
	}
	return 0
}

// GetUInt8 returns the rule value as uint8
func (r *Rule) GetUInt8() uint8 {
	r.mutex.RLock()
	defer r.mutex.RUnlock()
	if val, err := strconv.ParseUint(r.value, 10, 8); err == nil {
		return uint8(val)
	}
	return 0
}

// GetUInt16 returns the rule value as uint16
func (r *Rule) GetUInt16() uint16 {
	r.mutex.RLock()
	defer r.mutex.RUnlock()
	if val, err := strconv.ParseUint(r.value, 10, 16); err == nil {
		return uint16(val)
	}
	return 0
}

// GetUInt32 returns the rule value as uint32
func (r *Rule) GetUInt32() uint32 {
	r.mutex.RLock()
	defer r.mutex.RUnlock()
	if val, err := strconv.ParseUint(r.value, 10, 32); err == nil {
		return uint32(val)
	}
	return 0
}

// GetUInt64 returns the rule value as uint64
func (r *Rule) GetUInt64() uint64 {
	r.mutex.RLock()
	defer r.mutex.RUnlock()
	if val, err := strconv.ParseUint(r.value, 10, 64); err == nil {
		return val
	}
	return 0
}

// GetBool returns the rule value as bool (> 0 = true)
func (r *Rule) GetBool() bool {
	r.mutex.RLock()
	defer r.mutex.RUnlock()
	if val, err := strconv.ParseUint(r.value, 10, 32); err == nil {
		return val > 0
	}
	return false
}

// GetFloat32 returns the rule value as float32
func (r *Rule) GetFloat32() float32 {
	r.mutex.RLock()
	defer r.mutex.RUnlock()
	if val, err := strconv.ParseFloat(r.value, 32); err == nil {
		return float32(val)
	}
	return 0.0
}

// GetFloat64 returns the rule value as float64
func (r *Rule) GetFloat64() float64 {
	r.mutex.RLock()
	defer r.mutex.RUnlock()
	if val, err := strconv.ParseFloat(r.value, 64); err == nil {
		return val
	}
	return 0.0
}

// GetChar returns the first character of the rule value
func (r *Rule) GetChar() byte {
	r.mutex.RLock()
	defer r.mutex.RUnlock()
	if len(r.value) > 0 {
		return r.value[0]
	}
	return 0
}

// GetString returns the rule value as string (same as GetValue)
func (r *Rule) GetString() string {
	return r.GetValue()
}

// IsValid checks if the rule has valid data
func (r *Rule) IsValid() bool {
	r.mutex.RLock()
	defer r.mutex.RUnlock()
	return r.combined != "" && r.combined != "NONE"
}

// String returns a string representation of the rule
func (r *Rule) String() string {
	r.mutex.RLock()
	defer r.mutex.RUnlock()
	return fmt.Sprintf("Rule{%s: %s}", r.combined, r.value)
}

// RuleSet represents a collection of rules with a name and ID
// Converted from C++ RuleSet class
type RuleSet struct {
	id    int32                               // Rule set ID
	name  string                              // Rule set name
	rules map[RuleCategory]map[RuleType]*Rule // Rules organized by category and type
	mutex sync.RWMutex                        // Thread safety
}

// NewRuleSet creates a new empty rule set
func NewRuleSet() *RuleSet {
	return &RuleSet{
		id:    0,
		name:  "",
		rules: make(map[RuleCategory]map[RuleType]*Rule),
	}
}

// NewRuleSetFromRuleSet creates a copy of another rule set
func NewRuleSetFromRuleSet(source *RuleSet) *RuleSet {
	if source == nil {
		return NewRuleSet()
	}

	ruleSet := &RuleSet{
		id:    source.GetID(),
		name:  source.GetName(),
		rules: make(map[RuleCategory]map[RuleType]*Rule),
	}

	// Deep copy all rules
	source.mutex.RLock()
	for category, typeMap := range source.rules {
		ruleSet.rules[category] = make(map[RuleType]*Rule)
		for ruleType, rule := range typeMap {
			ruleSet.rules[category][ruleType] = NewRuleFromRule(rule)
		}
	}
	source.mutex.RUnlock()

	return ruleSet
}

// SetID sets the rule set ID
func (rs *RuleSet) SetID(id int32) {
	rs.mutex.Lock()
	defer rs.mutex.Unlock()
	rs.id = id
}

// SetName sets the rule set name
func (rs *RuleSet) SetName(name string) {
	rs.mutex.Lock()
	defer rs.mutex.Unlock()
	rs.name = name
}

// GetID returns the rule set ID
func (rs *RuleSet) GetID() int32 {
	rs.mutex.RLock()
	defer rs.mutex.RUnlock()
	return rs.id
}

// GetName returns the rule set name
func (rs *RuleSet) GetName() string {
	rs.mutex.RLock()
	defer rs.mutex.RUnlock()
	return rs.name
}

// AddRule adds a rule to the rule set
func (rs *RuleSet) AddRule(rule *Rule) {
	if rule == nil {
		return
	}

	rs.mutex.Lock()
	defer rs.mutex.Unlock()

	category := rule.GetCategory()
	ruleType := rule.GetType()

	// Initialize category map if it doesn't exist
	if rs.rules[category] == nil {
		rs.rules[category] = make(map[RuleType]*Rule)
	}

	// If rule already exists, just update the value
	if existingRule, exists := rs.rules[category][ruleType]; exists {
		existingRule.SetValue(rule.GetValue())
	} else {
		// Add new rule
		rs.rules[category][ruleType] = rule
	}
}

// GetRule retrieves a rule by category and type
func (rs *RuleSet) GetRule(category RuleCategory, ruleType RuleType) *Rule {
	rs.mutex.RLock()
	defer rs.mutex.RUnlock()

	if categoryMap, exists := rs.rules[category]; exists {
		if rule, exists := categoryMap[ruleType]; exists {
			return rule
		}
	}

	return nil
}

// GetRuleByName retrieves a rule by category and type names
func (rs *RuleSet) GetRuleByName(categoryName string, typeName string) *Rule {
	combined := fmt.Sprintf("%s:%s", categoryName, typeName)

	rs.mutex.RLock()
	defer rs.mutex.RUnlock()

	// Search through all rules for matching combined string
	for _, categoryMap := range rs.rules {
		for _, rule := range categoryMap {
			if rule.GetCombined() == combined {
				return rule
			}
		}
	}

	return nil
}

// CopyRulesInto copies rules from another rule set into this one
func (rs *RuleSet) CopyRulesInto(source *RuleSet) {
	if source == nil {
		return
	}

	rs.ClearRules()

	rs.mutex.Lock()
	defer rs.mutex.Unlock()

	source.mutex.RLock()
	defer source.mutex.RUnlock()

	// Deep copy all rules from source
	for category, typeMap := range source.rules {
		rs.rules[category] = make(map[RuleType]*Rule)
		for ruleType, rule := range typeMap {
			rs.rules[category][ruleType] = NewRuleFromRule(rule)
		}
	}
}

// ClearRules removes all rules from the rule set
func (rs *RuleSet) ClearRules() {
	rs.mutex.Lock()
	defer rs.mutex.Unlock()
	rs.rules = make(map[RuleCategory]map[RuleType]*Rule)
}

// GetRules returns the rules map (for iteration - use carefully)
func (rs *RuleSet) GetRules() map[RuleCategory]map[RuleType]*Rule {
	rs.mutex.RLock()
	defer rs.mutex.RUnlock()

	// Return a deep copy to prevent external modification
	rulesCopy := make(map[RuleCategory]map[RuleType]*Rule)
	for category, typeMap := range rs.rules {
		rulesCopy[category] = make(map[RuleType]*Rule)
		for ruleType, rule := range typeMap {
			rulesCopy[category][ruleType] = NewRuleFromRule(rule)
		}
	}

	return rulesCopy
}

// Size returns the total number of rules in the rule set
func (rs *RuleSet) Size() int {
	rs.mutex.RLock()
	defer rs.mutex.RUnlock()

	count := 0
	for _, typeMap := range rs.rules {
		count += len(typeMap)
	}
	return count
}

// GetRulesByCategory returns all rules in a specific category
func (rs *RuleSet) GetRulesByCategory(category RuleCategory) map[RuleType]*Rule {
	rs.mutex.RLock()
	defer rs.mutex.RUnlock()

	if categoryMap, exists := rs.rules[category]; exists {
		// Return a copy to prevent external modification
		rulesCopy := make(map[RuleType]*Rule)
		for ruleType, rule := range categoryMap {
			rulesCopy[ruleType] = NewRuleFromRule(rule)
		}
		return rulesCopy
	}

	return make(map[RuleType]*Rule)
}

// HasRule checks if a rule exists in the rule set
func (rs *RuleSet) HasRule(category RuleCategory, ruleType RuleType) bool {
	rs.mutex.RLock()
	defer rs.mutex.RUnlock()

	if categoryMap, exists := rs.rules[category]; exists {
		_, exists := categoryMap[ruleType]
		return exists
	}

	return false
}

// String returns a string representation of the rule set
func (rs *RuleSet) String() string {
	rs.mutex.RLock()
	defer rs.mutex.RUnlock()
	return fmt.Sprintf("RuleSet{ID: %d, Name: %s, Rules: %d}", rs.id, rs.name, rs.Size())
}

// RuleManagerStats tracks rule system usage and performance metrics
type RuleManagerStats struct {
	TotalRuleSets      int32 // Total rule sets loaded
	TotalRules         int32 // Total rules across all sets
	GlobalRuleSetID    int32 // Current global rule set ID
	ZoneRuleSets       int32 // Number of zone-specific rule sets
	RuleGetOperations  int64 // Number of rule get operations
	RuleSetOperations  int64 // Number of rule set operations
	DatabaseOperations int64 // Number of database operations
	mutex              sync.RWMutex
}

// IncrementRuleGetOperations increments the rule get counter
func (rms *RuleManagerStats) IncrementRuleGetOperations() {
	rms.mutex.Lock()
	defer rms.mutex.Unlock()
	rms.RuleGetOperations++
}

// IncrementRuleSetOperations increments the rule set counter
func (rms *RuleManagerStats) IncrementRuleSetOperations() {
	rms.mutex.Lock()
	defer rms.mutex.Unlock()
	rms.RuleSetOperations++
}

// IncrementDatabaseOperations increments the database operations counter
func (rms *RuleManagerStats) IncrementDatabaseOperations() {
	rms.mutex.Lock()
	defer rms.mutex.Unlock()
	rms.DatabaseOperations++
}

// GetSnapshot returns a snapshot of the current statistics
func (rms *RuleManagerStats) GetSnapshot() RuleManagerStats {
	rms.mutex.RLock()
	defer rms.mutex.RUnlock()

	return RuleManagerStats{
		TotalRuleSets:      rms.TotalRuleSets,
		TotalRules:         rms.TotalRules,
		GlobalRuleSetID:    rms.GlobalRuleSetID,
		ZoneRuleSets:       rms.ZoneRuleSets,
		RuleGetOperations:  rms.RuleGetOperations,
		RuleSetOperations:  rms.RuleSetOperations,
		DatabaseOperations: rms.DatabaseOperations,
	}
}

// Reset resets all statistics counters
func (rms *RuleManagerStats) Reset() {
	rms.mutex.Lock()
	defer rms.mutex.Unlock()

	rms.TotalRuleSets = 0
	rms.TotalRules = 0
	rms.GlobalRuleSetID = 0
	rms.ZoneRuleSets = 0
	rms.RuleGetOperations = 0
	rms.RuleSetOperations = 0
	rms.DatabaseOperations = 0
}
