package handlers

import (
	"bytes"
	"context"
	"encoding/csv"
	"encoding/json"
	"errors"
	"fmt"
	"io"
	"net/http"
	"strconv"
	"strings"
	"time"

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

type importAchat struct {
	Nom              string         `json:"nom"`
	Description      string         `json:"description,omitempty"`
	Quantite         int            `json:"quantite,omitempty"`
	PrixAchat        float64        `json:"prix_achat,omitempty"`
	DateAchat        string         `json:"date_achat,omitempty"`
	Boutique         string         `json:"boutique,omitempty"`
	NumeroSerie      string         `json:"numero_serie,omitempty"`
	NumeroModele     string         `json:"numero_modele,omitempty"`
	Fabricant        string         `json:"fabricant,omitempty"`
	Caracteristiques map[string]any `json:"caracteristiques,omitempty"`
}

type importAchatPayload struct {
	Boutique string        `json:"boutique"`
	Achats   []importAchat `json:"achats"`
}

type importErreur struct {
	Ligne   int    `json:"ligne,omitempty"`
	Message string `json:"message"`
	Nom     string `json:"nom,omitempty"`
}

type importResultat struct {
	Importes int            `json:"importes"`
	Crees    int            `json:"crees"`
	Doublons int            `json:"doublons"`
	Erreurs  []importErreur `json:"erreurs"`
}

// @Summary Importer des achats (CSV/JSON)
// @Tags Imports
// @Accept multipart/form-data,json
// @Produce json
// @Param boutique formData string false "Boutique (amazon, aliexpress, generic)"
// @Param fichier formData file false "Fichier CSV ou JSON"
// @Success 200 {object} importResultat
// @Failure 400 {object} map[string]string
// @Failure 500 {object} map[string]string
// @Router /imports/achats [post]
func (h *Handler) ImportAchats(c *gin.Context) {
	contentType := c.GetHeader("Content-Type")
	if strings.Contains(contentType, "application/json") {
		h.handleImportJSON(c)
		return
	}
	h.handleImportMultipart(c)
}

func (h *Handler) handleImportJSON(c *gin.Context) {
	raw, err := io.ReadAll(c.Request.Body)
	if err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"erreur": "lecture JSON impossible"})
		return
	}
	raw = bytes.TrimSpace(raw)
	if len(raw) == 0 {
		c.JSON(http.StatusBadRequest, gin.H{"erreur": "corps JSON vide"})
		return
	}

	var payload importAchatPayload
	if err := json.Unmarshal(raw, &payload); err == nil && len(payload.Achats) > 0 {
		if payload.Boutique != "" {
			for i := range payload.Achats {
				if payload.Achats[i].Boutique == "" {
					payload.Achats[i].Boutique = payload.Boutique
				}
			}
		}
		result := h.persistImportAchats(c, payload.Achats)
		c.JSON(http.StatusOK, result)
		return
	}

	var achats []importAchat
	if err := json.Unmarshal(raw, &achats); err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"erreur": "format JSON invalide"})
		return
	}
	result := h.persistImportAchats(c, achats)
	c.JSON(http.StatusOK, result)
}

func (h *Handler) handleImportMultipart(c *gin.Context) {
	boutique := strings.TrimSpace(c.PostForm("boutique"))
	file, header, err := c.Request.FormFile("fichier")
	if err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"erreur": "fichier manquant"})
		return
	}
	defer file.Close()

	data, err := io.ReadAll(file)
	if err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"erreur": "lecture fichier impossible"})
		return
	}
	if len(data) == 0 {
		c.JSON(http.StatusBadRequest, gin.H{"erreur": "fichier vide"})
		return
	}

	extension := strings.ToLower(header.Filename)
	if strings.HasSuffix(extension, ".json") {
		var payload importAchatPayload
		if err := json.Unmarshal(data, &payload); err == nil && len(payload.Achats) > 0 {
			if boutique == "" {
				boutique = payload.Boutique
			}
			if boutique != "" {
				for i := range payload.Achats {
					if payload.Achats[i].Boutique == "" {
						payload.Achats[i].Boutique = boutique
					}
				}
			}
			result := h.persistImportAchats(c, payload.Achats)
			c.JSON(http.StatusOK, result)
			return
		}

		var achats []importAchat
		if err := json.Unmarshal(data, &achats); err != nil {
			c.JSON(http.StatusBadRequest, gin.H{"erreur": "JSON invalide"})
			return
		}
		if boutique != "" {
			for i := range achats {
				if achats[i].Boutique == "" {
					achats[i].Boutique = boutique
				}
			}
		}
		result := h.persistImportAchats(c, achats)
		c.JSON(http.StatusOK, result)
		return
	}

	achats, erreurs := parseCSVImports(data, boutique)
	result := h.persistImportAchats(c, achats)
	result.Erreurs = append(result.Erreurs, erreurs...)
	c.JSON(http.StatusOK, result)
}

