package users

import (
	"fmt"
	"slices"
	"sort"
	"strings"
	"time"

	"dk/internal/helpers"
	"dk/internal/helpers/exp"

	nigiri "git.sharkk.net/Sharkk/Nigiri"
)

// User represents a user in the game
type User struct {
	ID         int    `json:"id"`
	Username   string `json:"username" db:"required,unique"`
	Password   string `json:"password" db:"required"`
	Email      string `json:"email" db:"required,unique"`
	Verified   int    `json:"verified"`
	Token      string `json:"token"`
	Registered int64  `json:"registered"`
	LastOnline int64  `json:"last_online"`
	Auth       int    `json:"auth"`
	X          int    `json:"x"`
	Y          int    `json:"y"`
	ClassID    int    `json:"class_id"`
	Currently  string `json:"currently"`
	FightID    int    `json:"fight_id"`
	HP         int    `json:"hp"`
	MP         int    `json:"mp"`
	TP         int    `json:"tp"`
	MaxHP      int    `json:"max_hp"`
	MaxMP      int    `json:"max_mp"`
	MaxTP      int    `json:"max_tp"`
	Level      int    `json:"level" db:"index"`
	Gold       int    `json:"gold"`
	Exp        int    `json:"exp"`
	GoldBonus  int    `json:"gold_bonus"`
	ExpBonus   int    `json:"exp_bonus"`
	Strength   int    `json:"strength"`
	Dexterity  int    `json:"dexterity"`
	Attack     int    `json:"attack"`
	Defense    int    `json:"defense"`
	WeaponID   int    `json:"weapon_id"`
	ArmorID    int    `json:"armor_id"`
	ShieldID   int    `json:"shield_id"`
	Slot1ID    int    `json:"slot_1_id"`
	Slot2ID    int    `json:"slot_2_id"`
	Slot3ID    int    `json:"slot_3_id"`
	WeaponName string `json:"weapon_name"`
	ArmorName  string `json:"armor_name"`
	ShieldName string `json:"shield_name"`
	Slot1Name  string `json:"slot_1_name"`
	Slot2Name  string `json:"slot_2_name"`
	Slot3Name  string `json:"slot_3_name"`
	Spells     string `json:"spells"`
	Towns      string `json:"towns"`
}

// Global store
var store *nigiri.BaseStore[User]

// Init sets up the Nigiri store and indices
func Init(collection *nigiri.Collection) {
	store = nigiri.NewBaseStore[User]()

	// Register custom indices
	store.RegisterIndex("byUsername", nigiri.BuildCaseInsensitiveLookupIndex(func(u *User) string {
		return u.Username
	}))

	store.RegisterIndex("byEmail", nigiri.BuildStringLookupIndex(func(u *User) string {
		return u.Email
	}))

	store.RegisterIndex("byLevel", nigiri.BuildIntGroupIndex(func(u *User) int {
		return u.Level
	}))

	store.RegisterIndex("allByRegistered", nigiri.BuildSortedListIndex(func(a, b *User) bool {
		if a.Registered != b.Registered {
			return a.Registered > b.Registered // DESC
		}
		return a.ID > b.ID // DESC
	}))

	store.RegisterIndex("allByLevelExp", nigiri.BuildSortedListIndex(func(a, b *User) bool {
		if a.Level != b.Level {
			return a.Level > b.Level // Level DESC
		}
		if a.Exp != b.Exp {
			return a.Exp > b.Exp // Exp DESC
		}
		return a.ID < b.ID // ID ASC
	}))

	store.RebuildIndices()
}

// GetStore returns the users store
func GetStore() *nigiri.BaseStore[User] {
	if store == nil {
		panic("users store not initialized - call Initialize first")
	}
	return store
}

// New creates a new User with sensible defaults
func New() *User {
	now := time.Now().Unix()
	return &User{
		Verified:   0,
		Token:      "",
		Registered: now,
		LastOnline: now,
		Auth:       0,
		X:          0,
		Y:          0,
		ClassID:    1,
		Currently:  "In Town",
		FightID:    0,
		HP:         10,
		MP:         10,
		TP:         10,
		MaxHP:      10,
		MaxMP:      10,
		MaxTP:      10,
		Level:      1,
		Gold:       100,
		Exp:        0,
		Strength:   0,
		Dexterity:  0,
		Attack:     0,
		Defense:    0,
		Spells:     "",
		Towns:      "",
	}
}

// Validate checks if user has valid values
func (u *User) Validate() error {
	if strings.TrimSpace(u.Username) == "" {
		return fmt.Errorf("user username cannot be empty")
	}
	if strings.TrimSpace(u.Email) == "" {
		return fmt.Errorf("user email cannot be empty")
	}
	if u.Registered <= 0 {
		return fmt.Errorf("user Registered timestamp must be positive")
	}
	if u.LastOnline <= 0 {
		return fmt.Errorf("user LastOnline timestamp must be positive")
	}
	if u.Level < 1 {
		return fmt.Errorf("user Level must be at least 1")
	}
	if u.HP < 0 {
		return fmt.Errorf("user HP cannot be negative")
	}
	if u.MaxHP < 1 {
		return fmt.Errorf("user MaxHP must be at least 1")
	}
	return nil
}

// CRUD operations
func (u *User) Save() error {
	if u.ID == 0 {
		id, err := store.Create(u)
		if err != nil {
			return err
		}
		u.ID = id
		return nil
	}
	return store.Update(u.ID, u)
}

