package handler

import (
	"encoding/json"
	"log/slog"
	"net/http"
	"runtime"
	"strconv"
	"time"

	"github.com/go-chi/chi/v5"
	"github.com/jackc/pgx/v5/pgxpool"

	"github.com/denisovdennis/autohero/internal/game"
	"github.com/denisovdennis/autohero/internal/model"
	"github.com/denisovdennis/autohero/internal/storage"
)

var serverStartedAt = time.Now()

// AdminHandler provides administrative endpoints for hero management,
// engine inspection, and server diagnostics.
type AdminHandler struct {
	store  *storage.HeroStore
	engine *game.Engine
	hub    *Hub
	pool   *pgxpool.Pool
	logger *slog.Logger
}

// NewAdminHandler creates a new AdminHandler with all required dependencies.
func NewAdminHandler(store *storage.HeroStore, engine *game.Engine, hub *Hub, pool *pgxpool.Pool, logger *slog.Logger) *AdminHandler {
	return &AdminHandler{
		store:  store,
		engine: engine,
		hub:    hub,
		pool:   pool,
		logger: logger,
	}
}

// ── Hero Management ─────────────────────────────────────────────────

type heroSummary struct {
	ID         int64           `json:"id"`
	TelegramID int64           `json:"telegramId"`
	Name       string          `json:"name"`
	Level      int             `json:"level"`
	Gold       int64           `json:"gold"`
	HP         int             `json:"hp"`
	MaxHP      int             `json:"maxHp"`
	State      model.GameState `json:"state"`
	UpdatedAt  time.Time       `json:"updatedAt"`
}

// ListHeroes returns a paginated list of all heroes.
// GET /admin/heroes?limit=20&offset=0
func (h *AdminHandler) ListHeroes(w http.ResponseWriter, r *http.Request) {
	limit, _ := strconv.Atoi(r.URL.Query().Get("limit"))
	offset, _ := strconv.Atoi(r.URL.Query().Get("offset"))
	if limit <= 0 {
		limit = 20
	}

	heroes, err := h.store.ListHeroes(r.Context(), limit, offset)
	if err != nil {
		h.logger.Error("admin: list heroes failed", "error", err)
		writeJSON(w, http.StatusInternalServerError, map[string]string{
			"error": "failed to list heroes",
		})
		return
	}

	summaries := make([]heroSummary, len(heroes))
	for i, hero := range heroes {
		summaries[i] = heroSummary{
			ID:         hero.ID,
			TelegramID: hero.TelegramID,
			Name:       hero.Name,
			Level:      hero.Level,
			Gold:       hero.Gold,
			HP:         hero.HP,
			MaxHP:      hero.MaxHP,
			State:      hero.State,
			UpdatedAt:  hero.UpdatedAt,
		}
	}

	writeJSON(w, http.StatusOK, map[string]any{
		"heroes": summaries,
		"limit":  limit,
		"offset": offset,
	})
}

// GetHero returns full hero detail by database ID.
// GET /admin/heroes/{heroId}
func (h *AdminHandler) GetHero(w http.ResponseWriter, r *http.Request) {
	heroID, err := parseHeroID(r)
	if err != nil {
		writeJSON(w, http.StatusBadRequest, map[string]string{
			"error": "invalid heroId: " + err.Error(),
		})
		return
	}

	hero, err := h.store.GetByID(r.Context(), heroID)
	if err != nil {
		h.logger.Error("admin: get hero failed", "hero_id", heroID, "error", err)
		writeJSON(w, http.StatusInternalServerError, map[string]string{
			"error": "failed to load hero",
		})
		return
	}
	if hero == nil {
		writeJSON(w, http.StatusNotFound, map[string]string{
			"error": "hero not found",
		})
		return
	}

	hero.RefreshDerivedCombatStats(time.Now())
	writeJSON(w, http.StatusOK, h.engine.GetMovements(heroID).Hero)
}

type setLevelRequest struct {
	Level int `json:"level"`
}

