package warehousedeposit

import (
	"encoding/json"
	"errors"
	"net/http"
	"strconv"

	"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"
)

type Handler struct {
	svc *Service
}

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

func (h *Handler) Create(w http.ResponseWriter, r *http.Request) {
	var in CreateInput
	if err := json.NewDecoder(r.Body).Decode(&in); err != nil {
		httpresponse.Error(w, http.StatusBadRequest, middleware.RequestIDFrom(r.Context()),
			httpresponse.CodeValidation, "Invalid request payload", nil)
		return
	}
	var userID *int64
	if u := auth.UserFrom(r.Context()); u != nil {
		userID = &u.ID
	}
	d, err := h.svc.Create(r.Context(), in, userID)
	if err != nil {
		respondErr(w, r, err)
		return
	}
	httpresponse.OK(w, http.StatusCreated, middleware.RequestIDFrom(r.Context()), d)
}

// UndepositedPayments answers GET /warehouse-deposits/undeposited-payments —
// the "Catat Setoran" checklist source.
func (h *Handler) UndepositedPayments(w http.ResponseWriter, r *http.Request) {
	q := r.URL.Query()
	f := UndepositedFilter{
		Date:   q.Get("date"),
		Search: q.Get("search"),
	}
	if v := q.Get("driver_id"); v != "" {
		if id, err := strconv.ParseInt(v, 10, 64); err == nil && id > 0 {
			f.DriverID = &id
		}
	}
	items, err := h.svc.UndepositedPayments(r.Context(), f)
	if err != nil {
		respondErr(w, r, err)
		return
	}
	httpresponse.OK(w, http.StatusOK, middleware.RequestIDFrom(r.Context()), items)
}

func (h *Handler) List(w http.ResponseWriter, r *http.Request) {
	q := r.URL.Query()
	f := Filter{
		Page:        queryInt(q.Get("page")),
		PerPage:     queryInt(q.Get("per_page")),
		WarehouseID: parseInt64(q.Get("warehouse_id")),
		Status:      q.Get("status"),
		DateFrom:    q.Get("date_from"),
		DateTo:      q.Get("date_to"),
		Search:      q.Get("search"),
	}
	items, total, err := h.svc.List(r.Context(), f)
	if err != nil {
		respondErr(w, r, err)
		return
	}
	page := f.Page
	if page < 1 {
		page = 1
	}
	perPage := f.PerPage
	if perPage < 1 || perPage > 100 {
		perPage = 20
	}
	httpresponse.OKPaginated(w, http.StatusOK, middleware.RequestIDFrom(r.Context()), items, page, perPage, total)
}

func (h *Handler) Get(w http.ResponseWriter, r *http.Request) {
	id, err := strconv.ParseInt(chi.URLParam(r, "id"), 10, 64)
	if err != nil {
		httpresponse.Error(w, http.StatusBadRequest, middleware.RequestIDFrom(r.Context()),
			httpresponse.CodeValidation, "Invalid deposit ID", nil)
		return
	}
	d, err := h.svc.Get(r.Context(), id)
	if err != nil {
		respondErr(w, r, err)
		return
	}
	httpresponse.OK(w, http.StatusOK, middleware.RequestIDFrom(r.Context()), d)
}

func (h *Handler) Receive(w http.ResponseWriter, r *http.Request) {
	id, err := strconv.ParseInt(chi.URLParam(r, "id"), 10, 64)
	if err != nil {
		httpresponse.Error(w, http.StatusBadRequest, middleware.RequestIDFrom(r.Context()),
			httpresponse.CodeValidation, "Invalid deposit ID", nil)
		return
	}
	u := auth.UserFrom(r.Context())
	if u == nil {
		httpresponse.Error(w, http.StatusUnauthorized, middleware.RequestIDFrom(r.Context()),
			httpresponse.CodeAuthentication, "Unauthenticated", nil)
		return
	}

	d, err := h.svc.Receive(r.Context(), id, u.ID)
	if err != nil {
		respondErr(w, r, err)
		return
	}
	httpresponse.OK(w, http.StatusOK, middleware.RequestIDFrom(r.Context()), d)
}

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, err.Error(), nil)
	case errors.Is(err, ErrDepositNotPending):
		httpresponse.Error(w, http.StatusConflict, rid, httpresponse.CodeBusinessViolation, err.Error(), nil)
	case errors.Is(err, ErrWarehouseRequired), errors.Is(err, ErrDriverRequired), errors.Is(err, ErrPaymentsRequired), errors.Is(err, ErrInvalidInput):
		httpresponse.Error(w, http.StatusBadRequest, rid, httpresponse.CodeValidation, err.Error(), nil)
	case errors.Is(err, ErrPaymentInvalid):
		httpresponse.Error(w, http.StatusConflict, rid, httpresponse.CodeBusinessViolation, err.Error(), nil)
	default:
		// Not a sentinel this handler knows: SafeError keeps a caller-fixable
		// rejection a 4xx with a safe message and turns a driver fault into a 500,
		// instead of answering 500 with the driver's own text.
		httpresponse.SafeError(w, rid, logger.FromContext(r.Context()), http.StatusInternalServerError, httpresponse.CodeInternal, 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
}
