package salestargets

import (
	"context"
	"encoding/json"
	"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"
)

// Store is the narrow repository surface the handler consumes — an interface
// so tests can gate-check without a database.
type Store interface {
	List(ctx context.Context, period, roleLevel, scopeLevel string, mineUserID int64) ([]SalesTarget, error)
	Summary(ctx context.Context, period string) ([]LevelSummary, error)
	Create(ctx context.Context, in TargetInput, createdBy int64) (*SalesTarget, error)
	Update(ctx context.Context, id int64, in TargetInput) (*SalesTarget, error)
	Delete(ctx context.Context, id int64) error
}

type Handler struct {
	repo Store
}

func NewHandler(repo Store) *Handler {
	return &Handler{repo: repo}
}

// roleScopePairs is the tiered hierarchy contract: GM owns a whole city,
// BM a kecamatan, SPV a kelurahan, Sales Lapangan a desa.
var roleScopePairs = map[string]string{
	"gm":    "kota",
	"bm":    "kecamatan",
	"spv":   "kelurahan",
	"sales": "desa",
}

// validateScope enforces the role↔scope pairing on writes so a target can
// never be created with, say, a sales rep owning a whole kota.
func validateScope(roleLevel, scopeLevel string) bool {
	want, ok := roleScopePairs[roleLevel]
	return ok && scopeLevel == want
}

// List handles GET /sales-targets.
//
// Scope rule: every sales-hierarchy account sees targets, but only the ones
// linked to it — a Sales Lapangan (or any non-GM identity that is not target
// management) gets just the rows naming them as PIC. GM, BM, SPV, admin,
// owner and system keep the full sheet they manage.
func (h *Handler) List(w http.ResponseWriter, r *http.Request) {
	period := r.URL.Query().Get("period")
	roleLevel := r.URL.Query().Get("role_level")
	scopeLevel := r.URL.Query().Get("scope_level")

	var mineOnly int64
	u := auth.UserFrom(r.Context())
	if u != nil && !canManageTargets(u) {
		mineOnly = u.ID
	}

	list, err := h.repo.List(r.Context(), period, roleLevel, scopeLevel, mineOnly)
	if err != nil {
		httpresponse.Internal(w, middleware.RequestIDFrom(r.Context()), logger.FromContext(r.Context()), err)
		return
	}
	httpresponse.OK(w, http.StatusOK, middleware.RequestIDFrom(r.Context()), list)
}

// Summary handles GET /sales-targets/summary
func (h *Handler) Summary(w http.ResponseWriter, r *http.Request) {
	period := r.URL.Query().Get("period")
	res, err := h.repo.Summary(r.Context(), period)
	if err != nil {
		httpresponse.Internal(w, middleware.RequestIDFrom(r.Context()), logger.FromContext(r.Context()), err)
		return
	}
	httpresponse.OK(w, http.StatusOK, middleware.RequestIDFrom(r.Context()), res)
}

// canManageTargets reports whether the identity may create/edit/delete sales
// targets. Policy (2026-09): only the GM — plus platform admin/owner and the
// scoped system role — manages the target sheet. BM/SPV keep view access but
// lost write rights; sales never had them.
func canManageTargets(u *auth.User) bool {
	if u == nil {
		return false
	}
	switch u.Role {
	case auth.RoleGM, auth.RoleAdmin, auth.RoleOwner, auth.RoleSystem:
		return true
	default:
		return false
	}
}

// requireTargetManager is the write gate for target routes. It double-checks
// the role in-handler so the policy holds even if a route is ever mounted
// without the permission middleware.
func requireTargetManager(w http.ResponseWriter, r *http.Request) bool {
	if canManageTargets(auth.UserFrom(r.Context())) {
		return true
	}
	httpresponse.Error(w, http.StatusForbidden, middleware.RequestIDFrom(r.Context()),
		httpresponse.CodeForbidden, "Hanya GM yang dapat menetapkan atau mengubah target sales", nil)
	return false
}

