package app

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

func TestAPIAppendRouteDecisionLogReturnsCreated(t *testing.T) {
	handler := NewAPIHandler("secret-token", ActionSet{
		AppendRouteDecisionLog: func(_ context.Context, req AppendRouteDecisionLogRequest) (RouteDecisionLogInfo, error) {
			if req.RequestID != "req-1" {
				t.Fatalf("RequestID = %q, want req-1", req.RequestID)
			}
			return RouteDecisionLogInfo{
				ID:                    1,
				RequestID:             req.RequestID,
				LogicalGroupID:        req.LogicalGroupID,
				PublicModel:           req.PublicModel,
				SelectedRouteID:       req.SelectedRouteID,
				SelectedShadowGroupID: req.SelectedShadowGroupID,
			}, nil
		},
	})

	request := httptestRequest(t, http.MethodPost, "/api/routing/logs/decisions", map[string]any{
		"request_id":               "req-1",
		"logical_group_id":         "gpt-shared",
		"public_model":             "gpt-5.4",
		"selected_route_id":        "asxs",
		"selected_shadow_group_id": "gpt-shared__asxs",
		"sync":                     true,
	}, "secret-token")
	response := httptestRecorder(handler, request)
	assertStatusCode(t, response, http.StatusCreated)
	assertJSONContains(t, response.Body().Bytes(), "decision_log.request_id", "req-1")
}

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

	request := httptestRequest(t, http.MethodGet, "/api/routing/logs/decisions?limit=zero", nil, "secret-token")
	response := httptestRecorder(handler, request)
	assertStatusCode(t, response, http.StatusBadRequest)
}

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

	decision, err := actions.AppendRouteDecisionLog(ctx, AppendRouteDecisionLogRequest{
		RequestID:             "req-1",
		LogicalGroupID:        "gpt-shared",
		PublicModel:           "gpt-5.4",
		StickyKey:             "sticky-1",
		StickyKeyType:         "conversation",
		StickyHit:             true,
		SelectedRouteID:       "asxs",
		SelectedShadowGroupID: "gpt-shared__asxs",
		UpstreamStatus:        200,
		LatencyMS:             90,
		Sync:                  true,
	})
	if err != nil {
		t.Fatalf("AppendRouteDecisionLog() error = %v", err)
	}
	if decision.SelectedRouteID != "asxs" {
		t.Fatalf("AppendRouteDecisionLog() = %+v, want selected route asxs", decision)
	}

	failover, err := actions.AppendRouteFailoverEvent(ctx, AppendRouteFailoverEventRequest{
		RequestID:      "req-2",
		LogicalGroupID: "gpt-shared",
		PublicModel:    "gpt-5.4",
		FromRouteID:    "asxs",
		ToRouteID:      "codex2api",
		Reason:         "timeout",
		FailureCount:   2,
		Sync:           true,
	})
	if err != nil {
		t.Fatalf("AppendRouteFailoverEvent() error = %v", err)
	}
	if failover.ToRouteID != "codex2api" {
		t.Fatalf("AppendRouteFailoverEvent() = %+v, want to_route_id codex2api", failover)
	}

	sticky, err := actions.AppendRouteStickyAudit(ctx, AppendRouteStickyAuditRequest{
		StickyKey:      "sticky-1",
		StickyKeyType:  "conversation",
		LogicalGroupID: "gpt-shared",
		PublicModel:    "gpt-5.4",
		RouteID:        "asxs",
		Action:         "bind",
		ExpiresAt:      "2026-05-28T18:00:00Z",
		Sync:           true,
	})
	if err != nil {
		t.Fatalf("AppendRouteStickyAudit() error = %v", err)
	}
	if sticky.Action != "bind" {
		t.Fatalf("AppendRouteStickyAudit() = %+v, want action bind", sticky)
	}

	decisions, err := actions.ListRouteDecisionLogs(ctx, ListRouteDecisionLogsRequest{
		RequestID: "req-1",
		Limit:     10,
	})
	if err != nil {
		t.Fatalf("ListRouteDecisionLogs() error = %v", err)
	}
	if len(decisions) != 1 || decisions[0].StickyKey != "sticky-1" {
		t.Fatalf("ListRouteDecisionLogs() = %+v, want sticky-1", decisions)
	}

	failovers, err := actions.ListRouteFailoverEvents(ctx, ListRouteFailoverEventsRequest{
		RequestID: "req-2",
		Limit:     10,
	})
	if err != nil {
		t.Fatalf("ListRouteFailoverEvents() error = %v", err)
	}
	if len(failovers) != 1 || failovers[0].FailureCount != 2 {
		t.Fatalf("ListRouteFailoverEvents() = %+v, want failure_count 2", failovers)
	}

	stickyAudits, err := actions.ListRouteStickyAudit(ctx, ListRouteStickyAuditRequest{
		StickyKey: "sticky-1",
		Limit:     10,
	})
	if err != nil {
		t.Fatalf("ListRouteStickyAudit() error = %v", err)
	}
	if len(stickyAudits) != 1 || stickyAudits[0].RouteID != "asxs" {
		t.Fatalf("ListRouteStickyAudit() = %+v, want route asxs", stickyAudits)
	}
}
