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 {array} domain.ShippingMethod
// @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
	}

	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 view shipping settings"))
			return
		}
	}

	methods, err := h.svc.GetShippingMethods(r.Context(), vendorID)
	if err != nil {
		writeError(w, http.StatusInternalServerError, err)
		return
	}
	writeJSON(w, http.StatusOK, methods)
}

// 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 {array} domain.ShippingMethod
// @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 len(req.Methods) == 0 {
		writeError(w, http.StatusBadRequest, errors.New("methods are required"))
		return
	}

	methods := make([]domain.ShippingMethod, 0, len(req.Methods))
	for _, method := range req.Methods {
		methodType, err := parseShippingMethodType(method.Type)
		if err != nil {
			writeError(w, http.StatusBadRequest, err)
			return
		}
		if method.PreparationMinutes < 0 {
			writeError(w, http.StatusBadRequest, errors.New("preparation_minutes must be >= 0"))
			return
		}
		if methodType != domain.ShippingMethodPickup {
			if method.MaxRadiusKm <= 0 {
				writeError(w, http.StatusBadRequest, errors.New("max_radius_km must be > 0 for delivery methods"))
				return
			}
			if method.MinFeeCents < 0 || method.PricePerKmCents < 0 {
				writeError(w, http.StatusBadRequest, errors.New("delivery pricing must be >= 0"))
				return
			}
			if method.FreeShippingThresholdCents != nil && *method.FreeShippingThresholdCents <= 0 {
				writeError(w, http.StatusBadRequest, errors.New("free_shipping_threshold_cents must be > 0"))
				return
			}
		}
		if methodType == domain.ShippingMethodPickup && method.Active {
			if strings.TrimSpace(method.PickupAddress) == "" || strings.TrimSpace(method.PickupHours) == "" {
				writeError(w, http.StatusBadRequest, errors.New("pickup_address and pickup_hours are required for active pickup"))
				return
			}
		}
		methods = append(methods, domain.ShippingMethod{
			Type:                       methodType,
			Active:                     method.Active,
			PreparationMinutes:         method.PreparationMinutes,
			MaxRadiusKm:                method.MaxRadiusKm,
			MinFeeCents:                method.MinFeeCents,
			PricePerKmCents:            method.PricePerKmCents,
			FreeShippingThresholdCents: method.FreeShippingThresholdCents,
			PickupAddress:              strings.TrimSpace(method.PickupAddress),
			PickupHours:                strings.TrimSpace(method.PickupHours),
		})
	}

	updated, err := h.svc.UpsertShippingMethods(r.Context(), vendorID, methods)
	if err != nil {
		writeError(w, http.StatusInternalServerError, err)
		return
	}
	writeJSON(w, http.StatusOK, updated)
}

// CalculateShipping godoc
// @Summary Calculate shipping options
// @Description Calculates shipping or pickup options based on vendor config and buyer location.
// @Tags Shipping
// @Accept json
// @Produce json
// @Param payload body shippingCalculateRequest true "Calculation inputs"
// @Success 200 {array} domain.ShippingOption
// @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) {
	var req shippingCalculateRequest
	if err := decodeJSON(r.Context(), r, &req); err != nil {
		writeError(w, http.StatusBadRequest, err)
		return
	}

	if req.VendorID == uuid.Nil {
		writeError(w, http.StatusBadRequest, errors.New("vendor_id is required"))
		return
	}
	if req.BuyerLatitude == nil || req.BuyerLongitude == nil {
		if req.AddressID != nil || req.PostalCode != "" {
			writeError(w, http.StatusBadRequest, errors.New("address_id or postal_code geocoding is not supported; provide buyer_latitude and buyer_longitude"))
			return
		}
		writeError(w, http.StatusBadRequest, errors.New("buyer_latitude and buyer_longitude are required"))
		return
	}

	options, err := h.svc.CalculateShippingOptions(r.Context(), req.VendorID, *req.BuyerLatitude, *req.BuyerLongitude, req.CartTotalCents)
	if err != nil {
		writeError(w, http.StatusInternalServerError, err)
		return
	}
	writeJSON(w, http.StatusOK, options)
}

func parseShippingMethodType(value string) (domain.ShippingMethodType, error) {
	switch strings.ToLower(strings.TrimSpace(value)) {
	case string(domain.ShippingMethodPickup):
		return domain.ShippingMethodPickup, nil
	case string(domain.ShippingMethodOwnDelivery):
		return domain.ShippingMethodOwnDelivery, nil
	case string(domain.ShippingMethodThirdParty):
		return domain.ShippingMethodThirdParty, nil
	default:
		return "", errors.New("invalid shipping method type")
	}
}
