package cashier

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

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

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

// Current returns the authenticated cashier's open shift.
// No open shift is not an error for POS — return 200 with null so the
// frontend doesn't log a 404 and getCurrent() can resolve to null.
func (h *Handler) Current(w http.ResponseWriter, r *http.Request) {
	u := auth.UserFrom(r.Context())
	if u == nil {
		httpresponse.Error(w, http.StatusUnauthorized, middleware.RequestIDFrom(r.Context()),
			httpresponse.CodeAuthentication, "Unauthenticated", nil)
		return
	}
	sh, err := h.svc.GetCurrentByCashier(r.Context(), u.ID)
	if err != nil {
		if errors.Is(err, ErrNotFound) {
			httpresponse.OK(w, http.StatusOK, middleware.RequestIDFrom(r.Context()), nil)
			return
		}
		respondErr(w, r, err)
		return
	}
	httpresponse.OK(w, http.StatusOK, middleware.RequestIDFrom(r.Context()), sh)
}

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")),
		CashierID: parseInt64(q.Get("cashier_id")),
		Status:    q.Get("status"),
	}
	list, total, err := h.svc.List(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, pageOrDefault(f.Page), perPageOrDefault(f.PerPage), total)
}

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

func (h *Handler) Summary(w http.ResponseWriter, r *http.Request) {
	sum, err := h.svc.Summary(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) Open(w http.ResponseWriter, r *http.Request) {
	var in OpenInput
	if !decode(w, r, &in) {
		return
	}
	// POS may omit cashier_id — fall back to authenticated user.
	if in.CashierID == 0 {
		if u := auth.UserFrom(r.Context()); u != nil {
			in.CashierID = u.ID
		}
	}
	sh, err := h.svc.Open(r.Context(), in)
	if err != nil {
		respondErr(w, r, err)
		return
	}
	httpresponse.OK(w, http.StatusCreated, middleware.RequestIDFrom(r.Context()), sh)
}

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

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

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

// --- helpers -----------------------------------------------------------------

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

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, ErrShiftOpen), errors.Is(err, ErrShiftNotOpen), errors.Is(err, ErrCashMismatch):
		httpresponse.Error(w, http.StatusConflict, rid, httpresponse.CodeBusinessViolation, err.Error(), nil)
	case errors.Is(err, ErrForbidden):
		httpresponse.Error(w, http.StatusForbidden, rid, httpresponse.CodeForbidden, 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 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
}
