package httpx

import (
	"context"
	"net/http"
	"time"

	"go.mongodb.org/mongo-driver/v2/bson"

	"bountyboard/internal/store"
)

func (s *Server) routesAdmin(mux *http.ServeMux) {
	adm := func(h http.HandlerFunc) http.Handler { return s.authedRole("admin", h) }

	mux.Handle("GET /api/v1/admin/users", adm(s.handleAdminListUsers))
	mux.Handle("PATCH /api/v1/admin/users/{id}", adm(s.handleAdminPatchUser))
	mux.Handle("DELETE /api/v1/admin/users/{id}", adm(s.handleAdminDeleteUser))

	mux.Handle("GET /api/v1/admin/customers", adm(s.handleAdminListCustomers))
	mux.Handle("POST /api/v1/admin/customers", adm(s.handleAdminCreateCustomer))
	mux.Handle("GET /api/v1/admin/customers/{id}", adm(s.handleAdminGetCustomer))
	mux.Handle("PATCH /api/v1/admin/customers/{id}", adm(s.handleAdminPatchCustomer))
	mux.Handle("DELETE /api/v1/admin/customers/{id}", adm(s.handleAdminDeleteCustomer))
	mux.Handle("POST /api/v1/admin/customers/test-connection", adm(s.handleAdminTestConnectionUnsaved))
	mux.Handle("POST /api/v1/admin/customers/{id}/test-connection", adm(s.handleAdminTestConnection))
	mux.Handle("POST /api/v1/admin/customers/{id}/sync-now", adm(s.handleAdminSyncNow))

	mux.Handle("GET /api/v1/admin/settings", adm(s.handleAdminGetSettings))
	mux.Handle("PATCH /api/v1/admin/settings", adm(s.handleAdminPatchSettings))
	mux.Handle("GET /api/v1/admin/audit-log", adm(s.handleAdminAuditLog))
	mux.Handle("GET /api/v1/admin/service-status", adm(s.handleAdminServiceStatus))
}

// audit records a privileged mutation; failure is logged, never fatal.
func (s *Server) audit(r *http.Request, action, entity, entityID string, diff map[string]any) {
	err := s.store.Audit(r.Context(), store.AuditEntry{
		ActorID:  CurrentUser(r.Context()).ID,
		ActorIP:  ClientIP(r.Context()).String(),
		Action:   action,
		Entity:   entity,
		EntityID: entityID,
		Diff:     diff,
	})
	if err != nil {
		s.log.Error("audit write failed", "action", action, "err", err)
	}
}

func (s *Server) handleAdminGetSettings(w http.ResponseWriter, r *http.Request) {
	st, err := s.store.GetSettings(r.Context())
	if err != nil {
		s.internalError(w, r, "get settings", err)
		return
	}
	writeJSON(w, http.StatusOK, map[string]any{"settings": st})
}

func (s *Server) handleAdminPatchSettings(w http.ResponseWriter, r *http.Request) {
	var req struct {
		BrandingName            *string  `json:"brandingName"`
		AtomizerBaseURLOverride *string  `json:"atomizerBaseUrlOverride"`
		HideCompetingClaims     *bool    `json:"hideCompetingClaims"`
		DefaultPollIntervalSec  *int     `json:"defaultPollIntervalSec"`
		DefaultCustomerBudget   *float64 `json:"defaultCustomerBudget"`
	}
	if !decodeJSON(w, r, &req) {
		return
	}
	set := bson.M{}
	if req.BrandingName != nil {
		set["brandingName"] = *req.BrandingName
	}
	if req.AtomizerBaseURLOverride != nil {
		set["atomizerBaseUrlOverride"] = *req.AtomizerBaseURLOverride
	}
	if req.HideCompetingClaims != nil {
		set["hideCompetingClaims"] = *req.HideCompetingClaims
	}
	if req.DefaultPollIntervalSec != nil {
		if *req.DefaultPollIntervalSec < 10 {
			writeError(w, http.StatusBadRequest, "bad_request", "poll interval must be >= 10s")
			return
		}
		set["defaultPollIntervalSec"] = *req.DefaultPollIntervalSec
	}
	if req.DefaultCustomerBudget != nil {
		set["defaultCustomerBudget"] = *req.DefaultCustomerBudget
	}
	if len(set) == 0 {
		writeError(w, http.StatusBadRequest, "empty_update", "no recognized fields")
		return
	}
	st, err := s.store.PatchSettings(r.Context(), set)
	if err != nil {
		s.internalError(w, r, "patch settings", err)
		return
	}
	s.audit(r, "settings.update", "settings", "app", map[string]any{"set": set})
	writeJSON(w, http.StatusOK, map[string]any{"settings": st})
}

