package realtime

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

	"github.com/redis/go-redis/v9"

	"lingniu-vehicle-ingest/go/vehicle-gateway/internal/envelope"
)

type Handler struct {
	repository *Repository
}

type OnlineIndexHandler struct {
	repository *Repository
}

type PipelineDebugHandler struct {
	repository *Repository
}

func NewHandler(repository *Repository) *Handler {
	if repository == nil {
		panic("realtime repository must not be nil")
	}
	return &Handler{repository: repository}
}

func NewOnlineIndexHandler(repository *Repository) *OnlineIndexHandler {
	if repository == nil {
		panic("online index repository must not be nil")
	}
	return &OnlineIndexHandler{repository: repository}
}

func NewPipelineDebugHandler(repository *Repository) *PipelineDebugHandler {
	if repository == nil {
		panic("pipeline debug repository must not be nil")
	}
	return &PipelineDebugHandler{repository: repository}
}

func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	if r.Method != http.MethodGet {
		writeError(w, http.StatusMethodNotAllowed, "method not allowed")
		return
	}
	path := strings.Trim(strings.TrimPrefix(r.URL.Path, "/api/realtime/vehicles/"), "/")
	parts := strings.Split(path, "/")
	if len(parts) == 0 || strings.TrimSpace(parts[0]) == "" {
		writeError(w, http.StatusNotFound, "vehicle not found")
		return
	}
	vin := parts[0]
	switch {
	case len(parts) == 1:
		snapshot, err := h.repository.GetMerged(r.Context(), vin)
		writeResult(w, snapshot, err)
	case len(parts) == 2 && parts[1] == "online":
		status, err := h.repository.IsOnline(r.Context(), vin)
		writeResult(w, status, err)
	case len(parts) == 3 && parts[1] == "protocols":
		snapshot, err := h.repository.GetProtocol(r.Context(), vin, envelope.Protocol(strings.ToUpper(parts[2])))
		writeResult(w, snapshot, err)
	case len(parts) == 3 && parts[1] == "realtime-raw":
		payload, err := h.repository.GetRealtimeRaw(r.Context(), vin, envelope.Protocol(strings.ToUpper(parts[2])))
		writeResult(w, payload, err)
	default:
		writeError(w, http.StatusNotFound, "route not found")
	}
}

func (h *OnlineIndexHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	if r.Method != http.MethodGet {
		writeError(w, http.StatusMethodNotAllowed, "method not allowed")
		return
	}
	if strings.Trim(r.URL.Path, "/") != "api/realtime/online" {
		writeError(w, http.StatusNotFound, "route not found")
		return
	}
	query, err := parseOnlineListQuery(r)
	if err != nil {
		writeError(w, http.StatusBadRequest, err.Error())
		return
	}
	items, total, err := h.repository.ListOnline(r.Context(), query)
	if err != nil {
		writeError(w, http.StatusInternalServerError, err.Error())
		return
	}
	w.Header().Set("Content-Type", "application/json")
	_ = json.NewEncoder(w).Encode(map[string]any{
		"items":  items,
		"total":  total,
		"limit":  query.Limit,
		"offset": query.Offset,
	})
}

func (h *PipelineDebugHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	if r.Method != http.MethodGet {
		writeError(w, http.StatusMethodNotAllowed, "method not allowed")
		return
	}
	if strings.Trim(r.URL.Path, "/") != "api/debug/pipeline" {
		writeError(w, http.StatusNotFound, "route not found")
		return
	}
	summary, err := h.repository.PipelineSummary(r.Context())
	writeResult(w, summary, err)
}

func parseOnlineListQuery(r *http.Request) (OnlineListQuery, error) {
	values := r.URL.Query()
	limit, err := parseBoundedInt(values.Get("limit"), 100, 1, 1000, "limit")
	if err != nil {
		return OnlineListQuery{}, err
	}
	offset, err := parseBoundedInt(values.Get("offset"), 0, 0, 1_000_000, "offset")
	if err != nil {
		return OnlineListQuery{}, err
	}
	return normalizeOnlineListQuery(OnlineListQuery{
		Protocol: envelope.Protocol(values.Get("protocol")),
		Limit:    limit,
		Offset:   offset,
	}), nil
}

func parseBoundedInt(raw string, fallback int, min int, max int, name string) (int, error) {
	raw = strings.TrimSpace(raw)
	if raw == "" {
		return fallback, nil
	}
	value, err := strconv.Atoi(raw)
	if err != nil || value < min || value > max {
		return 0, fmt.Errorf("%s must be between %d and %d", name, min, max)
	}
	return value, nil
}

func writeResult(w http.ResponseWriter, value any, err error) {
	if err != nil {
		if errors.Is(err, redis.Nil) {
			writeError(w, http.StatusNotFound, "not found")
			return
		}
		writeError(w, http.StatusInternalServerError, err.Error())
		return
	}
	w.Header().Set("Content-Type", "application/json")
	_ = json.NewEncoder(w).Encode(value)
}

func writeError(w http.ResponseWriter, status int, message string) {
	w.Header().Set("Content-Type", "application/json")
	w.WriteHeader(status)
	_ = json.NewEncoder(w).Encode(map[string]any{"error": message})
}