func (u *User) Delete() error {
	store.Remove(u.ID)
	return nil
}

// Insert with ID assignment
func (u *User) Insert() error {
	id, err := store.Create(u)
	if err != nil {
		return err
	}
	u.ID = id
	return nil
}

// Query functions
func Find(id int) (*User, error) {
	user, exists := store.Find(id)
	if !exists {
		return nil, fmt.Errorf("user with ID %d not found", id)
	}
	return user, nil
}

func GetByID(id int) *User {
	user, exists := store.Find(id)
	if !exists {
		return nil
	}
	return user
}

func All() ([]*User, error) {
	return store.AllSorted("allByRegistered"), nil
}

func ByUsername(username string) (*User, error) {
	user, exists := store.LookupByIndex("byUsername", strings.ToLower(username))
	if !exists {
		return nil, fmt.Errorf("user with username '%s' not found", username)
	}
	return user, nil
}

func ByEmail(email string) (*User, error) {
	user, exists := store.LookupByIndex("Email_idx", email)
	if !exists {
		return nil, fmt.Errorf("user with email '%s' not found", email)
	}
	return user, nil
}

func ByLevel(level int) ([]*User, error) {
	return store.GroupByIndex("level_idx", level), nil
}

func Online(within time.Duration) ([]*User, error) {
	cutoff := time.Now().Add(-within).Unix()

	result := store.FilterByIndex("allByRegistered", func(u *User) bool {
		return u.LastOnline >= cutoff
	})

	// Sort by last_online DESC, then ID ASC
	sort.Slice(result, func(i, j int) bool {
		if result[i].LastOnline != result[j].LastOnline {
			return result[i].LastOnline > result[j].LastOnline // DESC
		}
		return result[i].ID < result[j].ID // ASC
	})

	return result, nil
}

// Helper methods
func (u *User) RegisteredTime() time.Time {
	return time.Unix(u.Registered, 0)
}

func (u *User) LastOnlineTime() time.Time {
	return time.Unix(u.LastOnline, 0)
}

func (u *User) UpdateLastOnline() {
	u.LastOnline = time.Now().Unix()
}

func (u *User) IsVerified() bool {
	return u.Verified == 1
}

func (u *User) IsAdmin() bool {
	return u.Auth >= 4
}

func (u *User) IsModerator() bool {
	return u.Auth >= 3
}

func (u *User) IsFighting() bool {
	return u.FightID > 0
}

func (u *User) IsAlive() bool {
	return u.HP > 0
}

func (u *User) GetSpellIDs() []int {
	return helpers.StringToInts(u.Spells)
}

func (u *User) SetSpellIDs(spells []int) {
	u.Spells = helpers.IntsToString(spells)
}

func (u *User) HasSpell(spellID int) bool {
	return slices.Contains(u.GetSpellIDs(), spellID)
}

func (u *User) GetTownIDs() []int {
	return helpers.StringToInts(u.Towns)
}

func (u *User) SetTownIDs(towns []int) {
	u.Towns = helpers.IntsToString(towns)
}

func (u *User) HasTownMap(townID int) bool {
	return slices.Contains(u.GetTownIDs(), townID)
}

func (u *User) GetEquipment() map[string]any {
	return map[string]any{
		"weapon": map[string]any{"id": u.WeaponID, "name": u.WeaponName},
		"armor":  map[string]any{"id": u.ArmorID, "name": u.ArmorName},
		"shield": map[string]any{"id": u.ShieldID, "name": u.ShieldName},
		"slot1":  map[string]any{"id": u.Slot1ID, "name": u.Slot1Name},
		"slot2":  map[string]any{"id": u.Slot2ID, "name": u.Slot2Name},
		"slot3":  map[string]any{"id": u.Slot3ID, "name": u.Slot3Name},
	}
}

func (u *User) GetStats() map[string]int {
	return map[string]int{
		"level":     u.Level,
		"hp":        u.HP,
		"mp":        u.MP,
		"tp":        u.TP,
		"max_hp":    u.MaxHP,
		"max_mp":    u.MaxMP,
		"max_tp":    u.MaxTP,
		"strength":  u.Strength,
		"dexterity": u.Dexterity,
		"attack":    u.Attack,
		"defense":   u.Defense,
	}
}

func (u *User) GetPosition() (int, int) {
	return u.X, u.Y
}

func (u *User) SetPosition(x, y int) {
	u.X = x
	u.Y = y
}

func (u *User) ExpNeededForNextLevel() int {
	return exp.Calc(u.Level + 1)
}

func (u *User) GrantExp(expAmount int) {
	u.Exp += expAmount
	u.checkLevelUp()
}

func (u *User) checkLevelUp() {
	expNeeded := u.ExpNeededForNextLevel()

	if u.Exp >= expNeeded {
		// Level up
		u.Level++
		u.Strength++
		u.Dexterity++

		// Reset exp and carry over excess
		excessExp := u.Exp - expNeeded
		u.Exp = 0

		// Recursive level up if enough excess exp
		if excessExp > 0 {
			u.GrantExp(excessExp)
		}
	}
}

func (u *User) ExpProgress() float64 {
	if u.Level == 1 {
		return float64(u.Exp) / float64(u.ExpNeededForNextLevel()) * 100
	}

	currentLevelExp := exp.Calc(u.Level)
	nextLevelExp := u.ExpNeededForNextLevel()
	progressExp := u.Exp

	return float64(progressExp) / float64(nextLevelExp-currentLevelExp) * 100
}
