package partners

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

	"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"
	"github.com/go-chi/chi/v5"
)

// Handler binds the partners 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) ListSuppliers(w http.ResponseWriter, r *http.Request) {
	q := r.URL.Query()
	page, per := queryInt(q.Get("page")), queryInt(q.Get("per_page"))
	// Alias: ?q for ?search
	search := q.Get("search")
	if search == "" {
		search = q.Get("q")
	}
	list, total, err := h.svc.ListSuppliers(r.Context(), ListFilter{
		Page: page, PerPage: per, Search: search, Status: q.Get("status"),
		SortBy: q.Get("sort_by"), SortDir: q.Get("sort_dir"),
	})
	if err != nil {
		respondErr(w, r, err)
		return
	}
	httpresponse.OKPaginated(w, http.StatusOK, middleware.RequestIDFrom(r.Context()), list, pageOrDefault(page), perPageOrDefault(per), total)
}

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

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

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

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

func (h *Handler) ListCustomers(w http.ResponseWriter, r *http.Request) {
	q := r.URL.Query()
	page, per := queryInt(q.Get("page")), queryInt(q.Get("per_page"))
	search := q.Get("search")
	if search == "" {
		search = q.Get("q")
	}
	customerType := q.Get("customer_type")
	if customerType == "" {
		customerType = q.Get("type")
	}
	list, total, err := h.svc.ListCustomers(r.Context(), ListFilter{
		Page: page, PerPage: per, Search: search, Status: q.Get("status"),
		CustomerType: customerType,
		SortBy: q.Get("sort_by"), SortDir: q.Get("sort_dir"),
	})
	if err != nil {
		respondErr(w, r, err)
		return
	}
	httpresponse.OKPaginated(w, http.StatusOK, middleware.RequestIDFrom(r.Context()), list, pageOrDefault(page), perPageOrDefault(per), total)
}

// SearchCustomers is the lightweight picker contract. Route wiring is kept in
// cmd/server/main.go and can be added when that file is in scope.
func (h *Handler) SearchCustomers(w http.ResponseWriter, r *http.Request) {
	q := r.URL.Query()
	limit := queryInt(q.Get("limit"))
	if limit < 1 || limit > 100 {
		limit = 20
	}
	search := q.Get("q")
	if search == "" {
		search = q.Get("search")
	}
	customerType := q.Get("customer_type")
	if customerType == "" {
		customerType = q.Get("type")
	}
	list, _, err := h.svc.ListCustomers(r.Context(), ListFilter{Page: 1, PerPage: limit, Search: search, Status: "active", CustomerType: customerType})
	if err != nil {
		respondErr(w, r, err)
		return
	}
	httpresponse.OK(w, http.StatusOK, middleware.RequestIDFrom(r.Context()), list)
}

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

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

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

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

// ResetCustomerPassword handles POST /customers/{id}/reset-password — the ERP
// operator's recovery path for a locked-out B2B buyer (000151). Body
// {password}. The response is content-free: the outcome reads in the status.
func (h *Handler) ResetCustomerPassword(w http.ResponseWriter, r *http.Request) {
	var in struct {
		Password string `json:"password"`
	}
	if !decode(w, r, &in) {
		return
	}
	if err := h.svc.ResetCustomerPassword(r.Context(), pathID(r), in.Password); err != nil {
		respondErr(w, r, err)
		return
	}
	httpresponse.OK(w, http.StatusOK, middleware.RequestIDFrom(r.Context()), map[string]any{
		"reset": true,
	})
}

// actorID is the authenticated user behind the request. Used by the /customers/me
// routes, where the target is derived from the token rather than the path.
func actorID(r *http.Request) int64 {
	if u := auth.UserFrom(r.Context()); u != nil {
		return u.ID
	}
	return 0
}

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, ErrDuplicateCode):
		httpresponse.Error(w, http.StatusConflict, rid, httpresponse.CodeConflict, "Already exists", nil)
	case errors.Is(err, ErrValidation):
		httpresponse.SafeError(w, rid, logger.FromContext(r.Context()), http.StatusBadRequest, httpresponse.CodeValidation, err)
	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 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
}

// MyProfile serves GET /customers/me — the signed-in buyer's own company
// record for the storefront account page.
func (h *Handler) MyProfile(w http.ResponseWriter, r *http.Request) {
	v, err := h.svc.MyProfile(r.Context(), actorID(r))
	if err != nil {
		respondErr(w, r, err)
		return
	}
	httpresponse.OK(w, http.StatusOK, middleware.RequestIDFrom(r.Context()), v)
}

// UpdateMyProfile serves PUT /customers/me. The target comes from the token,
// never from the path, so a buyer cannot write to another company record.
func (h *Handler) UpdateMyProfile(w http.ResponseWriter, r *http.Request) {
	var in ProfileInput
	if !decode(w, r, &in) {
		return
	}
	v, err := h.svc.UpdateMyProfile(r.Context(), actorID(r), in)
	if err != nil {
		respondErr(w, r, err)
		return
	}
	httpresponse.OK(w, http.StatusOK, middleware.RequestIDFrom(r.Context()), v)
}
