package handlers

import (
	"net/http"

	"github.com/gin-gonic/gin"
	"github.com/google/uuid"
	"gitea.maison43.duckdns.org/gilles/matosbox/internal/data/ent"
	"gitea.maison43.duckdns.org/gilles/matosbox/internal/data/ent/lienobjetemplacement"
)

type lienObjetEmplacementRequest struct {
	EmplacementID *string `json:"emplacement_id"`
	Type          *string `json:"type"`
}

type lienObjetEmplacementUpdateRequest struct {
	EmplacementID *string `json:"emplacement_id"`
	Type          *string `json:"type"`
}

// @Summary Lister les liens objet/emplacement
// @Tags LiensEmplacements
// @Produce json
// @Param id path string true "ID objet"
// @Param page query int false "Page"
// @Param limit query int false "Limite"
// @Success 200 {object} map[string]any
// @Failure 400 {object} map[string]string
// @Failure 500 {object} map[string]string
// @Router /objets/{id}/liens_emplacements [get]
func (h *Handler) ListLiensEmplacements(c *gin.Context) {
	objetID, err := uuid.Parse(c.Param("id"))
	if err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"erreur": "identifiant invalide"})
		return
	}

	limit, offset, page := parsePagination(c.Query("page"), c.Query("limit"))
	query := h.client.LienObjetEmplacement.Query().
		Where(lienobjetemplacement.ObjetID(objetID))
	total, err := query.Count(c.Request.Context())
	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"erreur": "impossible de compter les liens"})
		return
	}

	items, err := query.
		Order(ent.Desc(lienobjetemplacement.FieldCreatedAt)).
		Limit(limit).
		Offset(offset).
		All(c.Request.Context())
	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"erreur": "impossible de lister les liens"})
		return
	}
	respondPaginated(c, items, total, page, limit)
}

// @Summary Creer un lien objet/emplacement
// @Tags LiensEmplacements
// @Accept json
// @Produce json
// @Param id path string true "ID objet"
// @Param body body lienObjetEmplacementRequest true "Lien"
// @Success 201 {object} ent.LienObjetEmplacement
// @Failure 400 {object} map[string]string
// @Failure 404 {object} map[string]string
// @Failure 409 {object} map[string]string
// @Failure 500 {object} map[string]string
// @Router /objets/{id}/liens_emplacements [post]
func (h *Handler) CreateLienEmplacement(c *gin.Context) {
	objetID, err := uuid.Parse(c.Param("id"))
	if err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"erreur": "identifiant invalide"})
		return
	}

	var req lienObjetEmplacementRequest
	if err := c.ShouldBindJSON(&req); err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"erreur": "donnees invalides"})
		return
	}
	if req.EmplacementID == nil || *req.EmplacementID == "" {
		c.JSON(http.StatusBadRequest, gin.H{"erreur": "emplacement_id obligatoire"})
		return
	}

	emplacementID, err := uuid.Parse(*req.EmplacementID)
	if err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"erreur": "emplacement_id invalide"})
		return
	}

	if _, err := h.client.Emplacement.Get(c.Request.Context(), emplacementID); err != nil {
		if ent.IsNotFound(err) {
			c.JSON(http.StatusNotFound, gin.H{"erreur": "emplacement introuvable"})
			return
		}
		c.JSON(http.StatusInternalServerError, gin.H{"erreur": "impossible de charger l'emplacement"})
		return
	}

	create := h.client.LienObjetEmplacement.Create().
		SetObjetID(objetID).
		SetEmplacementID(emplacementID)

	typeValeur := lienobjetemplacement.TypeStocke
	if req.Type != nil && *req.Type != "" {
		parsed, ok := parseLienType(*req.Type)
		if !ok {
			c.JSON(http.StatusBadRequest, gin.H{"erreur": "type invalide"})
			return
		}
		typeValeur = parsed
	}
	create.SetType(typeValeur)

	exists, err := h.client.LienObjetEmplacement.Query().
		Where(
			lienobjetemplacement.ObjetID(objetID),
			lienobjetemplacement.EmplacementID(emplacementID),
			lienobjetemplacement.TypeEQ(typeValeur),
		).
		Exist(c.Request.Context())
	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"erreur": "impossible de verifier le lien"})
		return
	}
	if exists {
		c.JSON(http.StatusConflict, gin.H{"erreur": "lien deja existant"})
		return
	}

	created, err := create.Save(c.Request.Context())
	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"erreur": "impossible de creer le lien"})
		return
	}
	c.JSON(http.StatusCreated, created)
}

