package cache

import (
	"context"
	"encoding/json"
	"errors"
	"fmt"
	"net/http"
	"strings"
	"time"

	"github.com/DeBrosOfficial/network/pkg/logging"
	olriclib "github.com/olric-data/olric"
	"go.uber.org/zap"
)

// GetHandler handles cache GET requests for retrieving a single key from a distributed map.
// It expects a JSON body with "dmap" (distributed map name) and "key" fields.
// Returns the value associated with the key, or 404 if the key is not found.
//
// Request body:
//
//	{
//	  "dmap": "my-cache",
//	  "key": "user:123"
//	}
//
// Response:
//
//	{
//	  "key": "user:123",
//	  "value": {...},
//	  "dmap": "my-cache"
//	}
func (h *CacheHandlers) GetHandler(w http.ResponseWriter, r *http.Request) {
	if h.olricClient == nil {
		writeError(w, http.StatusServiceUnavailable, "Olric cache client not initialized")
		return
	}

	if r.Method != http.MethodPost {
		writeError(w, http.StatusMethodNotAllowed, "method not allowed")
		return
	}

	r.Body = http.MaxBytesReader(w, r.Body, 10<<20) // 10MB
	var req GetRequest
	if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
		writeError(w, http.StatusBadRequest, "invalid json body")
		return
	}

	if strings.TrimSpace(req.DMap) == "" || strings.TrimSpace(req.Key) == "" {
		writeError(w, http.StatusBadRequest, "dmap and key are required")
		return
	}

	ctx, cancel := context.WithTimeout(r.Context(), 10*time.Second)
	defer cancel()

	// Namespace isolation: prefix dmap with namespace
	namespace := getNamespaceFromContext(ctx)
	if namespace == "" {
		writeError(w, http.StatusUnauthorized, "namespace not found in context")
		return
	}
	namespacedDMap := fmt.Sprintf("%s:%s", namespace, req.DMap)

	olricCluster := h.olricClient.GetClient()
	dm, err := olricCluster.NewDMap(namespacedDMap)
	if err != nil {
		writeError(w, http.StatusInternalServerError, fmt.Sprintf("failed to create DMap: %v", err))
		return
	}

	gr, err := dm.Get(ctx, req.Key)
	if err != nil {
		// Check for key not found error - handle both wrapped and direct errors
		if errors.Is(err, olriclib.ErrKeyNotFound) || err.Error() == "key not found" || strings.Contains(err.Error(), "key not found") {
			writeError(w, http.StatusNotFound, "key not found")
			return
		}
		h.logger.ComponentError(logging.ComponentGeneral, "failed to get key from cache",
			zap.String("dmap", req.DMap),
			zap.String("key", req.Key),
			zap.Error(err))
		writeError(w, http.StatusInternalServerError, fmt.Sprintf("failed to get key: %v", err))
		return
	}

	value, err := decodeValueFromOlric(gr)
	if err != nil {
		h.logger.ComponentError(logging.ComponentGeneral, "failed to decode value from cache",
			zap.String("dmap", req.DMap),
			zap.String("key", req.Key),
			zap.Error(err))
		writeError(w, http.StatusInternalServerError, fmt.Sprintf("failed to decode value: %v", err))
		return
	}

	writeJSON(w, http.StatusOK, map[string]any{
		"key":   req.Key,
		"value": value,
		"dmap":  req.DMap,
	})
}

// MultiGetHandler handles cache multi-GET requests for retrieving multiple keys from a distributed map.
// It expects a JSON body with "dmap" (distributed map name) and "keys" (array of keys) fields.
// Returns only the keys that were found; missing keys are silently skipped.
//
// Request body:
//
//	{
//	  "dmap": "my-cache",
//	  "keys": ["user:123", "user:456"]
//	}
//
// Response:
//
//	{
//	  "results": [
//	    {"key": "user:123", "value": {...}},
//	    {"key": "user:456", "value": {...}}
//	  ],
//	  "dmap": "my-cache"
//	}
func (h *CacheHandlers) MultiGetHandler(w http.ResponseWriter, r *http.Request) {
	if h.olricClient == nil {
		writeError(w, http.StatusServiceUnavailable, "Olric cache client not initialized")
		return
	}

	if r.Method != http.MethodPost {
		writeError(w, http.StatusMethodNotAllowed, "method not allowed")
		return
	}

	r.Body = http.MaxBytesReader(w, r.Body, 10<<20) // 10MB
	var req MultiGetRequest
	if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
		writeError(w, http.StatusBadRequest, "invalid json body")
		return
	}

	if strings.TrimSpace(req.DMap) == "" {
		writeError(w, http.StatusBadRequest, "dmap is required")
		return
	}

	if len(req.Keys) == 0 {
		writeError(w, http.StatusBadRequest, "keys array is required and cannot be empty")
		return
	}

	ctx, cancel := context.WithTimeout(r.Context(), 30*time.Second)
	defer cancel()

	// Namespace isolation: prefix dmap with namespace
	namespace := getNamespaceFromContext(ctx)
	if namespace == "" {
		writeError(w, http.StatusUnauthorized, "namespace not found in context")
		return
	}
	namespacedDMap := fmt.Sprintf("%s:%s", namespace, req.DMap)

	olricCluster := h.olricClient.GetClient()
	dm, err := olricCluster.NewDMap(namespacedDMap)
	if err != nil {
		writeError(w, http.StatusInternalServerError, fmt.Sprintf("failed to create DMap: %v", err))
		return
	}

	// Get all keys and collect results
	var results []map[string]any
	for _, key := range req.Keys {
		if strings.TrimSpace(key) == "" {
			continue // Skip empty keys
		}

		gr, err := dm.Get(ctx, key)
		if err != nil {
			// Skip keys that are not found - don't include them in results
			// This matches the SDK's expectation that only found keys are returned
			if err == olriclib.ErrKeyNotFound {
				continue
			}
			// For other errors, log but continue with other keys
			// We don't want one bad key to fail the entire request
			continue
		}

		value, err := decodeValueFromOlric(gr)
		if err != nil {
			// If we can't decode, skip this key
			continue
		}

		results = append(results, map[string]any{
			"key":   key,
			"value": value,
		})
	}

	writeJSON(w, http.StatusOK, map[string]any{
		"results": results,
		"dmap":    req.DMap,
	})
}

// writeJSON writes JSON response with the specified status code.
func writeJSON(w http.ResponseWriter, code int, v any) {
	w.Header().Set("Content-Type", "application/json")
	w.WriteHeader(code)
	_ = json.NewEncoder(w).Encode(v)
}

// writeError writes a standardized JSON error response.
func writeError(w http.ResponseWriter, code int, msg string) {
	writeJSON(w, code, map[string]any{"error": msg})
}