// SetHeroLevel sets the hero to a specific level, recalculating stats.
// POST /admin/heroes/{heroId}/set-level
func (h *AdminHandler) SetHeroLevel(w http.ResponseWriter, r *http.Request) {
	heroID, err := parseHeroID(r)
	if err != nil {
		writeJSON(w, http.StatusBadRequest, map[string]string{
			"error": "invalid heroId: " + err.Error(),
		})
		return
	}

	var req setLevelRequest
	if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
		writeJSON(w, http.StatusBadRequest, map[string]string{
			"error": "invalid request body: " + err.Error(),
		})
		return
	}
	if req.Level < 1 {
		writeJSON(w, http.StatusBadRequest, map[string]string{
			"error": "level must be >= 1",
		})
		return
	}
	const maxAdminLevel = 200
	if req.Level > maxAdminLevel {
		writeJSON(w, http.StatusBadRequest, map[string]string{
			"error": "level must be <= 200",
		})
		return
	}

	hero, err := h.store.GetByID(r.Context(), heroID)
	if err != nil {
		h.logger.Error("admin: get hero for set-level", "hero_id", heroID, "error", err)
		writeJSON(w, http.StatusInternalServerError, map[string]string{
			"error": "failed to load hero",
		})
		return
	}
	if hero == nil {
		writeJSON(w, http.StatusNotFound, map[string]string{
			"error": "hero not found",
		})
		return
	}

	if h.isHeroInCombat(w, heroID) {
		return
	}

	savedGold := hero.Gold
	resetHeroToLevel1(hero)
	hero.Gold = savedGold

	for hero.Level < req.Level {
		hero.XP = model.XPToNextLevel(hero.Level)
		if !hero.LevelUp() {
			break
		}
	}

	if err := h.store.Save(r.Context(), hero); err != nil {
		h.logger.Error("admin: save hero after set-level", "hero_id", heroID, "error", err)
		writeJSON(w, http.StatusInternalServerError, map[string]string{
			"error": "failed to save hero",
		})
		return
	}

	h.logger.Info("admin: hero level set", "hero_id", heroID, "level", hero.Level)
	hero.RefreshDerivedCombatStats(time.Now())
	writeJSON(w, http.StatusOK, hero)
}

type setGoldRequest struct {
	Gold int64 `json:"gold"`
}

// SetHeroGold sets the hero's gold to an exact value.
// POST /admin/heroes/{heroId}/set-gold
func (h *AdminHandler) SetHeroGold(w http.ResponseWriter, r *http.Request) {
	heroID, err := parseHeroID(r)
	if err != nil {
		writeJSON(w, http.StatusBadRequest, map[string]string{
			"error": "invalid heroId: " + err.Error(),
		})
		return
	}

	var req setGoldRequest
	if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
		writeJSON(w, http.StatusBadRequest, map[string]string{
			"error": "invalid request body: " + err.Error(),
		})
		return
	}
	if req.Gold < 0 {
		writeJSON(w, http.StatusBadRequest, map[string]string{
			"error": "gold must be >= 0",
		})
		return
	}

	hero, err := h.store.GetByID(r.Context(), heroID)
	if err != nil {
		h.logger.Error("admin: get hero for set-gold", "hero_id", heroID, "error", err)
		writeJSON(w, http.StatusInternalServerError, map[string]string{
			"error": "failed to load hero",
		})
		return
	}
	if hero == nil {
		writeJSON(w, http.StatusNotFound, map[string]string{
			"error": "hero not found",
		})
		return
	}

	if h.isHeroInCombat(w, heroID) {
		return
	}

	hero.Gold = req.Gold

	if err := h.store.Save(r.Context(), hero); err != nil {
		h.logger.Error("admin: save hero after set-gold", "hero_id", heroID, "error", err)
		writeJSON(w, http.StatusInternalServerError, map[string]string{
			"error": "failed to save hero",
		})
		return
	}

	h.logger.Info("admin: hero gold set", "hero_id", heroID, "gold", hero.Gold)
	hero.RefreshDerivedCombatStats(time.Now())
	writeJSON(w, http.StatusOK, hero)
}

type addPotionsRequest struct {
	Potions int `json:"potions"`
}

