package routes

import (
	"dk/internal/actions"
	"dk/internal/components"
	"dk/internal/database"
	"dk/internal/helpers"
	"dk/internal/models/items"
	"dk/internal/models/towns"
	"dk/internal/models/users"
	"fmt"
	"slices"

	sushi "git.sharkk.net/Sharkk/Sushi"
	"git.sharkk.net/Sharkk/Sushi/auth"
)

// Map acts as a representation of owned/unowned maps in the town stores.
type Map struct {
	ID    int
	Name  string
	Cost  int
	Owned bool
	X     int
	Y     int
	TP    int
}

func RegisterTownRoutes(app *sushi.App) {
	group := app.Group("/town")
	group.Use(auth.RequireAuth("/login"))
	group.Use(requireTown())

	group.Get("/", showTown)
	group.Get("/inn", showInn)
	group.Post("/inn", rest)
	group.Get("/shop", showShop)
	group.Get("/shop/buy/:id", buyItem)
	group.Get("/maps", showMaps)
	group.Get("/maps/buy/:id", buyMap)
}

// requireTown middleware ensures the user is in a town
func requireTown() sushi.Middleware {
	return func(ctx sushi.Ctx, next func()) {
		user := ctx.GetCurrentUser()
		if user == nil {
			ctx.SendError(401, "Not authenticated")
			return
		}

		userModel := user.(*users.User)
		if userModel.Currently != "In Town" {
			ctx.SendError(403, "You must be in town")
			return
		}

		town, err := towns.ByCoords(userModel.X, userModel.Y)
		if err != nil || town == nil || town.ID == 0 {
			ctx.SendError(403, fmt.Sprintf("Invalid town location (%d, %d)", userModel.X, userModel.Y))
			return
		}

		ctx.SetUserValue("town", town)
		next()
	}
}

func showTown(ctx sushi.Ctx) {
	town := ctx.UserValue("town").(*towns.Town)
	components.RenderPage(ctx, town.Name, "town/town.html", map[string]any{
		"town":        town,
		"newscontent": components.GenerateTownNews(),
		"whosonline":  components.GenerateTownWhosOnline(),
	})
}

func showInn(ctx sushi.Ctx) {
	town := ctx.UserValue("town").(*towns.Town)
	components.RenderPage(ctx, town.Name+" Inn", "town/inn.html", map[string]any{
		"town":   town,
		"rested": false,
	})
}

func rest(ctx sushi.Ctx) {
	sess := ctx.GetCurrentSession()
	town := ctx.UserValue("town").(*towns.Town)
	user := ctx.GetCurrentUser().(*users.User)

	if user.Gold < town.InnCost {
		sess.SetFlash("error", "You can't afford to stay here tonight.")
		ctx.Redirect("/town/inn")
		return
	}

	err := database.Transaction(func() error {
		return database.Update("users", map[string]any{
			"gold": user.Gold - town.InnCost,
			"hp":   user.MaxHP,
			"mp":   user.MaxMP,
			"tp":   user.MaxTP,
		}, "id", user.ID)
	})

	if err != nil {
		sess.SetFlash("error", "Failed to rest at inn.")
		ctx.Redirect("/town/inn")
		return
	}

	components.RenderPage(ctx, town.Name+" Inn", "town/inn.html", map[string]any{
		"town":   town,
		"rested": true,
	})
}

func showShop(ctx sushi.Ctx) {
	sess := ctx.GetCurrentSession()
	var errorHTML string

	errorMsg := sess.GetFlashMessage("error")
	if errorMsg != "" {
		errorHTML = `<div style="color: red; margin-bottom: 1rem;">` + errorMsg + "</div>"
	}

	town := ctx.UserValue("town").(*towns.Town)

	var itemlist []*items.Item
	if town.ShopList != "" {
		itemMap := helpers.NewOrderedMap[int, *items.Item]()
		for _, id := range town.GetShopItems() {
			if item, err := items.Find(id); err == nil {
				itemMap.Set(id, item)
			}
		}
		itemlist = itemMap.ToSlice()
	}

	components.RenderPage(ctx, town.Name+" Shop", "town/shop.html", map[string]any{
		"town":          town,
		"itemlist":      itemlist,
		"error_message": errorHTML,
	})
}