func (s *Server) handleAdminAuditLog(w http.ResponseWriter, r *http.Request) {
	q := r.URL.Query()
	entries, err := s.store.ListAudit(r.Context(), q.Get("actorId"), q.Get("entityId"),
		q.Get("cursor"), atoiDefault(q.Get("limit"), 50))
	if err != nil {
		s.internalError(w, r, "list audit", err)
		return
	}
	next := ""
	if len(entries) > 0 {
		next = entries[len(entries)-1].ID
	}
	writeJSON(w, http.StatusOK, map[string]any{"entries": entries, "nextCursor": next})
}

// serviceStatus is the §11.17 admin panel payload. Breaker and queue fields
// are filled in by the phases that introduce them.
type externalStatus struct {
	Name      string `json:"name"`
	URL       string `json:"url"`
	Healthy   bool   `json:"healthy"`
	LatencyMs int64  `json:"latencyMs"`
	Error     string `json:"error,omitempty"`
	Breaker   string `json:"breaker"`
}

func (s *Server) handleAdminServiceStatus(w http.ResponseWriter, r *http.Request) {
	ctx, cancel := context.WithTimeout(r.Context(), 6*time.Second)
	defer cancel()

	atomizer := s.probeExternal(ctx, "atomizer", s.effectiveAtomizerBaseURL(ctx))
	performer := s.probeExternal(ctx, "work-performer", s.cfg.WorkPerformerBaseURL)
	if s.atomizer != nil {
		atomizer.Breaker = s.atomizer.BreakerState()
	}
	if s.performer != nil {
		performer.Breaker = s.performer.BreakerState()
	}

	mongoOK := true
	mongoErr := ""
	if err := s.store.Ping(ctx); err != nil {
		mongoOK, mongoErr = false, err.Error()
	}

	out := map[string]any{
		"mongo":         map[string]any{"healthy": mongoOK, "error": mongoErr},
		"atomizer":      atomizer,
		"workPerformer": performer,
		"syncWorkers":   s.syncStatuses(),
		"jobs":          s.jobStatuses(),
	}
	writeJSON(w, http.StatusOK, out)
}

func (s *Server) probeExternal(ctx context.Context, name, baseURL string) externalStatus {
	st := externalStatus{Name: name, URL: baseURL, Breaker: "unknown"}
	start := time.Now()
	req, err := http.NewRequestWithContext(ctx, http.MethodGet, baseURL+"/healthz", nil)
	if err != nil {
		st.Error = err.Error()
		return st
	}
	resp, err := healthProbeClient.Do(req)
	st.LatencyMs = time.Since(start).Milliseconds()
	if err != nil {
		st.Error = err.Error()
		return st
	}
	resp.Body.Close()
	st.Healthy = resp.StatusCode == http.StatusOK
	if !st.Healthy {
		st.Error = resp.Status
	}
	return st
}

var healthProbeClient = &http.Client{Timeout: 3 * time.Second}

func atoiDefault(s string, def int) int {
	if s == "" {
		return def
	}
	var n int
	for _, c := range s {
		if c < '0' || c > '9' {
			return def
		}
		n = n*10 + int(c-'0')
	}
	return n
}