func (h *Handler) persistImportAchats(c *gin.Context, achats []importAchat) importResultat {
	result := importResultat{Importes: len(achats)}
	ctx := c.Request.Context()

	for index, achat := range achats {
		line := index + 1
		achat.Nom = strings.TrimSpace(achat.Nom)
		if achat.Nom == "" {
			result.Erreurs = append(result.Erreurs, importErreur{
				Ligne:   line,
				Message: "nom manquant",
			})
			continue
		}

		if achat.Quantite <= 0 {
			achat.Quantite = 1
		}

		var parsedDate *time.Time
		if achat.DateAchat != "" {
			value, err := parseImportDate(achat.DateAchat)
			if err != nil {
				result.Erreurs = append(result.Erreurs, importErreur{
					Ligne:   line,
					Message: "date invalide",
					Nom:     achat.Nom,
				})
				continue
			}
			parsedDate = &value
		}

		if achat.PrixAchat < 0 {
			result.Erreurs = append(result.Erreurs, importErreur{
				Ligne:   line,
				Message: "prix invalide",
				Nom:     achat.Nom,
			})
			continue
		}

		duplicate, err := h.isDuplicateAchat(ctx, achat, parsedDate)
		if err != nil {
			result.Erreurs = append(result.Erreurs, importErreur{
				Ligne:   line,
				Message: "verification doublon impossible",
				Nom:     achat.Nom,
			})
			continue
		}
		if duplicate {
			result.Doublons++
			continue
		}

		create := h.client.Objet.Create().
			SetNom(achat.Nom).
			SetQuantite(achat.Quantite)

		if achat.Description != "" {
			create.SetDescription(achat.Description)
		}
		if achat.PrixAchat > 0 {
			create.SetPrixAchat(achat.PrixAchat)
		}
		if parsedDate != nil {
			create.SetDateAchat(*parsedDate)
		}
		if achat.Boutique != "" {
			create.SetBoutique(achat.Boutique)
		}
		if achat.NumeroSerie != "" {
			create.SetNumeroSerie(achat.NumeroSerie)
		}
		if achat.NumeroModele != "" {
			create.SetNumeroModele(achat.NumeroModele)
		}
		if achat.Fabricant != "" {
			create.SetFabricant(achat.Fabricant)
		}
		if len(achat.Caracteristiques) > 0 {
			create.SetCaracteristiques(achat.Caracteristiques)
		}

		if _, err := create.Save(ctx); err != nil {
			result.Erreurs = append(result.Erreurs, importErreur{
				Ligne:   line,
				Message: "creation impossible",
				Nom:     achat.Nom,
			})
			continue
		}
		result.Crees++
	}

	return result
}

func (h *Handler) isDuplicateAchat(ctx context.Context, achat importAchat, date *time.Time) (bool, error) {
	query := h.client.Objet.Query().Where(objet.NomEQ(achat.Nom))
	if achat.Boutique != "" {
		query = query.Where(objet.BoutiqueEQ(achat.Boutique))
	}
	if date != nil {
		query = query.Where(objet.DateAchatEQ(*date))
	}
	if achat.PrixAchat > 0 {
		query = query.Where(objet.PrixAchatEQ(achat.PrixAchat))
	}
	count, err := query.Count(ctx)
	return count > 0, err
}

func parseCSVImports(data []byte, boutique string) ([]importAchat, []importErreur) {
	separator := detectCSVSeparator(data)
	reader := csv.NewReader(bytes.NewReader(data))
	reader.Comma = separator
	reader.FieldsPerRecord = -1
	reader.TrimLeadingSpace = true

	header, err := reader.Read()
	if err != nil {
		return nil, []importErreur{{Message: "lecture entete impossible"}}
	}

	headerIndex := mapCSVHeader(header, boutique)
	if len(headerIndex) == 0 {
		return nil, []importErreur{{Message: "entete CSV non reconnue"}}
	}

	var achats []importAchat
	var erreurs []importErreur
	line := 1
	for {
		record, err := reader.Read()
		if errors.Is(err, io.EOF) {
			break
		}
		line++
		if err != nil {
			erreurs = append(erreurs, importErreur{Ligne: line, Message: "ligne invalide"})
			continue
		}
		achat, err := achatFromCSV(record, headerIndex, boutique)
		if err != nil {
			erreurs = append(erreurs, importErreur{Ligne: line, Message: err.Error()})
			continue
		}
		achats = append(achats, achat)
	}
	return achats, erreurs
}