// @Summary Supprimer un lien objet/emplacement
// @Tags LiensEmplacements
// @Param id path string true "ID lien"
// @Success 204 {string} string "No Content"
// @Failure 400 {object} map[string]string
// @Failure 404 {object} map[string]string
// @Failure 500 {object} map[string]string
// @Router /liens_emplacements/{id} [delete]
func (h *Handler) DeleteLienEmplacement(c *gin.Context) {
	lienID, err := uuid.Parse(c.Param("id"))
	if err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"erreur": "identifiant invalide"})
		return
	}

	if err := h.client.LienObjetEmplacement.DeleteOneID(lienID).Exec(c.Request.Context()); err != nil {
		if ent.IsNotFound(err) {
			c.JSON(http.StatusNotFound, gin.H{"erreur": "lien introuvable"})
			return
		}
		c.JSON(http.StatusInternalServerError, gin.H{"erreur": "impossible de supprimer le lien"})
		return
	}
	c.Status(http.StatusNoContent)
}

// @Summary Mettre a jour un lien objet/emplacement
// @Tags LiensEmplacements
// @Accept json
// @Produce json
// @Param id path string true "ID lien"
// @Param body body lienObjetEmplacementUpdateRequest true "Mise a jour"
// @Success 200 {object} ent.LienObjetEmplacement
// @Failure 400 {object} map[string]string
// @Failure 404 {object} map[string]string
// @Failure 409 {object} map[string]string
// @Failure 500 {object} map[string]string
// @Router /liens_emplacements/{id} [put]
func (h *Handler) UpdateLienEmplacement(c *gin.Context) {
	lienID, err := uuid.Parse(c.Param("id"))
	if err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"erreur": "identifiant invalide"})
		return
	}

	var req lienObjetEmplacementUpdateRequest
	if err := c.ShouldBindJSON(&req); err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"erreur": "donnees invalides"})
		return
	}

	existant, err := h.client.LienObjetEmplacement.Get(c.Request.Context(), lienID)
	if err != nil {
		if ent.IsNotFound(err) {
			c.JSON(http.StatusNotFound, gin.H{"erreur": "lien introuvable"})
			return
		}
		c.JSON(http.StatusInternalServerError, gin.H{"erreur": "impossible de charger le lien"})
		return
	}

	nouveauEmplacementID := existant.EmplacementID
	if req.EmplacementID != nil && *req.EmplacementID != "" {
		parsed, err := uuid.Parse(*req.EmplacementID)
		if err != nil {
			c.JSON(http.StatusBadRequest, gin.H{"erreur": "emplacement_id invalide"})
			return
		}
		if _, err := h.client.Emplacement.Get(c.Request.Context(), parsed); err != nil {
			if ent.IsNotFound(err) {
				c.JSON(http.StatusNotFound, gin.H{"erreur": "emplacement introuvable"})
				return
			}
			c.JSON(http.StatusInternalServerError, gin.H{"erreur": "impossible de charger l'emplacement"})
			return
		}
		nouveauEmplacementID = parsed
	}

	nouveauType := existant.Type
	if req.Type != nil && *req.Type != "" {
		parsed, ok := parseLienType(*req.Type)
		if !ok {
			c.JSON(http.StatusBadRequest, gin.H{"erreur": "type invalide"})
			return
		}
		nouveauType = parsed
	}

	dupe, err := h.client.LienObjetEmplacement.Query().
		Where(
			lienobjetemplacement.ObjetID(existant.ObjetID),
			lienobjetemplacement.EmplacementID(nouveauEmplacementID),
			lienobjetemplacement.TypeEQ(nouveauType),
			lienobjetemplacement.IDNEQ(lienID),
		).
		Exist(c.Request.Context())
	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"erreur": "impossible de verifier le lien"})
		return
	}
	if dupe {
		c.JSON(http.StatusConflict, gin.H{"erreur": "lien deja existant"})
		return
	}

	updated, err := h.client.LienObjetEmplacement.UpdateOneID(lienID).
		SetEmplacementID(nouveauEmplacementID).
		SetType(nouveauType).
		Save(c.Request.Context())
	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"erreur": "impossible de mettre a jour le lien"})
		return
	}
	c.JSON(http.StatusOK, updated)
}

func parseLienType(value string) (lienobjetemplacement.Type, bool) {
	switch value {
	case "stocke":
		return lienobjetemplacement.TypeStocke, true
	case "utilise_dans":
		return lienobjetemplacement.TypeUtiliseDans, true
	default:
		return "", false
	}
}
