package backbeat

import (
	"encoding/json"
	"net/http"
	"strconv"
	"time"

	"github.com/gorilla/mux"
	"github.com/prometheus/client_golang/prometheus/promhttp"
	"github.com/rs/zerolog"
)

// AdminServer provides HTTP endpoints for BACKBEAT pulse administration
// Includes tempo control, drift monitoring, and leader status as specified
type AdminServer struct {
	router      *mux.Router
	pulseState  *PulseState
	metrics     *Metrics
	elector     *LeaderElector
	hlc         *HLC
	logger      zerolog.Logger
	degradation *DegradationManager
}

// AdminConfig configures the admin server
type AdminConfig struct {
	PulseState  *PulseState
	Metrics     *Metrics
	Elector     *LeaderElector
	HLC         *HLC
	Logger      zerolog.Logger
	Degradation *DegradationManager
}

// TempoResponse represents the response for tempo endpoints
type TempoResponse struct {
	CurrentBPM int    `json:"current_bpm"`
	PendingBPM int    `json:"pending_bpm"`
	CanChange  bool   `json:"can_change"`
	NextChange string `json:"next_change,omitempty"`
	Reason     string `json:"reason,omitempty"`
}

// DriftResponse represents the response for drift monitoring
type DriftResponse struct {
	TimerDriftPercent float64 `json:"timer_drift_percent"`
	HLCDriftSeconds   float64 `json:"hlc_drift_seconds"`
	LastSyncTime      string  `json:"last_sync_time"`
	DegradationMode   bool    `json:"degradation_mode"`
	WithinLimits      bool    `json:"within_limits"`
}

// LeaderResponse represents the response for leader status
type LeaderResponse struct {
	NodeID      string                 `json:"node_id"`
	IsLeader    bool                   `json:"is_leader"`
	Leader      string                 `json:"leader"`
	ClusterSize int                    `json:"cluster_size"`
	Stats       map[string]interface{} `json:"stats"`
}

// HealthResponse represents the health check response
type HealthResponse struct {
	Status      string    `json:"status"`
	Timestamp   time.Time `json:"timestamp"`
	Version     string    `json:"version"`
	NodeID      string    `json:"node_id"`
	IsLeader    bool      `json:"is_leader"`
	BeatIndex   int64     `json:"beat_index"`
	TempoBPM    int       `json:"tempo_bpm"`
	Degradation bool      `json:"degradation_mode"`
}

// NewAdminServer creates a new admin API server
func NewAdminServer(config AdminConfig) *AdminServer {
	server := &AdminServer{
		router:      mux.NewRouter(),
		pulseState:  config.PulseState,
		metrics:     config.Metrics,
		elector:     config.Elector,
		hlc:         config.HLC,
		logger:      config.Logger.With().Str("component", "admin-api").Logger(),
		degradation: config.Degradation,
	}

	server.setupRoutes()
	return server
}

// setupRoutes configures all admin API routes
func (s *AdminServer) setupRoutes() {
	// Tempo control endpoints
	s.router.HandleFunc("/tempo", s.getTempo).Methods("GET")
	s.router.HandleFunc("/tempo", s.setTempo).Methods("POST")

	// Drift monitoring endpoint
	s.router.HandleFunc("/drift", s.getDrift).Methods("GET")

	// Leader status endpoint
	s.router.HandleFunc("/leader", s.getLeader).Methods("GET")

	// Health check endpoints
	s.router.HandleFunc("/health", s.getHealth).Methods("GET")
	s.router.HandleFunc("/ready", s.getReady).Methods("GET")
	s.router.HandleFunc("/live", s.getLive).Methods("GET")

	// Metrics endpoint
	s.router.Handle("/metrics", promhttp.Handler())

	// Debug endpoints
	s.router.HandleFunc("/status", s.getStatus).Methods("GET")
	s.router.HandleFunc("/debug/state", s.getDebugState).Methods("GET")
}

