package handlers

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

	"github.com/CubeCraft-Creations/Extrudex/backend/internal/dtos"
	"github.com/CubeCraft-Creations/Extrudex/backend/internal/models"
	"github.com/CubeCraft-Creations/Extrudex/backend/internal/repositories"
	"github.com/CubeCraft-Creations/Extrudex/backend/internal/services"
	"github.com/go-chi/chi/v5"
)

// FilamentHandler handles HTTP requests for filament spool CRUD operations.
type FilamentHandler struct {
	service *services.FilamentService
}

// NewFilamentHandler creates a FilamentHandler with the given service.
func NewFilamentHandler(service *services.FilamentService) *FilamentHandler {
	return &FilamentHandler{service: service}
}

// List handles GET /api/filaments — returns paginated, filtered spools.
func (h *FilamentHandler) List(w http.ResponseWriter, r *http.Request) {
	limit, offset := parsePagination(r)
	filter := repositories.FilamentFilter{
		Material: r.URL.Query().Get("material"),
		Finish:   r.URL.Query().Get("finish"),
		Color:    r.URL.Query().Get("color"),
		LowStock: r.URL.Query().Get("low_stock") == "true",
		Limit:    limit,
		Offset:   offset,
	}

	spools, total, err := h.service.List(r.Context(), filter)
	if err != nil {
		slog.Error("failed to list filaments", "error", err)
		writeJSON(w, http.StatusInternalServerError, dtos.ErrorResponse{
			Error: "internal server error",
			Code:  http.StatusInternalServerError,
		})
		return
	}

	writeJSON(w, http.StatusOK, dtos.ListResponse{
		Data:   spools,
		Total:  total,
		Limit:  limit,
		Offset: offset,
	})
}

// Get handles GET /api/filaments/{id} — returns a single spool.
func (h *FilamentHandler) Get(w http.ResponseWriter, r *http.Request) {
	id, err := strconv.Atoi(chi.URLParam(r, "id"))
	if err != nil {
		writeJSON(w, http.StatusBadRequest, dtos.ErrorResponse{
			Error: "invalid filament ID",
			Code:  http.StatusBadRequest,
		})
		return
	}

	spool, err := h.service.GetByID(r.Context(), id)
	if err != nil {
		slog.Error("failed to get filament", "id", id, "error", err)
		writeJSON(w, http.StatusInternalServerError, dtos.ErrorResponse{
			Error: "internal server error",
			Code:  http.StatusInternalServerError,
		})
		return
	}
	if spool == nil {
		writeJSON(w, http.StatusNotFound, dtos.ErrorResponse{
			Error: "filament not found",
			Code:  http.StatusNotFound,
		})
		return
	}

	writeJSON(w, http.StatusOK, dtos.SingleResponse{Data: spool})
}

// Create handles POST /api/filaments — creates a new filament spool.
func (h *FilamentHandler) Create(w http.ResponseWriter, r *http.Request) {
	var req dtos.CreateFilamentRequest
	if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
		writeJSON(w, http.StatusBadRequest, dtos.ErrorResponse{
			Error: "invalid request body",
			Code:  http.StatusBadRequest,
		})
		return
	}

	// Validate required fields.
	if err := services.ValidateCreateFilamentRequest(req); err != nil {
		writeJSON(w, http.StatusBadRequest, dtos.ErrorResponse{
			Error: "validation failed: " + err.Error(),
			Code:  http.StatusBadRequest,
		})
		return
	}

	// Build domain model.
	spool := models.FilamentSpool{
		Name:                  req.Name,
		MaterialBaseID:        req.MaterialBaseID,
		MaterialFinishID:      req.MaterialFinishID,
		MaterialModifierID:    req.MaterialModifierID,
		ColorHex:             req.ColorHex,
		Brand:                req.Brand,
		DiameterMM:           1.75, // default
		InitialGrams:         req.InitialGrams,
		RemainingGrams:       req.RemainingGrams,
		SpoolWeightGrams:     req.SpoolWeightGrams,
		CostUSD:              req.CostUSD,
		LowStockThresholdGrams: 50, // default
		Notes:                req.Notes,
		Barcode:              req.Barcode,
	}
	if req.DiameterMM != nil {
		spool.DiameterMM = *req.DiameterMM
	}
	if req.LowStockThresholdGrams != nil {
		spool.LowStockThresholdGrams = *req.LowStockThresholdGrams
	}

	created, err := h.service.Create(r.Context(), &spool)
	if err != nil {
		slog.Error("failed to create filament", "error", err)
		writeJSON(w, http.StatusInternalServerError, dtos.ErrorResponse{
			Error: "internal server error",
			Code:  http.StatusInternalServerError,
		})
		return
	}

	writeJSON(w, http.StatusCreated, dtos.SingleResponse{Data: created})
}

