package handler

import (
	"errors"
	"net/http"
	"strings"

	"github.com/gofrs/uuid/v5"

	"github.com/saveinmed/backend-go/internal/domain"
)

// GetShippingSettings godoc
// @Summary Get vendor shipping settings
// @Description Returns pickup and delivery settings for a vendor.
// @Tags Shipping
// @Produce json
// @Param vendor_id path string true "Vendor ID"
// @Success 200 {object} domain.ShippingSettings
// @Failure 400 {object} map[string]string
// @Failure 403 {object} map[string]string
// @Failure 500 {object} map[string]string
// @Router /api/v1/shipping/settings/{vendor_id} [get]
func (h *Handler) GetShippingSettings(w http.ResponseWriter, r *http.Request) {
	vendorID, err := parseUUIDFromPath(r.URL.Path)
	if err != nil {
		writeError(w, http.StatusBadRequest, err)
		return
	}

	// Any authenticated user can view shipping settings (needed for checkout)
	// No role-based restriction here - shipping settings are public info for buyers

	settings, err := h.svc.GetShippingSettings(r.Context(), vendorID)
	if err != nil {
		// Log error if needed, but for 404/not found we might return empty object
		writeError(w, http.StatusInternalServerError, err)
		return
	}
	if settings == nil {
		// Return defaults
		settings = &domain.ShippingSettings{VendorID: vendorID, Active: false}
	}
	writeJSON(w, http.StatusOK, settings)
}

// UpsertShippingSettings godoc
// @Summary Update vendor shipping settings
// @Description Stores pickup and delivery settings for a vendor.
// @Tags Shipping
// @Accept json
// @Produce json
// @Param vendor_id path string true "Vendor ID"
// @Param payload body shippingSettingsRequest true "Shipping settings"
// @Success 200 {object} domain.ShippingSettings
// @Failure 400 {object} map[string]string
// @Failure 403 {object} map[string]string
// @Failure 500 {object} map[string]string
// @Router /api/v1/shipping/settings/{vendor_id} [put]
func (h *Handler) UpsertShippingSettings(w http.ResponseWriter, r *http.Request) {
	vendorID, err := parseUUIDFromPath(r.URL.Path)
	if err != nil {
		writeError(w, http.StatusBadRequest, err)
		return
	}

	requester, err := getRequester(r)
	if err != nil {
		writeError(w, http.StatusBadRequest, err)
		return
	}
	if !strings.EqualFold(requester.Role, "Admin") {
		if requester.CompanyID == nil || *requester.CompanyID != vendorID {
			writeError(w, http.StatusForbidden, errors.New("not allowed to update shipping settings"))
			return
		}
	}

	var req shippingSettingsRequest
	if err := decodeJSON(r.Context(), r, &req); err != nil {
		writeError(w, http.StatusBadRequest, err)
		return
	}

	if req.Active {
		if req.MaxRadiusKm < 0 {
			writeError(w, http.StatusBadRequest, errors.New("max_radius_km must be >= 0"))
			return
		}
		if req.PricePerKmCents < 0 || req.MinFeeCents < 0 {
			writeError(w, http.StatusBadRequest, errors.New("pricing fields must be >= 0"))
			return
		}
	}
	if req.PickupActive {
		if strings.TrimSpace(req.PickupAddress) == "" || strings.TrimSpace(req.PickupHours) == "" {
			writeError(w, http.StatusBadRequest, errors.New("pickup_address and pickup_hours are required for active pickup"))
			return
		}
	}

	settings := &domain.ShippingSettings{
		VendorID:                   vendorID,
		Active:                     req.Active,
		MaxRadiusKm:                req.MaxRadiusKm,
		PricePerKmCents:            req.PricePerKmCents,
		MinFeeCents:                req.MinFeeCents,
		FreeShippingThresholdCents: req.FreeShippingThresholdCents,
		PickupActive:               req.PickupActive,
		PickupAddress:              req.PickupAddress,
		PickupHours:                req.PickupHours,
		Latitude:                   req.Latitude,
		Longitude:                  req.Longitude,
	}

	if err := h.svc.UpsertShippingSettings(r.Context(), settings); err != nil {
		writeError(w, http.StatusInternalServerError, err)
		return
	}
	writeJSON(w, http.StatusOK, settings)
}