// Create handles POST /sales-targets — GM (or admin/owner) only.
func (h *Handler) Create(w http.ResponseWriter, r *http.Request) {
	if !requireTargetManager(w, r) {
		return
	}
	var in TargetInput
	if err := json.NewDecoder(r.Body).Decode(&in); err != nil {
		httpresponse.Error(w, http.StatusBadRequest, middleware.RequestIDFrom(r.Context()),
			httpresponse.CodeValidation, "Format data target tidak valid", nil)
		return
	}

	if in.RoleLevel == "" || in.ScopeLevel == "" || in.ScopeName == "" {
		httpresponse.Error(w, http.StatusBadRequest, middleware.RequestIDFrom(r.Context()),
			httpresponse.CodeValidation, "role_level, scope_level, dan scope_name wajib diisi", nil)
		return
	}
	if !validateScope(in.RoleLevel, in.ScopeLevel) {
		httpresponse.Error(w, http.StatusBadRequest, middleware.RequestIDFrom(r.Context()),
			httpresponse.CodeValidation, "Pasangan tingkatan & wilayah tidak valid: GM=1 kota, BM=1 kecamatan, SPV=1 kelurahan, Sales Lapangan=1 desa", nil)
		return
	}
	// Every target must be linked to an accountable account (PIC). Without a
	// user the row can never show up in the owner's "my target" view.
	if in.UserID == nil || *in.UserID <= 0 {
		httpresponse.Error(w, http.StatusBadRequest, middleware.RequestIDFrom(r.Context()),
			httpresponse.CodeValidation, "Target wajib terhubung ke akun penanggung jawab (PIC)", nil)
		return
	}

	var createdBy int64
	if u := auth.UserFrom(r.Context()); u != nil {
		createdBy = u.ID
	}

	t, err := h.repo.Create(r.Context(), in, createdBy)
	if err != nil {
		httpresponse.Internal(w, middleware.RequestIDFrom(r.Context()), logger.FromContext(r.Context()), err)
		return
	}
	httpresponse.OK(w, http.StatusCreated, middleware.RequestIDFrom(r.Context()), t)
}

// Update handles PUT /sales-targets/{id} — GM (or admin/owner) only.
func (h *Handler) Update(w http.ResponseWriter, r *http.Request) {
	if !requireTargetManager(w, r) {
		return
	}
	id, _ := strconv.ParseInt(chi.URLParam(r, "id"), 10, 64)
	if id <= 0 {
		httpresponse.Error(w, http.StatusBadRequest, middleware.RequestIDFrom(r.Context()),
			httpresponse.CodeValidation, "ID tidak valid", nil)
		return
	}

	var in TargetInput
	if err := json.NewDecoder(r.Body).Decode(&in); err != nil {
		httpresponse.Error(w, http.StatusBadRequest, middleware.RequestIDFrom(r.Context()),
			httpresponse.CodeValidation, "Format data target tidak valid", nil)
		return
	}
	if in.RoleLevel != "" && in.ScopeLevel != "" && !validateScope(in.RoleLevel, in.ScopeLevel) {
		httpresponse.Error(w, http.StatusBadRequest, middleware.RequestIDFrom(r.Context()),
			httpresponse.CodeValidation, "Pasangan tingkatan & wilayah tidak valid: GM=1 kota, BM=1 kecamatan, SPV=1 kelurahan, Sales Lapangan=1 desa", nil)
		return
	}

	t, err := h.repo.Update(r.Context(), id, in)
	if err != nil {
		httpresponse.Internal(w, middleware.RequestIDFrom(r.Context()), logger.FromContext(r.Context()), err)
		return
	}
	httpresponse.OK(w, http.StatusOK, middleware.RequestIDFrom(r.Context()), t)
}

// Delete handles DELETE /sales-targets/{id} — GM (or admin/owner) only.
func (h *Handler) Delete(w http.ResponseWriter, r *http.Request) {
	if !requireTargetManager(w, r) {
		return
	}
	id, _ := strconv.ParseInt(chi.URLParam(r, "id"), 10, 64)
	if id <= 0 {
		httpresponse.Error(w, http.StatusBadRequest, middleware.RequestIDFrom(r.Context()),
			httpresponse.CodeValidation, "ID tidak valid", nil)
		return
	}

	if err := h.repo.Delete(r.Context(), id); err != nil {
		httpresponse.Internal(w, middleware.RequestIDFrom(r.Context()), logger.FromContext(r.Context()), err)
		return
	}
	httpresponse.OK(w, http.StatusOK, middleware.RequestIDFrom(r.Context()), map[string]string{"message": "Target berhasil dihapus"})
}
