package distribution

import (
	"context"
	"errors"
	"mime/multipart"
	"net/http"
	"strconv"
	"strings"

	"github.com/go-chi/chi/v5"

	"github.com/TaruLab/keluargamart-backend/internal/auth"
	"github.com/TaruLab/keluargamart-backend/pkg/httpresponse"
	"github.com/TaruLab/keluargamart-backend/pkg/logger"
	"github.com/TaruLab/keluargamart-backend/pkg/middleware"
)

// Handler binds the distribution service to HTTP. Methods are exported so the
// router can attach RequirePermission per route in main.
type Handler struct {
	svc             *Service
	resolveCustomer CustomerResolver
}

func NewHandler(svc *Service) *Handler {
	return &Handler{svc: svc}
}

// CustomerResolver maps an authenticated user to their linked customer.
// ok=false means the user has no linked customer (buyer views return empty).
type CustomerResolver func(ctx context.Context, userID int64) (customerID int64, ok bool, err error)

// WithCustomerResolver wires the user->customer seam used by the buyer-scoped
// /me views (Phase 6 item 5, B2B surface).
func (h *Handler) WithCustomerResolver(fn CustomerResolver) *Handler {
	h.resolveCustomer = fn
	return h
}

// myCustomerID resolves the caller's customer, or 0 when they have none.
func (h *Handler) myCustomerID(r *http.Request) (int64, error) {
	if h.resolveCustomer == nil {
		return 0, ErrNotFound
	}
	u := auth.UserFrom(r.Context())
	if u == nil {
		return 0, ErrNotFound
	}
	id, ok, err := h.resolveCustomer(r.Context(), u.ID)
	if err != nil {
		return 0, err
	}
	if !ok {
		return 0, nil
	}
	return id, nil
}

func (h *Handler) ListDeliveryOrders(w http.ResponseWriter, r *http.Request) {
	q := r.URL.Query()
	f := DOFilter{
		Page:         pageOrDefault(queryInt(q.Get("page"))),
		PerPage:      perPageOrDefault(queryInt(q.Get("per_page"))),
		Search:       q.Get("search"),
		Status:       q.Get("status"),
		SalesOrderID: parseInt64(q.Get("sales_order_id")),
		// ERP table sort: whitelist lives in the SQL (dir-gated CASE pairs).
		SortBy:  q.Get("sort_by"),
		SortDir: q.Get("sort_dir"),
	}
	list, total, err := h.svc.ListDeliveryOrders(r.Context(), f)
	if err != nil {
		httpresponse.Internal(w, middleware.RequestIDFrom(r.Context()), logger.FromContext(r.Context()), err)
		return
	}
	httpresponse.OKPaginated(w, http.StatusOK, middleware.RequestIDFrom(r.Context()), list, f.Page, f.PerPage, total)
}

// MyDeliveryOrders lists the authenticated buyer's own delivery orders
// (Phase 6 item 5, B2B surface).
func (h *Handler) MyDeliveryOrders(w http.ResponseWriter, r *http.Request) {
	customerID, err := h.myCustomerID(r)
	if err != nil {
		httpresponse.Internal(w, middleware.RequestIDFrom(r.Context()), logger.FromContext(r.Context()), err)
		return
	}
	q := r.URL.Query()
	f := DOFilter{
		Page:       pageOrDefault(queryInt(q.Get("page"))),
		PerPage:    perPageOrDefault(queryInt(q.Get("per_page"))),
		Status:     q.Get("status"),
		CustomerID: customerID,
	}
	// customerID 0 means no linked customer; the list SQL treats <= 0 as
	// "no filter" and would leak every delivery order in the company.
	if customerID <= 0 {
		httpresponse.OKPaginated(w, http.StatusOK, middleware.RequestIDFrom(r.Context()), []DeliveryOrder{},
			f.Page, f.PerPage, 0)
		return
	}
	list, total, err := h.svc.ListDeliveryOrders(r.Context(), f)
	if err != nil {
		httpresponse.Internal(w, middleware.RequestIDFrom(r.Context()), logger.FromContext(r.Context()), err)
		return
	}
	httpresponse.OKPaginated(w, http.StatusOK, middleware.RequestIDFrom(r.Context()), list, f.Page, f.PerPage, total)
}

