package towns

import (
	"fmt"
	"math"
	"slices"
	"sort"
	"strconv"
	"strings"

	"dk/internal/helpers"

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

// Town represents a town in the game
type Town struct {
	ID       int    `json:"id"`
	Name     string `json:"name" db:"required,unique"`
	X        int    `json:"x"`
	Y        int    `json:"y"`
	InnCost  int    `json:"inn_cost"`
	MapCost  int    `json:"map_cost"`
	TPCost   int    `json:"tp_cost"`
	ShopList string `json:"shop_list"`
}

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

// coordsKey creates a key for coordinate-based lookup
func coordsKey(x, y int) string {
	return strconv.Itoa(x) + "," + strconv.Itoa(y)
}

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

	// Register custom indices
	store.RegisterIndex("byName", nigiri.BuildCaseInsensitiveLookupIndex(func(t *Town) string {
		return t.Name
	}))

	store.RegisterIndex("byCoords", nigiri.BuildStringLookupIndex(func(t *Town) string {
		return coordsKey(t.X, t.Y)
	}))

	store.RegisterIndex("byInnCost", nigiri.BuildIntGroupIndex(func(t *Town) int {
		return t.InnCost
	}))

	store.RegisterIndex("byTPCost", nigiri.BuildIntGroupIndex(func(t *Town) int {
		return t.TPCost
	}))

	store.RegisterIndex("allByID", nigiri.BuildSortedListIndex(func(a, b *Town) bool {
		return a.ID < b.ID
	}))

	store.RebuildIndices()
}

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

// Creates a new Town with sensible defaults
func New() *Town {
	return &Town{
		Name:     "",
		X:        0, // Default coordinates
		Y:        0,
		InnCost:  50,  // Default inn cost
		MapCost:  100, // Default map cost
		TPCost:   25,  // Default teleport cost
		ShopList: "",  // No items by default
	}
}

// Validate checks if town has valid values
func (t *Town) Validate() error {
	if t.Name == "" {
		return fmt.Errorf("town name cannot be empty")
	}
	if t.InnCost < 0 {
		return fmt.Errorf("town InnCost cannot be negative")
	}
	if t.MapCost < 0 {
		return fmt.Errorf("town MapCost cannot be negative")
	}
	if t.TPCost < 0 {
		return fmt.Errorf("town TPCost cannot be negative")
	}
	return nil
}

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

func (t *Town) Delete() error {
	store.Remove(t.ID)
	return nil
}

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

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

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

func ByName(name string) (*Town, error) {
	town, exists := store.LookupByIndex("byName", strings.ToLower(name))
	if !exists {
		return nil, fmt.Errorf("town with name '%s' not found", name)
	}
	return town, nil
}

func ByMaxInnCost(maxCost int) ([]*Town, error) {
	return store.FilterByIndex("allByID", func(t *Town) bool {
		return t.InnCost <= maxCost
	}), nil
}

func ByMaxTPCost(maxCost int) ([]*Town, error) {
	return store.FilterByIndex("allByID", func(t *Town) bool {
		return t.TPCost <= maxCost
	}), nil
}

func ByCoords(x, y int) (*Town, error) {
	town, exists := store.LookupByIndex("byCoords", coordsKey(x, y))
	if !exists {
		return nil, nil // Return nil if not found (like original)
	}
	return town, nil
}

func ExistsAt(x, y int) bool {
	_, exists := store.LookupByIndex("byCoords", coordsKey(x, y))
	return exists
}

func ByDistance(fromX, fromY, maxDistance int) ([]*Town, error) {
	maxDistance2 := float64(maxDistance * maxDistance)

	result := store.FilterByIndex("allByID", func(t *Town) bool {
		return t.DistanceFromSquared(fromX, fromY) <= maxDistance2
	})

	// Sort by distance, then by ID
	sort.Slice(result, func(i, j int) bool {
		distI := result[i].DistanceFromSquared(fromX, fromY)
		distJ := result[j].DistanceFromSquared(fromX, fromY)
		if distI == distJ {
			return result[i].ID < result[j].ID
		}
		return distI < distJ
	})

	return result, nil
}

// Helper methods
func (t *Town) GetShopItems() []int {
	return helpers.StringToInts(t.ShopList)
}

func (t *Town) SetShopItems(items []int) {
	t.ShopList = helpers.IntsToString(items)
}

func (t *Town) HasShopItem(itemID int) bool {
	return slices.Contains(t.GetShopItems(), itemID)
}

func (t *Town) DistanceFromSquared(x, y int) float64 {
	dx := float64(t.X - x)
	dy := float64(t.Y - y)
	return dx*dx + dy*dy // Return squared distance for performance
}

func (t *Town) DistanceFrom(x, y int) float64 {
	return math.Sqrt(t.DistanceFromSquared(x, y))
}

func (t *Town) IsStartingTown() bool {
	return t.X == 0 && t.Y == 0
}

func (t *Town) CanAffordInn(gold int) bool {
	return gold >= t.InnCost
}

func (t *Town) CanAffordMap(gold int) bool {
	return gold >= t.MapCost
}

func (t *Town) CanAffordTeleport(gold int) bool {
	return gold >= t.TPCost
}

func (t *Town) HasShop() bool {
	return len(t.GetShopItems()) > 0
}

func (t *Town) GetPosition() (int, int) {
	return t.X, t.Y
}

func (t *Town) SetPosition(x, y int) {
	t.X = x
	t.Y = y
}

// Legacy compatibility functions (will be removed later)
func LoadData(dataPath string) error {
	// No longer needed - Nigiri handles this
	return nil
}

func SaveData(dataPath string) error {
	// No longer needed - Nigiri handles this
	return nil
}