func detectCSVSeparator(data []byte) rune {
	firstLine := string(data)
	if idx := strings.Index(firstLine, "\n"); idx > 0 {
		firstLine = firstLine[:idx]
	}
	if strings.Count(firstLine, ";") > strings.Count(firstLine, ",") {
		return ';'
	}
	return ','
}

func mapCSVHeader(header []string, boutique string) map[string]int {
	index := make(map[string]int)
	for i, col := range header {
		key := normalizeCSVHeader(col)
		switch key {
		case "item name", "product name", "item", "nom", "designation":
			index["nom"] = i
		case "quantity", "qty", "quantite":
			index["quantite"] = i
		case "item price", "unit price", "price", "prix", "item total", "total price", "total":
			if _, ok := index["prix"]; !ok {
				index["prix"] = i
			}
		case "order date", "purchase date", "date", "date achat", "order time":
			index["date"] = i
		case "order id", "order number", "commande", "numero commande":
			index["order_id"] = i
		case "brand", "fabricant", "manufacturer":
			index["fabricant"] = i
		case "store name", "seller", "boutique":
			index["boutique"] = i
		}
	}

	if _, ok := index["nom"]; !ok {
		return nil
	}
	if boutique == "" {
		if _, ok := index["boutique"]; !ok {
			index["boutique"] = -1
		}
	}
	return index
}

func normalizeCSVHeader(value string) string {
	value = strings.TrimSpace(strings.ToLower(value))
	value = strings.ReplaceAll(value, "\ufeff", "")
	return value
}

func achatFromCSV(record []string, index map[string]int, fallbackBoutique string) (importAchat, error) {
	nom := csvValue(record, index["nom"])
	if nom == "" {
		return importAchat{}, fmt.Errorf("nom manquant")
	}

	quantite := parseIntOrDefault(csvValue(record, index["quantite"]), 1)
	prix, err := parsePrice(csvValue(record, index["prix"]))
	if err != nil && csvValue(record, index["prix"]) != "" {
		return importAchat{}, fmt.Errorf("prix invalide")
	}

	dateValue := csvValue(record, index["date"])
	if dateValue != "" {
		if _, err := parseImportDate(dateValue); err != nil {
			return importAchat{}, fmt.Errorf("date invalide")
		}
	}

	boutique := fallbackBoutique
	if boutique == "" {
		boutique = csvValue(record, index["boutique"])
	}

	achat := importAchat{
		Nom:       nom,
		Quantite:  quantite,
		PrixAchat: prix,
		DateAchat: dateValue,
		Boutique:  boutique,
	}

	orderID := csvValue(record, index["order_id"])
	if orderID != "" {
		achat.Caracteristiques = map[string]any{
			"order_id": orderID,
			"source":   boutique,
		}
	}

	if fabricant := csvValue(record, index["fabricant"]); fabricant != "" {
		achat.Fabricant = fabricant
	}
	return achat, nil
}

func csvValue(record []string, idx int) string {
	if idx < 0 || idx >= len(record) {
		return ""
	}
	return strings.TrimSpace(record[idx])
}

func parseIntOrDefault(value string, fallback int) int {
	if value == "" {
		return fallback
	}
	parsed, err := strconv.Atoi(strings.TrimSpace(value))
	if err != nil || parsed <= 0 {
		return fallback
	}
	return parsed
}

func parsePrice(value string) (float64, error) {
	value = strings.TrimSpace(value)
	if value == "" {
		return 0, nil
	}
	value = strings.ReplaceAll(value, "€", "")
	value = strings.ReplaceAll(value, "$", "")
	value = strings.ReplaceAll(value, "EUR", "")
	value = strings.ReplaceAll(value, "USD", "")
	value = strings.ReplaceAll(value, " ", "")
	value = strings.ReplaceAll(value, ",", ".")
	value = strings.TrimSpace(value)
	if value == "" {
		return 0, nil
	}
	return strconv.ParseFloat(value, 64)
}

func parseImportDate(value string) (time.Time, error) {
	value = strings.TrimSpace(value)
	formats := []string{
		time.RFC3339,
		"2006-01-02",
		"02/01/2006",
		"01/02/2006",
		"2006/01/02",
		"02-01-2006",
		"01-02-2006",
	}
	for _, format := range formats {
		if parsed, err := time.Parse(format, value); err == nil {
			return parsed, nil
		}
	}
	return time.Time{}, fmt.Errorf("format date inconnu")
}
