package handler

import (
	"log/slog"
	"net/http"
	"strconv"

	"github.com/go-chi/chi/v5"

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

// QuestHandler serves quest system API endpoints.
type QuestHandler struct {
	questStore *storage.QuestStore
	heroStore  *storage.HeroStore
	logStore   *storage.LogStore
	logger     *slog.Logger
}

// NewQuestHandler creates a new QuestHandler.
func NewQuestHandler(questStore *storage.QuestStore, heroStore *storage.HeroStore, logStore *storage.LogStore, logger *slog.Logger) *QuestHandler {
	return &QuestHandler{
		questStore: questStore,
		heroStore:  heroStore,
		logStore:   logStore,
		logger:     logger,
	}
}

// ListTowns returns all towns.
// GET /api/v1/towns
func (h *QuestHandler) ListTowns(w http.ResponseWriter, r *http.Request) {
	towns, err := h.questStore.ListTowns(r.Context())
	if err != nil {
		h.logger.Error("failed to list towns", "error", err)
		writeJSON(w, http.StatusInternalServerError, map[string]string{
			"error": "failed to list towns",
		})
		return
	}
	writeJSON(w, http.StatusOK, towns)
}

// ListNPCsByTown returns all NPCs in a town.
// GET /api/v1/towns/{townId}/npcs
func (h *QuestHandler) ListNPCsByTown(w http.ResponseWriter, r *http.Request) {
	townIDStr := chi.URLParam(r, "townId")
	townID, err := strconv.ParseInt(townIDStr, 10, 64)
	if err != nil {
		writeJSON(w, http.StatusBadRequest, map[string]string{
			"error": "invalid townId",
		})
		return
	}

	npcs, err := h.questStore.ListNPCsByTown(r.Context(), townID)
	if err != nil {
		h.logger.Error("failed to list npcs", "town_id", townID, "error", err)
		writeJSON(w, http.StatusInternalServerError, map[string]string{
			"error": "failed to list npcs",
		})
		return
	}
	writeJSON(w, http.StatusOK, npcs)
}

// ListBuildingsByTown returns all buildings in a town.
// GET /api/v1/towns/{townId}/buildings
func (h *QuestHandler) ListBuildingsByTown(w http.ResponseWriter, r *http.Request) {
	townIDStr := chi.URLParam(r, "townId")
	townID, err := strconv.ParseInt(townIDStr, 10, 64)
	if err != nil {
		writeJSON(w, http.StatusBadRequest, map[string]string{
			"error": "invalid townId",
		})
		return
	}

	town, err := h.questStore.GetTown(r.Context(), townID)
	if err != nil {
		h.logger.Error("failed to get town for buildings", "town_id", townID, "error", err)
		writeJSON(w, http.StatusInternalServerError, map[string]string{
			"error": "failed to load town",
		})
		return
	}
	if town == nil {
		writeJSON(w, http.StatusNotFound, map[string]string{
			"error": "town not found",
		})
		return
	}

	buildings, err := h.questStore.ListBuildingsByTown(r.Context(), townID)
	if err != nil {
		h.logger.Error("failed to list buildings", "town_id", townID, "error", err)
		writeJSON(w, http.StatusInternalServerError, map[string]string{
			"error": "failed to list buildings",
		})
		return
	}

	views := make([]model.BuildingView, 0, len(buildings))
	for _, b := range buildings {
		views = append(views, model.BuildingView{
			ID:           b.ID,
			BuildingType: b.BuildingType,
			WorldX:       town.WorldX + b.OffsetX,
			WorldY:       town.WorldY + b.OffsetY,
			Facing:       b.Facing,
			FootprintW:   b.FootprintW,
			FootprintH:   b.FootprintH,
		})
	}
	writeJSON(w, http.StatusOK, views)
}

// ListQuestsByNPC returns all quests offered by an NPC.
// GET /api/v1/npcs/{npcId}/quests
func (h *QuestHandler) ListQuestsByNPC(w http.ResponseWriter, r *http.Request) {
	npcIDStr := chi.URLParam(r, "npcId")
	npcID, err := strconv.ParseInt(npcIDStr, 10, 64)
	if err != nil {
		writeJSON(w, http.StatusBadRequest, map[string]string{
			"error": "invalid npcId",
		})
		return
	}

	quests, err := h.questStore.ListQuestsByNPC(r.Context(), npcID)
	if err != nil {
		h.logger.Error("failed to list quests", "npc_id", npcID, "error", err)
		writeJSON(w, http.StatusInternalServerError, map[string]string{
			"error": "failed to list quests",
		})
		return
	}
	writeJSON(w, http.StatusOK, quests)
}

// AcceptQuest accepts a quest for the hero.
// POST /api/v1/hero/quests/{questId}/accept
func (h *QuestHandler) AcceptQuest(w http.ResponseWriter, r *http.Request) {
	telegramID, ok := resolveTelegramID(r)
	if !ok {
		writeJSON(w, http.StatusBadRequest, map[string]string{
			"error": "missing telegramId",
		})
		return
	}

	questIDStr := chi.URLParam(r, "questId")
	questID, err := strconv.ParseInt(questIDStr, 10, 64)
	if err != nil || questID == 0 {
		writeJSON(w, http.StatusBadRequest, map[string]string{
			"error": "invalid questId",
		})
		return
	}
	req := struct{ QuestID int64 }{QuestID: questID}

	hero, err := h.heroStore.GetByTelegramID(r.Context(), telegramID)
	if err != nil {
		h.logger.Error("failed to get hero for quest accept", "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 err := h.questStore.AcceptQuest(r.Context(), hero.ID, req.QuestID); err != nil {
		h.logger.Error("failed to accept quest", "hero_id", hero.ID, "quest_id", req.QuestID, "error", err)
		writeJSON(w, http.StatusInternalServerError, map[string]string{
			"error": "failed to accept quest",
		})
		return
	}

	h.logger.Info("quest accepted", "hero_id", hero.ID, "quest_id", req.QuestID)
	writeJSON(w, http.StatusOK, map[string]string{
		"status": "accepted",
	})
}

// ListHeroQuests returns the hero's quest log.
// GET /api/v1/hero/quests
func (h *QuestHandler) ListHeroQuests(w http.ResponseWriter, r *http.Request) {
	telegramID, ok := resolveTelegramID(r)
	if !ok {
		writeJSON(w, http.StatusBadRequest, map[string]string{
			"error": "missing telegramId",
		})
		return
	}

	hero, err := h.heroStore.GetByTelegramID(r.Context(), telegramID)
	if err != nil {
		h.logger.Error("failed to get hero for quest list", "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
	}

	quests, err := h.questStore.ListHeroQuests(r.Context(), hero.ID)
	if err != nil {
		h.logger.Error("failed to list hero quests", "hero_id", hero.ID, "error", err)
		writeJSON(w, http.StatusInternalServerError, map[string]string{
			"error": "failed to list quests",
		})
		return
	}
	writeJSON(w, http.StatusOK, quests)
}

// ClaimQuestReward claims a completed quest's reward.
// POST /api/v1/hero/quests/{questId}/claim
func (h *QuestHandler) ClaimQuestReward(w http.ResponseWriter, r *http.Request) {
	telegramID, ok := resolveTelegramID(r)
	if !ok {
		writeJSON(w, http.StatusBadRequest, map[string]string{
			"error": "missing telegramId",
		})
		return
	}

	questIDStr := chi.URLParam(r, "questId")
	questID, err := strconv.ParseInt(questIDStr, 10, 64)
	if err != nil {
		h.logger.Error("error claiming quest", "error", err)
		writeJSON(w, http.StatusBadRequest, map[string]string{
			"error": "invalid questId",
		})
		return
	}

	hero, err := h.heroStore.GetByTelegramID(r.Context(), telegramID)
	if err != nil {
		h.logger.Error("failed to get hero for quest claim", "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
	}

	reward, err := h.questStore.ClaimQuestReward(r.Context(), hero.ID, questID)
	if err != nil {
		h.logger.Warn("failed to claim quest reward", "hero_id", hero.ID, "quest_id", questID, "error", err)
		writeJSON(w, http.StatusBadRequest, map[string]string{
			"error": err.Error(),
		})
		return
	}

	// Apply rewards to hero.
	hero.XP += reward.XP
	hero.Gold += reward.Gold
	hero.Potions += reward.Potions

	// Run level-up loop.
	for hero.LevelUp() {
	}

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

	h.logger.Info("quest reward claimed", "hero_id", hero.ID, "quest_id", questID,
		"xp", reward.XP, "gold", reward.Gold, "potions", reward.Potions)

	writeJSON(w, http.StatusOK, hero)
}

// AbandonQuest removes a quest from the hero's quest log.
// DELETE /api/v1/hero/quests/{questId}
func (h *QuestHandler) AbandonQuest(w http.ResponseWriter, r *http.Request) {
	telegramID, ok := resolveTelegramID(r)
	if !ok {
		writeJSON(w, http.StatusBadRequest, map[string]string{
			"error": "missing telegramId",
		})
		return
	}

	questIDStr := chi.URLParam(r, "questId")
	questID, err := strconv.ParseInt(questIDStr, 10, 64)
	if err != nil {
		writeJSON(w, http.StatusBadRequest, map[string]string{
			"error": "invalid questId",
		})
		return
	}

	hero, err := h.heroStore.GetByTelegramID(r.Context(), telegramID)
	if err != nil {
		h.logger.Error("failed to get hero for quest abandon", "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 err := h.questStore.AbandonQuest(r.Context(), hero.ID, questID); err != nil {
		h.logger.Warn("failed to abandon quest", "hero_id", hero.ID, "quest_id", questID, "error", err)
		writeJSON(w, http.StatusBadRequest, map[string]string{
			"error": err.Error(),
		})
		return
	}

	h.logger.Info("quest abandoned", "hero_id", hero.ID, "quest_id", questID)
	writeJSON(w, http.StatusOK, map[string]string{
		"status": "abandoned",
	})
}
