package api

import (
	"encoding/json"
	"net/http"
	"os"

	"github.com/pulse-lets-go/internal/esl"
	"github.com/pulse-lets-go/internal/models"
)

// handleRoute — GET /api/route — главный эндпоинт маршрутизации вызова.
// Принимает query-параметры: caller_id, dest, ingress_trunk (передаются из route.lua).
// Использует routing rules engine (PickNodeForCall) с priority-based first-match разрешением.
func (a *API) handleRoute(w http.ResponseWriter, r *http.Request) {
	a.engine.IncrementRouteRequests()

	callerID := r.URL.Query().Get("caller_id")
	dest := r.URL.Query().Get("dest")
	ingressTrunk := r.URL.Query().Get("ingress_trunk")

	nodeID, score, fallback, matchedRule := a.engine.PickNodeForCall(callerID, dest, ingressTrunk)

	// Нет зарегистрированных нод
	if nodeID == "" {
		a.engine.IncrementRouteFallbacks()
		writeJSON(w, http.StatusServiceUnavailable, models.RouteResponse{
			Error:       "no_nodes_registered",
			MatchedRule: matchedRule,
		})
		return
	}

	// Все ноды unhealthy — fallback
	if fallback || score < 0 {
		a.engine.IncrementRouteFallbacks()
		fallbackGW := a.cachedFallbackGateway()
		nodes := a.getRouteNodeInfo()

		writeJSON(w, http.StatusOK, models.RouteResponse{
			Fallback:    true,
			SIPGateway:  fallbackGW,
			Reason:      "all_nodes_unhealthy",
			Nodes:       nodes,
			MatchedRule: matchedRule,
		})
		return
	}

	// Нормальный маршрут — O(1) из in-memory кэша
	gw, ok := a.cachedBalanceGateway(nodeID)
	if !ok {
		writeError(w, http.StatusInternalServerError, "gateway не найден для ноды")
		return
	}

	writeJSON(w, http.StatusOK, models.RouteResponse{
		NodeID:      nodeID,
		Score:       score,
		SIPGateway:  gw,
		MatchedRule: matchedRule,
	})
}

// handleSetRouteMode — PUT /api/route/mode (admin only).
func (a *API) handleSetRouteMode(w http.ResponseWriter, r *http.Request) {
	var req struct {
		Mode string `json:"mode"`
	}
	if err := decodeJSON(r, &req); err != nil {
		writeError(w, http.StatusBadRequest, "некорректный JSON")
		return
	}
	if req.Mode != "best" && req.Mode != "weighted_random" {
		writeError(w, http.StatusBadRequest, "mode должен быть 'best' или 'weighted_random'")
		return
	}

	// Сохраняем в config.json атомарно
	if a.configManager != nil {
		cfg, err := a.configManager.ReadConfig()
		if err == nil {
			cfg.BalanceMode = req.Mode
			a.configManager.SaveConfig(cfg)
		}
	}

	a.engine.SetBalancingMode(req.Mode)

	// Немедленный WS-push для обновления UI
	go a.broadcastBalancerUpdate()

	writeJSON(w, http.StatusOK, map[string]string{"mode": req.Mode})
}

// GetBalanceGateway возвращает gateway для ноды из in-memory кэша (O(1)).
func (a *API) GetBalanceGateway(nodeID string) (string, bool) {
	return a.cachedBalanceGateway(nodeID)
}

// GetFallbackGateway возвращает fallback gateway из in-memory кэша (O(1)).
func (a *API) GetFallbackGateway() string {
	return a.cachedFallbackGateway()
}

// SetRouteCachePath устанавливает путь к файловому кэшу маршрута.
func (a *API) SetRouteCachePath(path string) {
	a.routeCachePath = path
}

// PushRouteCache читает best-node из engine, резолвит gateway из trunk cache,
// атомарно пишет файловый кэш и пушит маршрут в FreeSWITCH через ESL.
func (a *API) PushRouteCache() {
	nodeID, score, fallback := a.engine.GetBestNode()

	var gw string
	if nodeID != "" && !fallback {
		gw, _ = a.cachedBalanceGateway(nodeID)
	}
	if gw == "" {
		gw = a.cachedFallbackGateway()
	}

	entry := buildRouteCacheEntry(nodeID, gw, score, fallback)

	if a.routeCachePath != "" {
		writeRouteCacheFile(a.routeCachePath, entry)
	}

	if a.eslClient != nil && a.eslClient.IsConnected() {
		esl.PushRoute(a.eslClient, entry)
	}
}

// cachedBalanceGateway возвращает gateway для balance-транка из in-memory кэша (O(1)).
func (a *API) cachedBalanceGateway(nodeID string) (string, bool) {
	a.trunkCacheMu.RLock()
	defer a.trunkCacheMu.RUnlock()
	gw, ok := a.balanceGateways[nodeID]
	return gw, ok
}

// cachedFallbackGateway возвращает fallback gateway из in-memory кэша (O(1)).
func (a *API) cachedFallbackGateway() string {
	a.trunkCacheMu.RLock()
	defer a.trunkCacheMu.RUnlock()
	return a.fallbackGateway
}

// buildRouteCacheEntry строит запись для кэширования в ESL global / файл.
func buildRouteCacheEntry(nodeID, gw string, score float64, fallback bool) esl.RouteCacheEntry {
	entry := esl.RouteCacheEntry{
		SIPGateway: gw,
		Score:      score,
		Fallback:   fallback,
	}
	if nodeID == "" {
		entry.Error = "no_nodes_registered"
	} else if fallback {
		entry.Reason = "all_nodes_unhealthy"
	} else {
		entry.NodeID = nodeID
	}
	return entry
}

// writeRouteCacheFile атомарно записывает файловый кэш маршрута.
func writeRouteCacheFile(path string, entry esl.RouteCacheEntry) {
	b, err := json.Marshal(entry)
	if err != nil {
		return
	}
	tmpPath := path + ".tmp"
	if err := os.WriteFile(tmpPath, b, 0644); err != nil {
		return
	}
	os.Rename(tmpPath, path)
}

// getRouteNodeInfo возвращает список нод с их скорами для ответа маршрутизации.
func (a *API) getRouteNodeInfo() []models.RouteNodeInfo {
	nodes := a.engine.GetAllNodes()
	result := make([]models.RouteNodeInfo, 0, len(nodes))
	for _, n := range nodes {
		info := models.RouteNodeInfo{
			ID:     n.NodeID,
			Score:  n.Score,
			Reason: n.LethalReason,
			Status: n.Status,
		}
		if n.Disabled {
			info.Status = "disabled"
		} else if n.IsStale {
			info.Status = "stale"
		} else if n.Score < 0 {
			info.Status = "unhealthy"
		} else {
			info.Status = "healthy"
		}
		result = append(result, info)
	}
	return result
}