// SetHeroGold sets the hero's gold to an exact value.
// POST /admin/heroes/{heroId}/add-potions
func (h *AdminHandler) AddPotions(w http.ResponseWriter, r *http.Request) {
	heroID, err := parseHeroID(r)
	if err != nil {
		writeJSON(w, http.StatusBadRequest, map[string]string{
			"error": "invalid heroId: " + err.Error(),
		})
		return
	}

	var req addPotionsRequest
	if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
		writeJSON(w, http.StatusBadRequest, map[string]string{
			"error": "invalid request body: " + err.Error(),
		})
		return
	}
	if req.Potions < 0 {
		writeJSON(w, http.StatusBadRequest, map[string]string{
			"error": "potions must be >= 1",
		})
		return
	}


	var hero = h.engine.GetMovements(heroID).Hero
	
	if hero == nil {
		writeJSON(w, http.StatusNotFound, map[string]string{
			"error": "hero not found",
		})
		return
	}

	hero.Potions += req.Potions

	if err := h.store.Save(r.Context(), hero); err != nil {
		h.logger.Error("admin: save hero after set-gold", "hero_id", heroID, "error", err)
		writeJSON(w, http.StatusInternalServerError, map[string]string{
			"error": "failed to save hero",
		})
		return
	}

	h.logger.Info("admin: hero added potions", "hero_id", heroID, "potions", hero.Potions)
	hero.RefreshDerivedCombatStats(time.Now())
	h.engine.SyncHeroState(hero)
	writeJSON(w, http.StatusOK, hero)
}

type setHPRequest struct {
	HP int `json:"hp"`
}

// SetHeroHP sets the hero's current HP, clamped to [1, maxHp].
// POST /admin/heroes/{heroId}/set-hp
func (h *AdminHandler) SetHeroHP(w http.ResponseWriter, r *http.Request) {
	heroID, err := parseHeroID(r)
	if err != nil {
		writeJSON(w, http.StatusBadRequest, map[string]string{
			"error": "invalid heroId: " + err.Error(),
		})
		return
	}

	var req setHPRequest
	if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
		writeJSON(w, http.StatusBadRequest, map[string]string{
			"error": "invalid request body: " + err.Error(),
		})
		return
	}

	hero, err := h.store.GetByID(r.Context(), heroID)
	if err != nil {
		h.logger.Error("admin: get hero for set-hp", "hero_id", heroID, "error", err)
		writeJSON(w, http.StatusInternalServerError, map[string]string{
			"error": "failed to load hero",
		})
		return
	}
	if hero == nil {
		writeJSON(w, http.StatusNotFound, map[string]string{
			"error": "hero not found",
		})
		return
	}

	if h.isHeroInCombat(w, heroID) {
		return
	}

	hp := req.HP
	if hp < 1 {
		hp = 1
	}
	if hp > hero.MaxHP {
		hp = hero.MaxHP
	}
	hero.HP = hp

	if hero.State == model.StateDead && hero.HP > 0 {
		hero.State = model.StateWalking
	}

	if err := h.store.Save(r.Context(), hero); err != nil {
		h.logger.Error("admin: save hero after set-hp", "hero_id", heroID, "error", err)
		writeJSON(w, http.StatusInternalServerError, map[string]string{
			"error": "failed to save hero",
		})
		return
	}

	h.logger.Info("admin: hero HP set", "hero_id", heroID, "hp", hero.HP)
	hero.RefreshDerivedCombatStats(time.Now())
	writeJSON(w, http.StatusOK, hero)
}

// ReviveHero force-revives a hero to full HP regardless of current state.
// POST /admin/heroes/{heroId}/revive
func (h *AdminHandler) ReviveHero(w http.ResponseWriter, r *http.Request) {
	heroID, err := parseHeroID(r)
	if err != nil {
		writeJSON(w, http.StatusBadRequest, map[string]string{
			"error": "invalid heroId: " + err.Error(),
		})
		return
	}

	hero, err := h.store.GetByID(r.Context(), heroID)
	if err != nil {
		h.logger.Error("admin: get hero for revive", "hero_id", heroID, "error", err)
		writeJSON(w, http.StatusInternalServerError, map[string]string{
			"error": "failed to load hero",
		})
		return
	}
	if hero == nil {
		writeJSON(w, http.StatusNotFound, map[string]string{
			"error": "hero not found",
		})
		return
	}

	hero.HP = hero.MaxHP
	hero.State = model.StateWalking
	hero.Buffs = nil
	hero.Debuffs = nil

	if err := h.store.Save(r.Context(), hero); err != nil {
		h.logger.Error("admin: save hero after revive", "hero_id", heroID, "error", err)
		writeJSON(w, http.StatusInternalServerError, map[string]string{
			"error": "failed to save hero",
		})
		return
	}

	h.engine.ApplyAdminHeroRevive(hero)

	h.logger.Info("admin: hero revived", "hero_id", heroID, "hp", hero.HP)
	hero.RefreshDerivedCombatStats(time.Now())
	writeJSON(w, http.StatusOK, hero)
}