func buyItem(ctx sushi.Ctx) {
	sess := ctx.GetCurrentSession()

	id := ctx.Param("id").Int()

	town := ctx.UserValue("town").(*towns.Town)
	if !slices.Contains(town.GetShopItems(), id) {
		sess.SetFlash("error", "The item doesn't exist in this shop.")
		ctx.Redirect("/town/shop")
		return
	}

	item, err := items.Find(id)
	if err != nil {
		sess.SetFlash("error", "Error purchasing item; "+err.Error())
		ctx.Redirect("/town/shop")
		return
	}

	user := ctx.GetCurrentUser().(*users.User)
	if user.Gold < item.Value {
		sess.SetFlash("error", "You don't have enough gold to buy "+item.Name)
		ctx.Redirect("/town/shop")
		return
	}

	// Get equipment updates from actions
	equipUpdates := actions.UserEquipItem(user, item)

	err = database.Transaction(func() error {
		// Start with gold deduction
		updates := map[string]any{
			"gold": user.Gold - item.Value,
		}

		// Add equipment updates
		for field, value := range equipUpdates {
			updates[field] = value
		}

		return database.Update("users", updates, "id", user.ID)
	})

	if err != nil {
		sess.SetFlash("error", "Failed to purchase item.")
		ctx.Redirect("/town/shop")
		return
	}

	ctx.Redirect("/town/shop")
}

func showMaps(ctx sushi.Ctx) {
	sess := ctx.GetCurrentSession()
	var errorHTML string

	errorMsg := sess.GetFlashMessage("error")
	if errorMsg != "" {
		errorHTML = `<div style="color: red; margin-bottom: 1rem;">` + errorMsg + "</div>"
	}

	town := ctx.UserValue("town").(*towns.Town)
	user := ctx.GetCurrentUser().(*users.User)

	maplist := helpers.NewOrderedMap[int, Map]()
	towns, _ := towns.All()
	var mapped []int
	if user.Towns != "" {
		mapped = user.GetTownIDs()
	}
	for _, town := range towns {
		var owned bool
		if mapped != nil && slices.Contains(mapped, town.ID) {
			owned = true
		} else {
			owned = false
		}

		maplist.Set(town.ID, Map{
			ID:    town.ID,
			Name:  town.Name,
			Cost:  town.MapCost,
			Owned: owned,
			X:     town.X,
			Y:     town.Y,
			TP:    town.TPCost,
		})
	}

	components.RenderPage(ctx, town.Name+" Maps", "town/maps.html", map[string]any{
		"town":          town,
		"maplist":       maplist.ToSlice(),
		"error_message": errorHTML,
	})
}

func buyMap(ctx sushi.Ctx) {
	sess := ctx.GetCurrentSession()

	id := ctx.Param("id").Int()

	mapped, err := towns.Find(id)
	if err != nil {
		sess.SetFlash("error", "Error purchasing map; "+err.Error())
		ctx.Redirect("/town/maps")
		return
	}

	user := ctx.GetCurrentUser().(*users.User)
	if user.Gold < mapped.MapCost {
		sess.SetFlash("error", "You don't have enough gold to buy the map to "+mapped.Name)
		ctx.Redirect("/town/maps")
		return
	}

	townIDs := user.GetTownIDs()
	townIDs = append(townIDs, id)
	newTownsString := helpers.IntsToString(townIDs)

	err = database.Transaction(func() error {
		return database.Update("users", map[string]any{
			"gold":  user.Gold - mapped.MapCost,
			"towns": newTownsString,
		}, "id", user.ID)
	})

	if err != nil {
		sess.SetFlash("error", "Failed to purchase map.")
		ctx.Redirect("/town/maps")
		return
	}

	ctx.Redirect("/town/maps")
}
