package app

import (
	"context"
	"net/http"
	"path/filepath"
	"testing"
)

func TestAPIListRouteHealthRejectsInvalidStatus(t *testing.T) {
	handler := NewAPIHandler("secret-token", ActionSet{
		ListRouteHealth: func(context.Context, ListRouteHealthRequest) ([]RouteHealthInfo, error) {
			t.Fatal("ListRouteHealth should not be called")
			return nil, nil
		},
	})

	request := httptestRequest(t, http.MethodGet, "/api/routing/routes/health?status=broken", nil, "secret-token")
	response := httptestRecorder(handler, request)
	assertStatusCode(t, response, http.StatusBadRequest)
	assertJSONContains(t, response.Body().Bytes(), "error.code", "bad_request")
}

func TestNewActionSetRouteHealthFlow(t *testing.T) {
	dsn := "file:" + filepath.ToSlash(filepath.Join(t.TempDir(), "route-health.db")) + "?_busy_timeout=5000"
	actions := NewActionSet(dsn)
	ctx := context.Background()

	if _, err := actions.CreateLogicalGroup(ctx, CreateLogicalGroupRequest{
		LogicalGroupID:         "gpt-shared",
		DisplayName:            "GPT Shared",
		Status:                 "active",
		RoutePolicy:            "priority",
		StickyMode:             "conversation_preferred",
		ConversationTTLSeconds: 1200,
		UserModelTTLSeconds:    600,
		FailoverThreshold:      2,
		CooldownSeconds:        300,
	}); err != nil {
		t.Fatalf("CreateLogicalGroup(gpt-shared) error = %v", err)
	}

	createRoute := func(routeID, name, status, shadowGroup string, priority int) {
		t.Helper()
		if _, err := actions.CreateLogicalGroupRoute(ctx, CreateLogicalGroupRouteRequest{
			LogicalGroupID:      "gpt-shared",
			RouteID:             routeID,
			Name:                name,
			Status:              status,
			Priority:            priority,
			ShadowGroupID:       shadowGroup,
			ShadowHostID:        "remote43",
			UpstreamBaseURLHint: "https://example.com/v1",
		}); err != nil {
			t.Fatalf("CreateLogicalGroupRoute(%s) error = %v", routeID, err)
		}
	}

	createRoute("healthy-route", "Healthy", "active", "shadow-healthy", 10)
	createRoute("failing-route", "Failing", "active", "shadow-failing", 20)
	createRoute("cooldown-route", "Cooldown", "active", "shadow-cooldown", 30)
	createRoute("disabled-route", "Disabled", "disabled", "shadow-disabled", 40)

	if _, err := actions.AppendRouteDecisionLog(ctx, AppendRouteDecisionLogRequest{
		RequestID:             "req-healthy",
		LogicalGroupID:        "gpt-shared",
		PublicModel:           "gpt-5.4",
		SelectedRouteID:       "healthy-route",
		SelectedShadowGroupID: "shadow-healthy",
		UpstreamStatus:        200,
		Sync:                  true,
	}); err != nil {
		t.Fatalf("AppendRouteDecisionLog(healthy) error = %v", err)
	}

	if _, err := actions.AppendRouteDecisionLog(ctx, AppendRouteDecisionLogRequest{
		RequestID:             "req-failing",
		LogicalGroupID:        "gpt-shared",
		PublicModel:           "gpt-5.4",
		SelectedRouteID:       "failing-route",
		SelectedShadowGroupID: "shadow-failing",
		ErrorClass:            "upstream_5xx",
		UpstreamStatus:        503,
		Sync:                  true,
	}); err != nil {
		t.Fatalf("AppendRouteDecisionLog(failing) error = %v", err)
	}

	if _, err := actions.AppendRouteFailoverEvent(ctx, AppendRouteFailoverEventRequest{
		RequestID:      "req-failover",
		LogicalGroupID: "gpt-shared",
		PublicModel:    "gpt-5.4",
		FromRouteID:    "failing-route",
		ToRouteID:      "healthy-route",
		Reason:         "failure_threshold_exceeded:timeout",
		FailureCount:   2,
		Sync:           true,
	}); err != nil {
		t.Fatalf("AppendRouteFailoverEvent() error = %v", err)
	}

	if _, err := actions.SetRouteFailure(ctx, SetRouteFailureRequest{
		RouteID:        "failing-route",
		FailureCount:   2,
		LastErrorClass: "timeout",
		TTLSeconds:     600,
	}); err != nil {
		t.Fatalf("SetRouteFailure() error = %v", err)
	}

	if _, err := actions.SetRouteCooldown(ctx, SetRouteCooldownRequest{
		RouteID:    "cooldown-route",
		Reason:     "degraded",
		TTLSeconds: 600,
	}); err != nil {
		t.Fatalf("SetRouteCooldown() error = %v", err)
	}

	items, err := actions.ListRouteHealth(ctx, ListRouteHealthRequest{})
	if err != nil {
		t.Fatalf("ListRouteHealth() error = %v", err)
	}
	if len(items) != 4 {
		t.Fatalf("ListRouteHealth() len = %d, want 4", len(items))
	}

	byRoute := make(map[string]RouteHealthInfo, len(items))
	for _, item := range items {
		byRoute[item.RouteID] = item
	}

	if got := byRoute["healthy-route"]; got.RuntimeStatus != routeRuntimeStatusHealthy || got.LastUpstreamStatus != 200 {
		t.Fatalf("healthy-route = %+v, want healthy with upstream 200", got)
	}
	if got := byRoute["failing-route"]; got.RuntimeStatus != routeRuntimeStatusFailing || got.FailureCount != 2 || got.LastErrorClass != "timeout" || got.RecentFailoverCount != 1 || got.LastUpstreamStatus != 503 {
		t.Fatalf("failing-route = %+v, want failing with failure_count=2 recent_failover_count=1 upstream=503", got)
	}
	if got := byRoute["cooldown-route"]; got.RuntimeStatus != routeRuntimeStatusCooldown || got.CooldownReason != "degraded" || got.CooldownUntil == "" {
		t.Fatalf("cooldown-route = %+v, want cooldown with reason degraded", got)
	}
	if got := byRoute["disabled-route"]; got.RuntimeStatus != routeRuntimeStatusDisabled {
		t.Fatalf("disabled-route = %+v, want disabled", got)
	}

	failingOnly, err := actions.ListRouteHealth(ctx, ListRouteHealthRequest{Status: routeRuntimeStatusFailing})
	if err != nil {
		t.Fatalf("ListRouteHealth(failing) error = %v", err)
	}
	if len(failingOnly) != 1 || failingOnly[0].RouteID != "failing-route" {
		t.Fatalf("ListRouteHealth(failing) = %+v, want only failing-route", failingOnly)
	}

	oneRoute, err := actions.ListRouteHealth(ctx, ListRouteHealthRequest{RouteID: "cooldown-route"})
	if err != nil {
		t.Fatalf("ListRouteHealth(route_id) error = %v", err)
	}
	if len(oneRoute) != 1 || oneRoute[0].RouteID != "cooldown-route" {
		t.Fatalf("ListRouteHealth(route_id) = %+v, want only cooldown-route", oneRoute)
	}
}