func (h *Handler) GetDeliveryOrder(w http.ResponseWriter, r *http.Request) {
	o, err := h.svc.GetDeliveryOrder(r.Context(), pathID(r))
	if err != nil {
		respondErr(w, r, err)
		return
	}
	httpresponse.OK(w, http.StatusOK, middleware.RequestIDFrom(r.Context()), o)
}

func (h *Handler) CreateDeliveryOrder(w http.ResponseWriter, r *http.Request) {
	var in DOInput
	if !decode(w, r, &in) {
		return
	}
	o, err := h.svc.CreateDeliveryOrder(r.Context(), in, userID(r))
	if err != nil {
		respondErr(w, r, err)
		return
	}
	httpresponse.OK(w, http.StatusCreated, middleware.RequestIDFrom(r.Context()), o)
}

func (h *Handler) CreateShipment(w http.ResponseWriter, r *http.Request) {
	var in ShipmentInput
	if !decode(w, r, &in) {
		return
	}
	sh, err := h.svc.CreateShipment(r.Context(), pathID(r), in, userID(r))
	if err != nil {
		respondErr(w, r, err)
		return
	}
	httpresponse.OK(w, http.StatusCreated, middleware.RequestIDFrom(r.Context()), sh)
}

func (h *Handler) CreateDispatch(w http.ResponseWriter, r *http.Request) {
	var in DispatchInput
	if !decode(w, r, &in) {
		return
	}
	d, err := h.svc.CreateDispatch(r.Context(), pathID(r), in, userID(r))
	if err != nil {
		respondErr(w, r, err)
		return
	}
	httpresponse.OK(w, http.StatusCreated, middleware.RequestIDFrom(r.Context()), d)
}

func (h *Handler) DeliverShipment(w http.ResponseWriter, r *http.Request) {
	sh, err := h.svc.DeliverShipment(r.Context(), pathID(r), userID(r))
	if err != nil {
		respondErr(w, r, err)
		return
	}
	httpresponse.OK(w, http.StatusOK, middleware.RequestIDFrom(r.Context()), sh)
}

func (h *Handler) DeliverDeliveryOrder(w http.ResponseWriter, r *http.Request) {
	// The POD photo + recipient ride the completion request: the operator
	// takes the photo at the handover, so a separate upload step would never
	// happen in the yard. Body may be empty (no evidence) or multipart with
	// `file` + `recipient_name`. The header is passed through whole — size
	// and type limits live in the shared uploads pipeline the saver wraps.
	var podPhoto *multipart.FileHeader
	var podRecipient string
	ct := r.Header.Get("Content-Type")
	if strings.HasPrefix(ct, "multipart/form-data") {
		if err := r.ParseMultipartForm(10 << 20); err != nil && !errors.Is(err, http.ErrMissingBoundary) {
			httpresponse.Error(w, http.StatusBadRequest, middleware.RequestIDFrom(r.Context()),
				httpresponse.CodeValidation, "invalid multipart form", nil)
			return
		}
		if fhs := r.MultipartForm.File["file"]; len(fhs) > 0 {
			podPhoto = fhs[0]
		}
		podRecipient = strings.TrimSpace(r.FormValue("recipient_name"))
	}
	do, err := h.svc.DeliverDeliveryOrder(r.Context(), pathID(r), userID(r), podPhoto, podRecipient)
	if err != nil {
		respondErr(w, r, err)
		return
	}
	httpresponse.OK(w, http.StatusOK, middleware.RequestIDFrom(r.Context()), do)
}

func (h *Handler) GetShipmentTracking(w http.ResponseWriter, r *http.Request) {
	sum, err := h.svc.GetShipmentTracking(r.Context(), pathID(r))
	if err != nil {
		respondErr(w, r, err)
		return
	}
	httpresponse.OK(w, http.StatusOK, middleware.RequestIDFrom(r.Context()), sum)
}

