package purchasing

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

	"github.com/TaruLab/keluargamart-backend/internal/auth"
	"github.com/TaruLab/keluargamart-backend/internal/inventory"
	"github.com/TaruLab/keluargamart-backend/pkg/httpresponse"
	"github.com/TaruLab/keluargamart-backend/pkg/logger"
	"github.com/TaruLab/keluargamart-backend/pkg/middleware"
	"github.com/go-chi/chi/v5"
	"github.com/shopspring/decimal"
)

// Handler binds the purchasing 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}
}

func (h *Handler) ListOrders(w http.ResponseWriter, r *http.Request) {
	q := r.URL.Query()
	f := POFilter{
		Page:       queryInt(q.Get("page")),
		PerPage:    queryInt(q.Get("per_page")),
		Search:     q.Get("search"),
		Status:     q.Get("status"),
		SupplierID: parseInt64(q.Get("supplier_id")),
		SortBy:     q.Get("sort_by"),
		SortDir:    q.Get("sort_dir"),
	}
	list, total, err := h.svc.ListOrders(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)
}

// PrintOrder serves the authoritative printable PO document payload — the
// same enriched aggregate as GET /{id}, exposed as a stable contract for the
// formal print view (client feedback 2026-08-26 section 1).
func (h *Handler) PrintOrder(w http.ResponseWriter, r *http.Request) {
	o, err := h.svc.GetOrder(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) GetOrder(w http.ResponseWriter, r *http.Request) {
	o, err := h.svc.GetOrder(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) CreateOrder(w http.ResponseWriter, r *http.Request) {
	var in POInput
	if !decode(w, r, &in) {
		return
	}
	u := auth.UserFrom(r.Context())
	if u != nil {
		in.CreatedBy = &u.ID
	}
	o, err := h.svc.Create(r.Context(), in)
	if err != nil {
		respondErr(w, r, err)
		return
	}
	httpresponse.OK(w, http.StatusCreated, middleware.RequestIDFrom(r.Context()), o)
}

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

func (h *Handler) DeleteOrder(w http.ResponseWriter, r *http.Request) {
	if err := h.svc.Delete(r.Context(), pathID(r)); err != nil {
		respondErr(w, r, err)
		return
	}
	w.WriteHeader(http.StatusNoContent)
}

func (h *Handler) ApproveOrder(w http.ResponseWriter, r *http.Request) {
	u := auth.UserFrom(r.Context())
	var approverID int64
	if u != nil {
		approverID = u.ID
	}
	o, err := h.svc.Approve(r.Context(), pathID(r), approverID)
	if err != nil {
		respondErr(w, r, err)
		return
	}
	httpresponse.OK(w, http.StatusOK, middleware.RequestIDFrom(r.Context()), o)
}

func (h *Handler) CancelOrder(w http.ResponseWriter, r *http.Request) {
	o, err := h.svc.Cancel(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) ListGRs(w http.ResponseWriter, r *http.Request) {
	q := r.URL.Query()
	f := GRFilter{Page: queryInt(q.Get("page")), PerPage: queryInt(q.Get("per_page")), Status: q.Get("status")}
	list, total, err := h.svc.ListGRs(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) CreateGR(w http.ResponseWriter, r *http.Request) {
	var in GRInput
	if !decode(w, r, &in) {
		return
	}
	g, err := h.svc.CreateGR(inventory.WithMovementRequest(r.Context(), r.Header.Get("Idempotency-Key"), "goods_receipt", in), in)
	if err != nil {
		respondErr(w, r, err)
		return
	}
	httpresponse.OK(w, http.StatusCreated, middleware.RequestIDFrom(r.Context()), g)
}

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

func (h *Handler) ListDebts(w http.ResponseWriter, r *http.Request) {
	q := r.URL.Query()
	debtStatus := q.Get("status")
	if debtStatus == "" {
		debtStatus = q.Get("debt_status")
	}
	if debtStatus == "" {
		debtStatus = "unpaid"
	}
	f := DebtFilter{
		Page:       queryInt(q.Get("page")),
		PerPage:    queryInt(q.Get("per_page")),
		SupplierID: parseInt64(q.Get("supplier_id")),
		Search:     q.Get("search"),
		DueDate:    q.Get("due_date"),
		SortBy:     q.Get("sort_by"),
		SortDir:    q.Get("sort_dir"),
		DebtStatus: debtStatus,
	}
	list, count, summary, err := h.svc.ListDebts(r.Context(), f)
	if err != nil {
		respondErr(w, r, err)
		return
	}
	httpresponse.OKPaginatedWithMeta(w, http.StatusOK, middleware.RequestIDFrom(r.Context()), list,
		pageOrDefault(f.Page), perPageOrDefault(f.PerPage), count, map[string]any{
			"total_amount":     summary.TotalAmount,
			"total_paid":       summary.TotalPaid,
			"debt_count":       summary.CountUnpaid,
			"active_suppliers": summary.ActiveSuppliers,
		})
}

func (h *Handler) PayDebt(w http.ResponseWriter, r *http.Request) {
	var in struct {
		Amount    decimal.Decimal `json:"amount"`
		VoucherID *int64          `json:"voucher_id"`
	}
	if !decode(w, r, &in) {
		return
	}
	u := auth.UserFrom(r.Context())
	var userID int64
	if u != nil {
		userID = u.ID
	}
	o, err := h.svc.PayDebt(r.Context(), pathID(r), in.Amount, in.VoucherID, &userID)
	if err != nil {
		respondErr(w, r, err)
		return
	}
	httpresponse.OK(w, http.StatusOK, middleware.RequestIDFrom(r.Context()), o)
}

func (h *Handler) ListVouchers(w http.ResponseWriter, r *http.Request) {
	q := r.URL.Query()
	f := VoucherFilter{Page: queryInt(q.Get("page")), PerPage: queryInt(q.Get("per_page")), Status: q.Get("status")}
	list, total, err := h.svc.ListVouchers(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) CreateVoucher(w http.ResponseWriter, r *http.Request) {
	var in VoucherInput
	if !decode(w, r, &in) {
		return
	}
	v, err := h.svc.CreateVoucher(r.Context(), in)
	if err != nil {
		respondErr(w, r, err)
		return
	}
	httpresponse.OK(w, http.StatusCreated, middleware.RequestIDFrom(r.Context()), v)
}

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

// PrintVoucher records printed_at/printed_by (first call) or reports the
// existing stamp (repeat calls) — informational audit trail, never a hard
// block on reprinting a lost document.
func (h *Handler) PrintVoucher(w http.ResponseWriter, r *http.Request) {
	u := auth.UserFrom(r.Context())
	var userID int64
	if u != nil {
		userID = u.ID
	}
	v, alreadyPrinted, err := h.svc.MarkVoucherPrinted(r.Context(), pathID(r), userID)
	if err != nil {
		respondErr(w, r, err)
		return
	}
	httpresponse.OK(w, http.StatusOK, middleware.RequestIDFrom(r.Context()), map[string]any{
		"printed_at":      v.PrintedAt,
		"printed_by":      v.PrintedBy,
		"already_printed": alreadyPrinted,
	})
}

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

func (h *Handler) UpdateVoucherStatus(w http.ResponseWriter, r *http.Request) {
	var in struct {
		Status string `json:"status"`
	}
	if !decode(w, r, &in) {
		return
	}
	v, err := h.svc.UpdateVoucherStatus(r.Context(), pathID(r), in.Status)
	if err != nil {
		respondErr(w, r, err)
		return
	}
	httpresponse.OK(w, http.StatusOK, middleware.RequestIDFrom(r.Context()), v)
}

// --- 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, ErrPaymentTooLarge):
		httpresponse.Error(w, http.StatusConflict, rid, httpresponse.CodeBusinessViolation, err.Error(), nil)
	case errors.Is(err, ErrCannotModify) || errors.Is(err, ErrCannotCancel) || errors.Is(err, ErrVoucherUsed) || errors.Is(err, ErrPONotApproved) || errors.Is(err, ErrPONotPayable):
		httpresponse.Error(w, http.StatusConflict, rid, httpresponse.CodeBusinessViolation, 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
}
