package users

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

	"dk/internal/database"
	"dk/internal/helpers"
	"dk/internal/helpers/exp"
)

// User represents a user in the game
type User struct {
	ID         int
	Username   string
	Password   string
	Email      string
	Verified   int
	Token      string
	Registered int64
	LastOnline int64
	Auth       int
	X          int
	Y          int
	ClassID    int
	Currently  string
	FightID    int
	HP         int
	MP         int
	TP         int
	MaxHP      int
	MaxMP      int
	MaxTP      int
	Level      int
	Gold       int
	Exp        int
	GoldBonus  int
	ExpBonus   int
	Strength   int
	Dexterity  int
	Attack     int
	Defense    int
	WeaponID   int
	ArmorID    int
	ShieldID   int
	Slot1ID    int
	Slot2ID    int
	Slot3ID    int
	WeaponName string
	ArmorName  string
	ShieldName string
	Slot1Name  string
	Slot2Name  string
	Slot3Name  string
	Spells     string
	Towns      string
}

// 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
}

func (u *User) Delete() error {
	return database.Exec("DELETE FROM users WHERE id = %d", u.ID)
}

func (u *User) Insert() error {
	id, err := database.Insert("users", u, "id")
	if err != nil {
		return err
	}
	u.ID = int(id)
	return nil
}

func Find(id int) (*User, error) {
	var user User
	err := database.Get(&user, "SELECT * FROM users WHERE id = %d", id)
	if err != nil {
		return nil, fmt.Errorf("user with ID %d not found", id)
	}
	return &user, nil
}

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

func All() ([]*User, error) {
	var users []*User
	err := database.Select(&users, "SELECT * FROM users ORDER BY registered DESC, id DESC")
	return users, err
}

func ByUsername(username string) (*User, error) {
	var user User
	err := database.Get(&user, "SELECT * FROM users WHERE username = %s COLLATE NOCASE", username)
	if err != nil {
		return nil, fmt.Errorf("user with username '%s' not found", username)
	}
	return &user, nil
}

func ByEmail(email string) (*User, error) {
	var user User
	err := database.Get(&user, "SELECT * FROM users WHERE email = %s", email)
	if err != nil {
		return nil, fmt.Errorf("user with email '%s' not found", email)
	}
	return &user, nil
}

func ByLevel(level int) ([]*User, error) {
	var users []*User
	err := database.Select(&users, "SELECT * FROM users WHERE level = %d ORDER BY exp DESC, id ASC", level)
	return users, err
}

func Online(within time.Duration) ([]*User, error) {
	cutoff := time.Now().Add(-within).Unix()
	var users []*User
	err := database.Select(&users, "SELECT * FROM users WHERE last_online >= %d ORDER BY last_online DESC, id ASC", cutoff)
	return users, err
}

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
}