// shippingCalculateV2Item is a single cart item in the new frontend request format.
type shippingCalculateV2Item struct {
	SellerID  uuid.UUID `json:"seller_id"`
	ProductID uuid.UUID `json:"product_id"`
	Quantity  int64     `json:"quantity"`
	PriceCents int64   `json:"price_cents"`
}

// shippingCalculateV2Request is the frontend-native calculate request.
type shippingCalculateV2Request struct {
	BuyerID        uuid.UUID                 `json:"buyer_id"`
	OrderTotalCents int64                    `json:"order_total_cents"`
	Items          []shippingCalculateV2Item `json:"items"`
}

// shippingOptionV2Response is the frontend-native shipping option per seller.
type shippingOptionV2Response struct {
	SellerID        string  `json:"seller_id"`
	DeliveryFeeCents int64  `json:"delivery_fee_cents"`
	DistanceKm      float64 `json:"distance_km"`
	EstimatedDays   int     `json:"estimated_days"`
	PickupAvailable bool    `json:"pickup_available"`
	PickupAddress   string  `json:"pickup_address,omitempty"`
	PickupHours     string  `json:"pickup_hours,omitempty"`
}

// CalculateShipping godoc
// @Summary Calculate shipping options
// @Description Accepts two formats:
// (1) Legacy: { vendor_id, cart_total_cents, buyer_latitude, buyer_longitude }
// (2) Frontend: { buyer_id, order_total_cents, items: [{seller_id, ...}] }
// Returns options per seller in format { options: [...] }.
// @Tags Shipping
// @Accept json
// @Produce json
// @Success 200 {object} map[string]interface{}
// @Failure 400 {object} map[string]string
// @Failure 500 {object} map[string]string
// @Router /api/v1/shipping/calculate [post]
func (h *Handler) CalculateShipping(w http.ResponseWriter, r *http.Request) {
	// Decode into a generic map to detect request format
	var raw map[string]interface{}
	if err := decodeJSON(r.Context(), r, &raw); err != nil {
		writeError(w, http.StatusBadRequest, err)
		return
	}

	// Detect frontend format: has "items" array with seller_id
	_, hasItems := raw["items"]
	_, hasBuyerID := raw["buyer_id"]

	if hasItems && hasBuyerID {
		// New frontend format
		h.calculateShippingV2(w, r, raw)
		return
	}

	// Legacy format: vendor_id + buyer coordinates
	h.calculateShippingLegacy(w, r, raw)
}

// calculateShippingV2 handles the frontend's multi-seller shipping calculation.
func (h *Handler) calculateShippingV2(w http.ResponseWriter, r *http.Request, raw map[string]interface{}) {
	// Re-parse items from raw map
	buyerIDStr, _ := raw["buyer_id"].(string)
	buyerID, err := uuid.FromString(buyerIDStr)
	if err != nil {
		writeError(w, http.StatusBadRequest, errors.New("invalid buyer_id"))
		return
	}

	orderTotalCents := int64(0)
	if v, ok := raw["order_total_cents"].(float64); ok {
		orderTotalCents = int64(v)
	}

	itemsRaw, _ := raw["items"].([]interface{})

	// Group items by seller_id to get totals per seller
	sellerTotals := map[uuid.UUID]int64{}
	for _, itemRaw := range itemsRaw {
		item, ok := itemRaw.(map[string]interface{})
		if !ok {
			continue
		}
		sellerIDStr, _ := item["seller_id"].(string)
		sellerID, err := uuid.FromString(sellerIDStr)
		if err != nil {
			continue
		}
		qty := int64(1)
		if q, ok := item["quantity"].(float64); ok {
			qty = int64(q)
		}
		price := int64(0)
		if p, ok := item["price_cents"].(float64); ok {
			price = int64(p)
		}
		sellerTotals[sellerID] += qty * price
	}

	if len(sellerTotals) == 0 {
		writeError(w, http.StatusBadRequest, errors.New("no valid items provided"))
		return
	}

	// Try to get buyer's primary address for coordinates
	buyerAddr := &domain.Address{}
	addresses, _ := h.svc.ListAddresses(r.Context(), buyerID)
	if len(addresses) > 0 {
		buyerAddr = &addresses[0]
	}

	// Calculate options per seller
	results := make([]shippingOptionV2Response, 0, len(sellerTotals))
	for sellerID, sellerTotal := range sellerTotals {
		opt := shippingOptionV2Response{
			SellerID: sellerID.String(),
		}

		// Use per-seller total if available, otherwise distribute order total equally
		cartTotal := sellerTotal
		if cartTotal == 0 && orderTotalCents > 0 {
			cartTotal = orderTotalCents / int64(len(sellerTotals))
		}

		fee, distKm, calcErr := h.svc.CalculateShipping(r.Context(), buyerAddr, sellerID, cartTotal)
		if calcErr == nil {
			opt.DeliveryFeeCents = fee
			opt.DistanceKm = distKm
			opt.EstimatedDays = 1
			if distKm > 50 {
				opt.EstimatedDays = 3
			} else if distKm > 20 {
				opt.EstimatedDays = 2
			}
		}

		// Check pickup availability
		settings, _ := h.svc.GetShippingSettings(r.Context(), sellerID)
		if settings != nil && settings.PickupActive {
			opt.PickupAvailable = true
			opt.PickupAddress = settings.PickupAddress
			opt.PickupHours = settings.PickupHours
		}

		results = append(results, opt)
	}

	writeJSON(w, http.StatusOK, map[string]any{"options": results})
}