// getTempo handles GET /tempo requests
func (s *AdminServer) getTempo(w http.ResponseWriter, r *http.Request) {
	s.logger.Debug().Msg("GET /tempo request")

	response := TempoResponse{
		CurrentBPM: s.pulseState.TempoBPM,
		PendingBPM: s.pulseState.PendingBPM,
		CanChange:  s.elector.IsLeader(),
	}

	// Check if tempo change is pending
	if s.pulseState.PendingBPM != s.pulseState.TempoBPM {
		// Calculate next downbeat time
		beatsToDownbeat := int64(s.pulseState.BarLength) - ((s.pulseState.BeatIndex - 1) % int64(s.pulseState.BarLength))
		beatDuration := time.Duration(60000/s.pulseState.TempoBPM) * time.Millisecond
		nextDownbeat := time.Now().Add(time.Duration(beatsToDownbeat) * beatDuration)
		response.NextChange = nextDownbeat.Format(time.RFC3339)
	}

	if !response.CanChange {
		response.Reason = "not leader"
	}

	w.Header().Set("Content-Type", "application/json")
	json.NewEncoder(w).Encode(response)
}

// setTempo handles POST /tempo requests with BACKBEAT-REQ-004 validation
func (s *AdminServer) setTempo(w http.ResponseWriter, r *http.Request) {
	s.logger.Debug().Msg("POST /tempo request")

	// Only leader can change tempo
	if !s.elector.IsLeader() {
		s.respondError(w, http.StatusForbidden, "only leader can change tempo")
		s.metrics.RecordTempoChangeError()
		return
	}

	var req TempoChangeRequest
	if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
		s.respondError(w, http.StatusBadRequest, "invalid JSON: "+err.Error())
		s.metrics.RecordTempoChangeError()
		return
	}

	// Validate tempo change per BACKBEAT-REQ-004
	if err := ValidateTempoChange(s.pulseState.TempoBPM, req.TempoBPM); err != nil {
		s.respondError(w, http.StatusBadRequest, err.Error())
		s.metrics.RecordTempoChangeError()
		return
	}

	// Set pending tempo - will be applied on next downbeat
	s.pulseState.PendingBPM = req.TempoBPM

	s.logger.Info().
		Int("current_bpm", s.pulseState.TempoBPM).
		Int("pending_bpm", req.TempoBPM).
		Str("justification", req.Justification).
		Msg("tempo change scheduled")

	response := TempoResponse{
		CurrentBPM: s.pulseState.TempoBPM,
		PendingBPM: req.TempoBPM,
		CanChange:  true,
		Reason:     "scheduled for next downbeat",
	}

	w.Header().Set("Content-Type", "application/json")
	json.NewEncoder(w).Encode(response)
}

// getDrift handles GET /drift requests for BACKBEAT-PER-003 monitoring
func (s *AdminServer) getDrift(w http.ResponseWriter, r *http.Request) {
	s.logger.Debug().Msg("GET /drift request")

	hlcDrift := s.hlc.GetDrift()
	timerDrift := s.degradation.GetTimerDrift()

	response := DriftResponse{
		TimerDriftPercent: timerDrift * 100, // Convert to percentage
		HLCDriftSeconds:   hlcDrift.Seconds(),
		DegradationMode:   s.degradation.IsInDegradationMode(),
		WithinLimits:      timerDrift <= 0.01, // BACKBEAT-PER-003: ≤ 1%
	}

	// Add last sync time if available
	if hlcDrift > 0 {
		response.LastSyncTime = time.Now().Add(-hlcDrift).Format(time.RFC3339)
	}

	w.Header().Set("Content-Type", "application/json")
	json.NewEncoder(w).Encode(response)
}

// getLeader handles GET /leader requests
func (s *AdminServer) getLeader(w http.ResponseWriter, r *http.Request) {
	s.logger.Debug().Msg("GET /leader request")

	stats := s.elector.GetStats()
	clusterSize := 1 // Default to 1 if no stats available
	if size, ok := stats["num_peers"]; ok {
		if sizeStr, ok := size.(string); ok {
			if parsed, err := strconv.Atoi(sizeStr); err == nil {
				clusterSize = parsed + 1 // Add 1 for this node
			}
		}
	}

	response := LeaderResponse{
		NodeID:      s.pulseState.NodeID,
		IsLeader:    s.elector.IsLeader(),
		Leader:      s.elector.GetLeader(),
		ClusterSize: clusterSize,
		Stats:       stats,
	}

	w.Header().Set("Content-Type", "application/json")
	json.NewEncoder(w).Encode(response)
}

