package handlers

import (
	"errors"
	"net/http"
	"os"
	"path/filepath"
	"strings"

	"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/piecejointe"
)

// UploadPiecesJointes gere l'upload multiple des fichiers d'un objet.
// @Summary Uploader des pieces jointes
// @Tags PiecesJointes
// @Accept multipart/form-data
// @Produce json
// @Param id path string true "ID objet"
// @Param fichiers formData file true "Fichiers (multi)"
// @Success 201 {object} map[string]any
// @Failure 400 {object} map[string]string
// @Failure 404 {object} map[string]string
// @Failure 413 {object} map[string]string
// @Failure 500 {object} map[string]string
// @Router /objets/{id}/pieces_jointes [post]
func (h *Handler) UploadPiecesJointes(c *gin.Context) {
	id, err := uuid.Parse(c.Param("id"))
	if err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"erreur": "identifiant invalide"})
		return
	}

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

	form, err := c.MultipartForm()
	if err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"erreur": "formulaire invalide"})
		return
	}

	files := form.File["fichiers"]
	if len(files) == 0 {
		c.JSON(http.StatusBadRequest, gin.H{"erreur": "aucun fichier fourni"})
		return
	}

	maxBytes := maxUploadBytes()

	hasPrincipale, err := h.client.PieceJointe.Query().
		Where(
			piecejointe.ObjetID(id),
			piecejointe.EstPrincipale(true),
		).
		Exist(c.Request.Context())
	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"erreur": "impossible de verifier la piece jointe principale"})
		return
	}

	baseDir := os.Getenv("ATTACHMENTS_DIR")
	if baseDir == "" {
		baseDir = "./data/pieces_jointes"
	}
	if err := os.MkdirAll(baseDir, 0o755); err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"erreur": "impossible de creer le dossier de stockage"})
		return
	}

	var saved []map[string]any
	for idx, file := range files {
		ext := strings.ToLower(filepath.Ext(file.Filename))
		if file.Size > maxBytes {
			c.JSON(http.StatusRequestEntityTooLarge, gin.H{"erreur": "fichier trop volumineux"})
			return
		}

		mime := file.Header.Get("Content-Type")
		if !isAllowedUpload(mime, ext) {
			c.JSON(http.StatusBadRequest, gin.H{"erreur": "type de fichier non supporte"})
			return
		}

		uuidName := uuid.New().String() + ext
		path := filepath.Join(baseDir, uuidName)

		if err := c.SaveUploadedFile(file, path); err != nil {
			c.JSON(http.StatusInternalServerError, gin.H{"erreur": "echec enregistrement fichier"})
			return
		}

		categorie := resolveCategorie(ext, mime)
		estPrincipale := idx == 0 && !hasPrincipale

		pj, err := h.client.PieceJointe.Create().
			SetObjetID(id).
			SetNomFichier(file.Filename).
			SetChemin(path).
			SetTypeMime(mime).
			SetCategorie(piecejointe.Categorie(categorie)).
			SetEstPrincipale(estPrincipale).
			Save(c.Request.Context())
		if err != nil {
			c.JSON(http.StatusInternalServerError, gin.H{"erreur": "impossible d'enregistrer la piece jointe"})
			return
		}

		saved = append(saved, map[string]any{
			"id":             pj.ID,
			"nom_fichier":    pj.NomFichier,
			"chemin":         pj.Chemin,
			"type_mime":      pj.TypeMime,
			"categorie":      pj.Categorie,
			"est_principale": pj.EstPrincipale,
		})
	}

	c.JSON(http.StatusCreated, gin.H{"pieces_jointes": saved})
}

// ListPiecesJointes retourne la liste des pieces jointes d'un objet.
// @Summary Lister les pieces jointes
// @Tags PiecesJointes
// @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}/pieces_jointes [get]
func (h *Handler) ListPiecesJointes(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.PieceJointe.Query().
		Where(piecejointe.ObjetID(objetID))
	total, err := query.Count(c.Request.Context())
	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"erreur": "impossible de compter les pieces jointes"})
		return
	}

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

// DeletePieceJointe supprime une piece jointe et son fichier.
// @Summary Supprimer une piece jointe
// @Tags PiecesJointes
// @Param id path string true "ID piece jointe"
// @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 /pieces_jointes/{id} [delete]
func (h *Handler) DeletePieceJointe(c *gin.Context) {
	pieceID, err := uuid.Parse(c.Param("id"))
	if err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"erreur": "identifiant invalide"})
		return
	}

	item, err := h.client.PieceJointe.Get(c.Request.Context(), pieceID)
	if err != nil {
		if ent.IsNotFound(err) {
			c.JSON(http.StatusNotFound, gin.H{"erreur": "piece jointe introuvable"})
			return
		}
		c.JSON(http.StatusInternalServerError, gin.H{"erreur": "impossible de charger la piece jointe"})
		return
	}

	if item.Chemin != "" {
		if err := os.Remove(item.Chemin); err != nil && !errors.Is(err, os.ErrNotExist) {
			c.JSON(http.StatusInternalServerError, gin.H{"erreur": "impossible de supprimer le fichier"})
			return
		}
	}

	if err := h.client.PieceJointe.DeleteOneID(pieceID).Exec(c.Request.Context()); err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"erreur": "impossible de supprimer la piece jointe"})
		return
	}
	c.Status(http.StatusNoContent)
}

// SetPieceJointePrincipale marque une piece jointe comme principale.
// @Summary Definir la piece jointe principale
// @Tags PiecesJointes
// @Produce json
// @Param id path string true "ID piece jointe"
// @Success 200 {object} ent.PieceJointe
// @Failure 400 {object} map[string]string
// @Failure 404 {object} map[string]string
// @Failure 500 {object} map[string]string
// @Router /pieces_jointes/{id}/principale [put]
func (h *Handler) SetPieceJointePrincipale(c *gin.Context) {
	pieceID, err := uuid.Parse(c.Param("id"))
	if err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"erreur": "identifiant invalide"})
		return
	}

	item, err := h.client.PieceJointe.Get(c.Request.Context(), pieceID)
	if err != nil {
		if ent.IsNotFound(err) {
			c.JSON(http.StatusNotFound, gin.H{"erreur": "piece jointe introuvable"})
			return
		}
		c.JSON(http.StatusInternalServerError, gin.H{"erreur": "impossible de charger la piece jointe"})
		return
	}

	tx, err := h.client.Tx(c.Request.Context())
	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"erreur": "impossible d'ouvrir la transaction"})
		return
	}
	defer func() {
		if err != nil {
			_ = tx.Rollback()
		}
	}()

	if _, err = tx.PieceJointe.Update().
		Where(piecejointe.ObjetID(item.ObjetID)).
		SetEstPrincipale(false).
		Save(c.Request.Context()); err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"erreur": "impossible de reinitialiser la principale"})
		_ = tx.Rollback()
		return
	}

	if _, err = tx.PieceJointe.UpdateOneID(pieceID).
		SetEstPrincipale(true).
		Save(c.Request.Context()); err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"erreur": "impossible de definir la principale"})
		_ = tx.Rollback()
		return
	}

	if err = tx.Commit(); err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"erreur": "impossible de finaliser la transaction"})
		return
	}

	updated, err := h.client.PieceJointe.Get(c.Request.Context(), pieceID)
	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"erreur": "impossible de charger la piece jointe"})
		return
	}
	c.JSON(http.StatusOK, updated)
}