// calculateShippingLegacy handles the original vendor_id + coordinates format.
func (h *Handler) calculateShippingLegacy(w http.ResponseWriter, r *http.Request, raw map[string]interface{}) {
	vendorIDStr, _ := raw["vendor_id"].(string)
	vendorID, err := uuid.FromString(vendorIDStr)
	if err != nil || vendorID == uuid.Nil {
		writeError(w, http.StatusBadRequest, errors.New("vendor_id is required"))
		return
	}

	cartTotal := int64(0)
	if v, ok := raw["cart_total_cents"].(float64); ok {
		cartTotal = int64(v)
	}

	buyerLat, hasLat := raw["buyer_latitude"].(float64)
	buyerLng, hasLng := raw["buyer_longitude"].(float64)
	if !hasLat || !hasLng {
		writeError(w, http.StatusBadRequest, errors.New("buyer_latitude and buyer_longitude are required"))
		return
	}

	buyerAddr := &domain.Address{Latitude: buyerLat, Longitude: buyerLng}
	options := make([]domain.ShippingOption, 0)

	fee, distKm, calcErr := h.svc.CalculateShipping(r.Context(), buyerAddr, vendorID, cartTotal)
	if calcErr == nil {
		desc := "Entrega padrão"
		if fee == 0 {
			desc = "Frete Grátis"
		}
		options = append(options, domain.ShippingOption{
			Type:             "delivery",
			Description:      desc,
			ValueCents:       fee,
			DistanceKm:       distKm,
			EstimatedMinutes: 120,
		})
	}

	settings, _ := h.svc.GetShippingSettings(r.Context(), vendorID)
	if settings != nil && settings.PickupActive {
		options = append(options, domain.ShippingOption{
			Type:             "pickup",
			Description:      settings.PickupAddress,
			ValueCents:       0,
			EstimatedMinutes: 0,
		})
	}

	writeJSON(w, http.StatusOK, options)
}

// ListShipments godoc
// @Summary List shipments
// @Description Returns shipments. Admins see all, Tenants see only their own.
// @Tags Shipments
// @Security BearerAuth
// @Produce json
// @Param page query int false "Página"
// @Param page_size query int false "Tamanho da página"
// @Success 200 {object} domain.ShipmentPage
// @Failure 401 {object} map[string]string
// @Failure 500 {object} map[string]string
// @Router /api/v1/shipments [get]
func (h *Handler) ListShipments(w http.ResponseWriter, r *http.Request) {
	page, pageSize := parsePagination(r)

	requester, err := getRequester(r)
	if err != nil {
		writeError(w, http.StatusUnauthorized, err)
		return
	}

	filter := domain.ShipmentFilter{}
	if !strings.EqualFold(requester.Role, "Admin") {
		if requester.CompanyID == nil {
			writeError(w, http.StatusForbidden, errors.New("user has no company associated"))
			return
		}
		// Shipments logic:
		// Shipments are linked to orders, and orders belong to sellers.
		filter.SellerID = requester.CompanyID
	}

	result, err := h.svc.ListShipments(r.Context(), filter, page, pageSize)
	if err != nil {
		writeError(w, http.StatusInternalServerError, err)
		return
	}

	writeJSON(w, http.StatusOK, result)
}