// ResetHero resets a hero to fresh level 1 defaults.
// POST /admin/heroes/{heroId}/reset
func (h *AdminHandler) ResetHero(w http.ResponseWriter, r *http.Request) {
	heroID, err := parseHeroID(r)
	if err != nil {
		writeJSON(w, http.StatusBadRequest, map[string]string{
			"error": "invalid heroId: " + err.Error(),
		})
		return
	}

	hero, err := h.store.GetByID(r.Context(), heroID)
	if err != nil {
		h.logger.Error("admin: get hero for reset", "hero_id", heroID, "error", err)
		writeJSON(w, http.StatusInternalServerError, map[string]string{
			"error": "failed to load hero",
		})
		return
	}
	if hero == nil {
		writeJSON(w, http.StatusNotFound, map[string]string{
			"error": "hero not found",
		})
		return
	}

	if h.isHeroInCombat(w, heroID) {
		return
	}

	resetHeroToLevel1(hero)

	if err := h.store.Save(r.Context(), hero); err != nil {
		h.logger.Error("admin: save hero after reset", "hero_id", heroID, "error", err)
		writeJSON(w, http.StatusInternalServerError, map[string]string{
			"error": "failed to save hero",
		})
		return
	}

	h.logger.Info("admin: hero reset", "hero_id", heroID)
	hero.RefreshDerivedCombatStats(time.Now())
	writeJSON(w, http.StatusOK, hero)
}

type resetBuffChargesRequest struct {
	BuffType string `json:"buffType"` // optional — if empty, reset ALL
}

// ResetBuffCharges resets per-buff free charges to their maximums.
// If buffType is provided, only that buff is reset; otherwise all are reset.
// POST /admin/heroes/{heroId}/reset-buff-charges
func (h *AdminHandler) ResetBuffCharges(w http.ResponseWriter, r *http.Request) {
	heroID, err := parseHeroID(r)
	if err != nil {
		writeJSON(w, http.StatusBadRequest, map[string]string{
			"error": "invalid heroId: " + err.Error(),
		})
		return
	}

	var req resetBuffChargesRequest
	if r.Body != nil && r.ContentLength > 0 {
		if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
			writeJSON(w, http.StatusBadRequest, map[string]string{
				"error": "invalid request body: " + err.Error(),
			})
			return
		}
	}

	hero, err := h.store.GetByID(r.Context(), heroID)
	if err != nil {
		h.logger.Error("admin: get hero for reset-buff-charges", "hero_id", heroID, "error", err)
		writeJSON(w, http.StatusInternalServerError, map[string]string{
			"error": "failed to load hero",
		})
		return
	}
	if hero == nil {
		writeJSON(w, http.StatusNotFound, map[string]string{
			"error": "hero not found",
		})
		return
	}

	now := time.Now()
	hero.EnsureBuffChargesPopulated(now)

	if req.BuffType != "" {
		bt, ok := model.ValidBuffType(req.BuffType)
		if !ok {
			writeJSON(w, http.StatusBadRequest, map[string]string{
				"error": "invalid buffType: " + req.BuffType,
			})
			return
		}
		hero.ResetBuffCharges(&bt, now)
		h.logger.Info("admin: buff charges reset (single)", "hero_id", heroID, "buff_type", bt)
	} else {
		hero.ResetBuffCharges(nil, now)
		h.logger.Info("admin: buff charges reset (all)", "hero_id", heroID)
	}

	if err := h.store.Save(r.Context(), hero); err != nil {
		h.logger.Error("admin: save hero after reset-buff-charges", "hero_id", heroID, "error", err)
		writeJSON(w, http.StatusInternalServerError, map[string]string{
			"error": "failed to save hero",
		})
		return
	}

	hero.RefreshDerivedCombatStats(now)
	writeJSON(w, http.StatusOK, hero)
}

