package galon

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

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

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

// Handler binds the galon 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) Services(w http.ResponseWriter, r *http.Request) {
	services := []map[string]any{
		{"id": "isi_ulang", "name": "Isi Ulang Galon", "description": "Mengisi ulang galon yang dibawa", "usesGallon": true},
		{"id": "beli_galon", "name": "Beli Galon Baru", "description": "Pembelian galon baru beserta isinya", "usesGallon": false},
		{"id": "pinjam_galon", "name": "Pinjam / Deposit Galon", "description": "Meminjam galon dengan deposit yang dikembalikan", "usesGallon": true},
	}
	httpresponse.OK(w, http.StatusOK, middleware.RequestIDFrom(r.Context()), services)
}

// Status returns the POS stat tiles.
//
// It was a hardcoded literal — {"diToko": 45, "dipinjam": 12, "pengiriman": 3}
// — served as if it were measured state, on a screen the cashier reads as the
// store's galon position. The database held three galon transactions in total.
// diToko is null because a physical unit count cannot be derived from anything
// stored here; the POS shows "—" for it rather than a fabricated figure.
func (h *Handler) Status(w http.ResponseWriter, r *http.Request) {
	status, err := h.svc.InventoryStatus(r.Context())
	if err != nil {
		respondErr(w, r, err)
		return
	}
	httpresponse.OK(w, http.StatusOK, middleware.RequestIDFrom(r.Context()), status)
}

func (h *Handler) Units(w http.ResponseWriter, r *http.Request) {
	httpresponse.OK(w, http.StatusOK, middleware.RequestIDFrom(r.Context()), []any{})
}

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")),
		Status:     q.Get("status"),
		Type:       q.Get("type"),
		CustomerID: parseInt64(q.Get("customer_id")),
	}
	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) {
	t, 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()), t)
}

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

func (h *Handler) Complete(w http.ResponseWriter, r *http.Request) {
	var body struct {
		PaymentMethod   string `json:"payment_method"`
		ReferenceNumber string `json:"reference_number"`
	}
	_ = json.NewDecoder(r.Body).Decode(&body)
	method := strings.TrimSpace(body.PaymentMethod)
	if method == "" {
		method = "cash"
	}
	t, err := h.svc.CompleteWithPayment(r.Context(), pathID(r), method, strings.TrimSpace(body.ReferenceNumber))
	if err != nil {
		respondErr(w, r, err)
		return
	}
	httpresponse.OK(w, http.StatusOK, middleware.RequestIDFrom(r.Context()), t)
}

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

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

func (h *Handler) Cancel(w http.ResponseWriter, r *http.Request) {
	t, 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()), t)
}

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

// ByCode lists a physical gallon's recent transactions (barcode tracker).
func (h *Handler) ByCode(w http.ResponseWriter, r *http.Request) {
	code := strings.ToUpper(strings.TrimSpace(chi.URLParam(r, "code")))
	list, err := h.svc.GallonHistory(r.Context(), code)
	if err != nil {
		respondErr(w, r, err)
		return
	}
	httpresponse.OK(w, http.StatusOK, middleware.RequestIDFrom(r.Context()), list)
}

// Info returns aggregate tracking info for one physical gallon.
func (h *Handler) Info(w http.ResponseWriter, r *http.Request) {
	code := strings.ToUpper(strings.TrimSpace(chi.URLParam(r, "code")))
	info, err := h.svc.GallonInfo(r.Context(), code)
	if err != nil {
		respondErr(w, r, err)
		return
	}
	httpresponse.OK(w, http.StatusOK, middleware.RequestIDFrom(r.Context()), info)
}

// --- 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, ErrInvalidStatus):
		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
}