// getHealth handles GET /health requests
func (s *AdminServer) getHealth(w http.ResponseWriter, r *http.Request) {
	response := HealthResponse{
		Status:      "ok",
		Timestamp:   time.Now(),
		Version:     "2.0.0",
		NodeID:      s.pulseState.NodeID,
		IsLeader:    s.elector.IsLeader(),
		BeatIndex:   s.pulseState.BeatIndex,
		TempoBPM:    s.pulseState.TempoBPM,
		Degradation: s.degradation.IsInDegradationMode(),
	}

	// Check if degradation mode indicates unhealthy state
	if s.degradation.IsInDegradationMode() {
		drift := s.degradation.GetTimerDrift()
		if drift > 0.05 { // 5% drift indicates serious issues
			response.Status = "degraded"
		}
	}

	statusCode := http.StatusOK
	if response.Status != "ok" {
		statusCode = http.StatusServiceUnavailable
	}

	w.Header().Set("Content-Type", "application/json")
	w.WriteHeader(statusCode)
	json.NewEncoder(w).Encode(response)
}

// getReady handles GET /ready requests for k8s readiness probes
func (s *AdminServer) getReady(w http.ResponseWriter, r *http.Request) {
	// Ready if we have a leader (this node or another)
	if leader := s.elector.GetLeader(); leader != "" {
		w.WriteHeader(http.StatusOK)
		w.Write([]byte("ready"))
	} else {
		w.WriteHeader(http.StatusServiceUnavailable)
		w.Write([]byte("no leader"))
	}
}

// getLive handles GET /live requests for k8s liveness probes
func (s *AdminServer) getLive(w http.ResponseWriter, r *http.Request) {
	// Always live unless we're in severe degradation
	drift := s.degradation.GetTimerDrift()
	if drift > 0.10 { // 10% drift indicates critical issues
		w.WriteHeader(http.StatusServiceUnavailable)
		w.Write([]byte("severe drift"))
		return
	}

	w.WriteHeader(http.StatusOK)
	w.Write([]byte("alive"))
}

// getStatus handles GET /status requests for comprehensive status
func (s *AdminServer) getStatus(w http.ResponseWriter, r *http.Request) {
	status := map[string]interface{}{
		"timestamp":   time.Now(),
		"node_id":     s.pulseState.NodeID,
		"cluster_id":  s.pulseState.ClusterID,
		"is_leader":   s.elector.IsLeader(),
		"leader":      s.elector.GetLeader(),
		"beat_index":  s.pulseState.BeatIndex,
		"tempo_bpm":   s.pulseState.TempoBPM,
		"pending_bpm": s.pulseState.PendingBPM,
		"bar_length":  s.pulseState.BarLength,
		"phases":      s.pulseState.Phases,
		"degradation": s.degradation.IsInDegradationMode(),
		"uptime":      time.Since(s.pulseState.StartTime),
		"raft_stats":  s.elector.GetStats(),
	}

	w.Header().Set("Content-Type", "application/json")
	json.NewEncoder(w).Encode(status)
}

// getDebugState handles GET /debug/state requests
func (s *AdminServer) getDebugState(w http.ResponseWriter, r *http.Request) {
	debugState := map[string]interface{}{
		"pulse_state":  s.pulseState,
		"hlc_drift":    s.hlc.GetDrift(),
		"timer_drift":  s.degradation.GetTimerDrift(),
		"leader_stats": s.elector.GetStats(),
		"degradation":  s.degradation.GetState(),
	}

	w.Header().Set("Content-Type", "application/json")
	json.NewEncoder(w).Encode(debugState)
}

// respondError sends a JSON error response
func (s *AdminServer) respondError(w http.ResponseWriter, statusCode int, message string) {
	w.Header().Set("Content-Type", "application/json")
	w.WriteHeader(statusCode)

	errorResp := map[string]string{
		"error":     message,
		"timestamp": time.Now().Format(time.RFC3339),
	}

	json.NewEncoder(w).Encode(errorResp)
}

// ServeHTTP implements http.Handler interface
func (s *AdminServer) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	// Add common headers
	w.Header().Set("X-BACKBEAT-Node-ID", s.pulseState.NodeID)
	w.Header().Set("X-BACKBEAT-Version", "2.0.0")

	// Log request
	s.logger.Debug().
		Str("method", r.Method).
		Str("path", r.URL.Path).
		Str("remote_addr", r.RemoteAddr).
		Msg("admin API request")

	s.router.ServeHTTP(w, r)
}