func (h *Handler) RecordTrackingEvent(w http.ResponseWriter, r *http.Request) {
	var in TrackingEventInput
	if !decode(w, r, &in) {
		return
	}
	ev, err := h.svc.RecordTrackingEvent(r.Context(), pathID(r), in, userID(r))
	if err != nil {
		respondErr(w, r, err)
		return
	}
	httpresponse.OK(w, http.StatusCreated, middleware.RequestIDFrom(r.Context()), ev)
}

func (h *Handler) LookupShipment(w http.ResponseWriter, r *http.Request) {
	res, err := h.svc.LookupShipment(r.Context(), r.URL.Query().Get("q"))
	if err != nil {
		respondErr(w, r, err)
		return
	}
	httpresponse.OK(w, http.StatusOK, middleware.RequestIDFrom(r.Context()), res)
}

// ListDrivers powers the dispatch modal's driver picker.
func (h *Handler) ListDrivers(w http.ResponseWriter, r *http.Request) {
	list, err := h.svc.ListDrivers(r.Context())
	if err != nil {
		httpresponse.Internal(w, middleware.RequestIDFrom(r.Context()), logger.FromContext(r.Context()), err)
		return
	}
	httpresponse.OK(w, http.StatusOK, middleware.RequestIDFrom(r.Context()), list)
}

// CreateDriver adds a driver candidate (name only — see Service.CreateDriver).
func (h *Handler) CreateDriver(w http.ResponseWriter, r *http.Request) {
	var in struct {
		Name string `json:"name"`
	}
	if !decode(w, r, &in) {
		return
	}
	d, err := h.svc.CreateDriver(r.Context(), in.Name)
	if err != nil {
		respondErr(w, r, err)
		return
	}
	httpresponse.OK(w, http.StatusCreated, middleware.RequestIDFrom(r.Context()), d)
}

func (h *Handler) ListFleet(w http.ResponseWriter, r *http.Request) {
	q := r.URL.Query()
	f := FleetFilter{
		Page:     pageOrDefault(queryInt(q.Get("page"))),
		PerPage:  perPageOrDefault(queryInt(q.Get("per_page"))),
		IsActive: queryBoolPtr(q.Get("active")),
	}
	list, total, err := h.svc.ListFleet(r.Context(), f)
	if err != nil {
		httpresponse.Internal(w, middleware.RequestIDFrom(r.Context()), logger.FromContext(r.Context()), err)
		return
	}
	httpresponse.OKPaginated(w, http.StatusOK, middleware.RequestIDFrom(r.Context()), list, f.Page, f.PerPage, total)
}

func (h *Handler) GetVehicle(w http.ResponseWriter, r *http.Request) {
	v, err := h.svc.GetVehicle(r.Context(), pathID(r))
	if err != nil {
		respondErr(w, r, err)
		return
	}
	httpresponse.OK(w, http.StatusOK, middleware.RequestIDFrom(r.Context()), v)
}

func (h *Handler) CreateVehicle(w http.ResponseWriter, r *http.Request) {
	var in VehicleInput
	if !decode(w, r, &in) {
		return
	}
	v, err := h.svc.CreateVehicle(r.Context(), in)
	if err != nil {
		respondErr(w, r, err)
		return
	}
	httpresponse.OK(w, http.StatusCreated, middleware.RequestIDFrom(r.Context()), v)
}

// --- helpers (mirror sales handler) -----------------------------------------

func pathID(r *http.Request) int64 {
	id, _ := strconv.ParseInt(chi.URLParam(r, "id"), 10, 64)
	return id
}

func userID(r *http.Request) int64 {
	if u := auth.UserFrom(r.Context()); u != nil {
		return u.ID
	}
	return 0
}

func decode(w http.ResponseWriter, r *http.Request, dst any) bool {
	return httpresponse.DecodeJSON(w, r, middleware.RequestIDFrom(r.Context()), dst)
}