// Update handles PUT /api/filaments/{id} — partially updates a spool.
func (h *FilamentHandler) Update(w http.ResponseWriter, r *http.Request) {
	id, err := strconv.Atoi(chi.URLParam(r, "id"))
	if err != nil {
		writeJSON(w, http.StatusBadRequest, dtos.ErrorResponse{
			Error: "invalid filament ID",
			Code:  http.StatusBadRequest,
		})
		return
	}

	var req dtos.UpdateFilamentRequest
	if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
		writeJSON(w, http.StatusBadRequest, dtos.ErrorResponse{
			Error: "invalid request body",
			Code:  http.StatusBadRequest,
		})
		return
	}

	// Validate update fields.
	if err := services.ValidateUpdateFilamentRequest(req); err != nil {
		writeJSON(w, http.StatusBadRequest, dtos.ErrorResponse{
			Error: "validation failed: " + err.Error(),
			Code:  http.StatusBadRequest,
		})
		return
	}

	// Build updates map (only non-nil fields).
	updates := buildFilamentUpdates(req)

	updated, err := h.service.Update(r.Context(), id, updates)
	if err != nil {
		slog.Error("failed to update filament", "id", id, "error", err)
		writeJSON(w, http.StatusInternalServerError, dtos.ErrorResponse{
			Error: "internal server error",
			Code:  http.StatusInternalServerError,
		})
		return
	}
	if updated == nil {
		writeJSON(w, http.StatusNotFound, dtos.ErrorResponse{
			Error: "filament not found",
			Code:  http.StatusNotFound,
		})
		return
	}

	writeJSON(w, http.StatusOK, dtos.SingleResponse{Data: updated})
}

// Delete handles DELETE /api/filaments/{id} — soft-deletes a spool.
func (h *FilamentHandler) Delete(w http.ResponseWriter, r *http.Request) {
	id, err := strconv.Atoi(chi.URLParam(r, "id"))
	if err != nil {
		writeJSON(w, http.StatusBadRequest, dtos.ErrorResponse{
			Error: "invalid filament ID",
			Code:  http.StatusBadRequest,
		})
		return
	}

	deleted, err := h.service.SoftDelete(r.Context(), id)
	if err != nil {
		slog.Error("failed to delete filament", "id", id, "error", err)
		writeJSON(w, http.StatusInternalServerError, dtos.ErrorResponse{
			Error: "internal server error",
			Code:  http.StatusInternalServerError,
		})
		return
	}
	if !deleted {
		writeJSON(w, http.StatusNotFound, dtos.ErrorResponse{
			Error: "filament not found",
			Code:  http.StatusNotFound,
		})
		return
	}

	w.WriteHeader(http.StatusNoContent)
}

// buildFilamentUpdates converts an UpdateFilamentRequest to a map of column→value.
func buildFilamentUpdates(req dtos.UpdateFilamentRequest) map[string]interface{} {
	updates := make(map[string]interface{})
	if req.Name != nil {
		updates["name"] = *req.Name
	}
	if req.MaterialBaseID != nil {
		updates["material_base_id"] = *req.MaterialBaseID
	}
	if req.MaterialFinishID != nil {
		updates["material_finish_id"] = *req.MaterialFinishID
	}
	if req.MaterialModifierID != nil {
		updates["material_modifier_id"] = *req.MaterialModifierID
	}
	if req.ColorHex != nil {
		updates["color_hex"] = *req.ColorHex
	}
	if req.Brand != nil {
		updates["brand"] = *req.Brand
	}
	if req.DiameterMM != nil {
		updates["diameter_mm"] = *req.DiameterMM
	}
	if req.InitialGrams != nil {
		updates["initial_grams"] = *req.InitialGrams
	}
	if req.RemainingGrams != nil {
		updates["remaining_grams"] = *req.RemainingGrams
	}
	if req.SpoolWeightGrams != nil {
		updates["spool_weight_grams"] = *req.SpoolWeightGrams
	}
	if req.CostUSD != nil {
		updates["cost_usd"] = *req.CostUSD
	}
	if req.LowStockThresholdGrams != nil {
		updates["low_stock_threshold_grams"] = *req.LowStockThresholdGrams
	}
	if req.Notes != nil {
		updates["notes"] = *req.Notes
	}
	if req.Barcode != nil {
		updates["barcode"] = *req.Barcode
	}
	return updates
}