// DeleteHero permanently removes a hero from the database.
// DELETE /admin/heroes/{heroId}
func (h *AdminHandler) DeleteHero(w http.ResponseWriter, r *http.Request) {
	heroID, err := parseHeroID(r)
	if err != nil {
		writeJSON(w, http.StatusBadRequest, map[string]string{
			"error": "invalid heroId: " + err.Error(),
		})
		return
	}

	hero, err := h.store.GetByID(r.Context(), heroID)
	if err != nil {
		h.logger.Error("admin: get hero for delete", "hero_id", heroID, "error", err)
		writeJSON(w, http.StatusInternalServerError, map[string]string{
			"error": "failed to load hero",
		})
		return
	}
	if hero == nil {
		writeJSON(w, http.StatusNotFound, map[string]string{
			"error": "hero not found",
		})
		return
	}

	h.engine.StopCombat(heroID)

	if err := h.store.DeleteByID(r.Context(), heroID); err != nil {
		h.logger.Error("admin: delete hero failed", "hero_id", heroID, "error", err)
		writeJSON(w, http.StatusInternalServerError, map[string]string{
			"error": "failed to delete hero",
		})
		return
	}

	h.logger.Info("admin: hero deleted", "hero_id", heroID)
	writeJSON(w, http.StatusOK, map[string]string{
		"status": "deleted",
	})
}

// ── Game Engine ─────────────────────────────────────────────────────

// EngineStatus returns operational status of the game engine.
// GET /admin/engine/status
func (h *AdminHandler) EngineStatus(w http.ResponseWriter, r *http.Request) {
	status := h.engine.Status()
	writeJSON(w, http.StatusOK, map[string]any{
		"running":       status.Running,
		"tickRateMs":    status.TickRate.Milliseconds(),
		"activeCombats": status.ActiveCombats,
		"uptimeMs":      status.UptimeMs,
	})
}

// ActiveCombats returns all active combat sessions.
// GET /admin/engine/combats
func (h *AdminHandler) ActiveCombats(w http.ResponseWriter, r *http.Request) {
	combats := h.engine.ListActiveCombats()
	writeJSON(w, http.StatusOK, map[string]any{
		"combats": combats,
		"count":   len(combats),
	})
}

// ── WebSocket Hub ───────────────────────────────────────────────────

// WSConnections returns active WebSocket connection info.
// GET /admin/ws/connections
func (h *AdminHandler) WSConnections(w http.ResponseWriter, r *http.Request) {
	writeJSON(w, http.StatusOK, map[string]any{
		"connectionCount": h.hub.ConnectionCount(),
		"heroIds":         h.hub.ConnectedHeroIDs(),
	})
}

// ── Server Info ─────────────────────────────────────────────────────

// ServerInfo returns general server diagnostics.
// GET /admin/info
func (h *AdminHandler) ServerInfo(w http.ResponseWriter, r *http.Request) {
	poolStat := h.pool.Stat()
	writeJSON(w, http.StatusOK, map[string]any{
		"version":   "0.1.0-dev",
		"goVersion": runtime.Version(),
		"uptimeMs":  time.Since(serverStartedAt).Milliseconds(),
		"dbPool": map[string]any{
			"totalConns":    poolStat.TotalConns(),
			"acquiredConns": poolStat.AcquiredConns(),
			"idleConns":     poolStat.IdleConns(),
			"maxConns":      poolStat.MaxConns(),
		},
	})
}

// ── Helpers ─────────────────────────────────────────────────────────

func parseHeroID(r *http.Request) (int64, error) {
	return strconv.ParseInt(chi.URLParam(r, "heroId"), 10, 64)
}

// isHeroInCombat checks if the hero is in active engine combat and writes
// a 409 Conflict response if so. Returns true when the caller should abort.
func (h *AdminHandler) isHeroInCombat(w http.ResponseWriter, heroID int64) bool {
	if _, active := h.engine.GetCombat(heroID); active {
		writeJSON(w, http.StatusConflict, map[string]string{
			"error": "hero is in active combat — stop combat first",
		})
		return true
	}
	return false
}

// resetHeroToLevel1 restores a hero to fresh level 1 defaults,
// preserving identity fields (ID, TelegramID, Name, CreatedAt).
func resetHeroToLevel1(hero *model.Hero) {
	hero.Level = 1
	hero.XP = 0
	hero.Gold = 0
	hero.HP = 100
	hero.MaxHP = 100
	hero.Attack = 10
	hero.Defense = 5
	hero.Speed = 1.0
	hero.Strength = 1
	hero.Constitution = 1
	hero.Agility = 1
	hero.Luck = 1
	hero.State = model.StateWalking
	hero.Buffs = nil
	hero.Debuffs = nil
	hero.BuffCharges = nil
	hero.BuffFreeChargesRemaining = model.FreeBuffActivationsPerPeriod
	hero.BuffQuotaPeriodEnd = nil
}