func respondErr(w http.ResponseWriter, r *http.Request, err error) {
	rid := middleware.RequestIDFrom(r.Context())
	switch {
	case errors.Is(err, ErrNotFound):
		httpresponse.Error(w, http.StatusNotFound, rid, httpresponse.CodeNotFound, "Not found", nil)
	case errors.Is(err, ErrOverShipment) || errors.Is(err, ErrInsufficientStock) ||
		errors.Is(err, ErrCannotShipment) || errors.Is(err, ErrShipmentClosed) ||
		errors.Is(err, ErrCannotDispatch) ||
		errors.Is(err, ErrSOInvalidStatus) || errors.Is(err, ErrOpenDOExists) ||
		errors.Is(err, ErrNothingShipped):
		httpresponse.Error(w, http.StatusConflict, rid, httpresponse.CodeBusinessViolation, err.Error(), nil)
	case errors.Is(err, ErrDOAlreadyBatched):
		// 422, not 409: nothing collided, a business rule said no. The DO is
		// mid-collection on someone else's sheet and has to be split out first.
		httpresponse.Error(w, http.StatusUnprocessableEntity, rid, httpresponse.CodeBusinessViolation, err.Error(), nil)
	case errors.Is(err, ErrSORequired) || errors.Is(err, ErrItemRequired) ||
		errors.Is(err, ErrQuantityRequired) || errors.Is(err, ErrItemNotOnSO) ||
		errors.Is(err, ErrItemNotOnDelivery) || errors.Is(err, ErrDispatchFleetRequired) ||
		errors.Is(err, ErrShipmentOnDORequired) || errors.Is(err, ErrPriorityInvalid) ||
		errors.Is(err, ErrWarehouseRequired) || errors.Is(err, ErrPlateRequired) ||
		errors.Is(err, ErrVehicleNameRequired) || errors.Is(err, ErrTrackingStatusInvalid) ||
		errors.Is(err, ErrTrackingSourceInvalid) || errors.Is(err, ErrTrackingCoordsInvalid) ||
		errors.Is(err, ErrTrackingAccuracy) || errors.Is(err, ErrTrackingEventTime) ||
		errors.Is(err, ErrIdentifierRequired):
		httpresponse.Error(w, http.StatusBadRequest, rid, httpresponse.CodeValidation, err.Error(), nil)
	case err != nil:
		httpresponse.SafeError(w, rid, logger.FromContext(r.Context()), http.StatusBadRequest, httpresponse.CodeValidation, err)
	}
}

func queryInt(s string) int {
	n, _ := strconv.Atoi(s)
	return n
}

func parseInt64(s string) int64 {
	n, _ := strconv.ParseInt(s, 10, 64)
	return n
}

func queryBoolPtr(s string) *bool {
	if s == "" {
		return nil
	}
	b, err := strconv.ParseBool(s)
	if err != nil {
		return nil
	}
	return &b
}

func pageOrDefault(n int) int {
	if n < 1 {
		return 1
	}
	return n
}

func perPageOrDefault(n int) int {
	if n < 1 || n > 100 {
		return 20
	}
	return n
}

// LatestShipmentLocation serves GET /shipments/{id}/tracking/latest.
func (h *Handler) LatestShipmentLocation(w http.ResponseWriter, r *http.Request) {
	ev, err := h.svc.LatestShipmentLocation(r.Context(), pathID(r))
	if err != nil {
		respondErr(w, r, err)
		return
	}
	httpresponse.OK(w, http.StatusOK, middleware.RequestIDFrom(r.Context()), ev)
}

// MapFeed serves GET /distribution/map — facilities + active shipment fixes.
func (h *Handler) MapFeed(w http.ResponseWriter, r *http.Request) {
	feed, err := h.svc.MapFeed(r.Context())
	if err != nil {
		httpresponse.Internal(w, middleware.RequestIDFrom(r.Context()), logger.FromContext(r.Context()), err)
		return
	}
	httpresponse.OK(w, http.StatusOK, middleware.RequestIDFrom(r.Context()), feed)
}
