package app

import (
	"context"
	"encoding/json"
	"net/http"
	"path/filepath"
	"testing"

	"sub2api-cn-relay-manager/internal/store/sqlite"
)

func TestAPICreateLogicalGroupReturnsCreated(t *testing.T) {
	handler := NewAPIHandler("secret-token", ActionSet{
		CreateLogicalGroup: func(_ context.Context, req CreateLogicalGroupRequest) (LogicalGroupInfo, error) {
			if req.LogicalGroupID != "gpt-shared" {
				t.Fatalf("LogicalGroupID = %q, want gpt-shared", req.LogicalGroupID)
			}
			if req.UsageScenario != "适合统一 GPT 产品入口" {
				t.Fatalf("UsageScenario = %q, want configured guidance", req.UsageScenario)
			}
			if req.VisibilityScope != "login_required" || req.PackageTier != "pro" || req.PurchaseCTALabel != "升级到 Pro" || req.PurchaseCTAURL != "https://sub.tksea.top/portal/upgrade/pro" {
				t.Fatalf("packaging fields = %+v, want configured packaging", req)
			}
			return LogicalGroupInfo{
				LogicalGroupID:   req.LogicalGroupID,
				DisplayName:      req.DisplayName,
				Status:           req.Status,
				UsageScenario:    req.UsageScenario,
				VisibilityScope:  req.VisibilityScope,
				PackageTier:      req.PackageTier,
				PurchaseCTALabel: req.PurchaseCTALabel,
				PurchaseCTAURL:   req.PurchaseCTAURL,
			}, nil
		},
	})

	request := httptestRequest(t, http.MethodPost, "/api/logical-groups", map[string]any{
		"logical_group_id":   "gpt-shared",
		"display_name":       "GPT Shared",
		"status":             "active",
		"usage_scenario":     "适合统一 GPT 产品入口",
		"visibility_scope":   "login_required",
		"package_tier":       "pro",
		"purchase_cta_label": "升级到 Pro",
		"purchase_cta_url":   "https://sub.tksea.top/portal/upgrade/pro",
	}, "secret-token")
	response := httptestRecorder(handler, request)
	assertStatusCode(t, response, http.StatusCreated)
	assertJSONContains(t, response.Body().Bytes(), "logical_group.logical_group_id", "gpt-shared")
}

func TestAPIGetLogicalGroupReturnsAggregatedItem(t *testing.T) {
	handler := NewAPIHandler("secret-token", ActionSet{
		GetLogicalGroup: func(_ context.Context, groupID string) (LogicalGroupInfo, error) {
			if groupID != "gpt-shared" {
				t.Fatalf("groupID = %q, want gpt-shared", groupID)
			}
			return LogicalGroupInfo{
				LogicalGroupID:   groupID,
				DisplayName:      "GPT Shared",
				Status:           "active",
				UsageScenario:    "适合统一 GPT 产品入口",
				Recommendation:   "优先使用 gpt-5.4",
				NextStepHint:     "先创建测试 Key",
				VisibilityScope:  "login_required",
				PackageTier:      "pro",
				PurchaseCTALabel: "升级到 Pro",
				PurchaseCTAURL:   "https://sub.tksea.top/portal/upgrade/pro",
				Models:           []LogicalGroupModelInfo{{PublicModel: "gpt-5.4", Status: "active"}},
				Routes: []LogicalGroupRouteInfo{{
					RouteID:        "asxs",
					LogicalGroupID: groupID,
					Name:           "ASXS",
					Status:         "active",
					Models:         []LogicalGroupRouteModelInfo{{PublicModel: "gpt-5.4", ShadowModel: "gpt-5.4"}},
				}},
			}, nil
		},
	})

	request := httptestRequest(t, http.MethodGet, "/api/logical-groups/gpt-shared", nil, "secret-token")
	response := httptestRecorder(handler, request)
	assertStatusCode(t, response, http.StatusOK)
	var payload map[string]any
	if err := json.Unmarshal(response.Body().Bytes(), &payload); err != nil {
		t.Fatalf("json.Unmarshal() error = %v", err)
	}
	group, ok := payload["logical_group"].(map[string]any)
	if !ok {
		t.Fatalf("logical_group = %#v, want object", payload["logical_group"])
	}
	models, ok := group["models"].([]any)
	if !ok || len(models) != 1 {
		t.Fatalf("models = %#v, want one item", group["models"])
	}
	firstModel, ok := models[0].(map[string]any)
	if !ok || firstModel["public_model"] != "gpt-5.4" {
		t.Fatalf("first model = %#v, want public_model gpt-5.4", models[0])
	}
	routes, ok := group["routes"].([]any)
	if !ok || len(routes) != 1 {
		t.Fatalf("routes = %#v, want one item", group["routes"])
	}
	firstRoute, ok := routes[0].(map[string]any)
	if !ok || firstRoute["route_id"] != "asxs" {
		t.Fatalf("first route = %#v, want route_id asxs", routes[0])
	}
	if group["usage_scenario"] != "适合统一 GPT 产品入口" || group["recommendation"] != "优先使用 gpt-5.4" || group["next_step_hint"] != "先创建测试 Key" {
		t.Fatalf("group guidance = %#v, want configured guidance fields", group)
	}
	if group["visibility_scope"] != "login_required" || group["package_tier"] != "pro" || group["purchase_cta_label"] != "升级到 Pro" || group["purchase_cta_url"] != "https://sub.tksea.top/portal/upgrade/pro" {
		t.Fatalf("group packaging = %#v, want configured packaging fields", group)
	}
}

func TestAPIListLogicalGroupsReturnsRows(t *testing.T) {
	handler := NewAPIHandler("secret-token", ActionSet{
		ListLogicalGroups: func(_ context.Context) ([]LogicalGroupInfo, error) {
			return []LogicalGroupInfo{{
				LogicalGroupID:   "gpt-shared",
				DisplayName:      "GPT Shared",
				Status:           "active",
				UsageScenario:    "适合统一 GPT 产品入口",
				VisibilityScope:  "login_required",
				PackageTier:      "pro",
				PurchaseCTALabel: "升级到 Pro",
				PurchaseCTAURL:   "https://sub.tksea.top/portal/upgrade/pro",
			}}, nil
		},
	})

	request := httptestRequest(t, http.MethodGet, "/api/logical-groups", nil, "secret-token")
	response := httptestRecorder(handler, request)
	assertStatusCode(t, response, http.StatusOK)
	var payload struct {
		LogicalGroups []LogicalGroupInfo `json:"logical_groups"`
	}
	if err := json.Unmarshal(response.Body().Bytes(), &payload); err != nil {
		t.Fatalf("json.Unmarshal() error = %v", err)
	}
	if len(payload.LogicalGroups) != 1 || payload.LogicalGroups[0].LogicalGroupID != "gpt-shared" {
		t.Fatalf("logical_groups = %+v, want one row gpt-shared", payload.LogicalGroups)
	}
	if payload.LogicalGroups[0].VisibilityScope != "login_required" {
		t.Fatalf("logical_groups[0].visibility_scope = %q, want login_required", payload.LogicalGroups[0].VisibilityScope)
	}
}

func TestAPIUpdateLogicalGroupUsesPathID(t *testing.T) {
	handler := NewAPIHandler("secret-token", ActionSet{
		UpdateLogicalGroup: func(_ context.Context, req UpdateLogicalGroupRequest) (LogicalGroupInfo, error) {
			if req.LogicalGroupID != "gpt-shared" {
				t.Fatalf("LogicalGroupID = %q, want gpt-shared", req.LogicalGroupID)
			}
			if req.DisplayName != "GPT Shared Updated" || req.Status != "paused" {
				t.Fatalf("request = %+v, want updated display name and status", req)
			}
			return LogicalGroupInfo{
				LogicalGroupID:   req.LogicalGroupID,
				DisplayName:      req.DisplayName,
				Status:           req.Status,
				Recommendation:   req.Recommendation,
				VisibilityScope:  req.VisibilityScope,
				PackageTier:      req.PackageTier,
				PurchaseCTALabel: req.PurchaseCTALabel,
			}, nil
		},
	})

	request := httptestRequest(t, http.MethodPut, "/api/logical-groups/gpt-shared", map[string]any{
		"display_name":       "GPT Shared Updated",
		"status":             "paused",
		"recommendation":     "先验证高质量推理链路",
		"visibility_scope":   "entitled_only",
		"package_tier":       "enterprise",
		"purchase_cta_label": "联系销售升级",
	}, "secret-token")
	response := httptestRecorder(handler, request)
	assertStatusCode(t, response, http.StatusOK)
	assertJSONContains(t, response.Body().Bytes(), "logical_group.logical_group_id", "gpt-shared")
	assertJSONContains(t, response.Body().Bytes(), "logical_group.package_tier", "enterprise")
}

func TestAPIDeleteLogicalGroupUsesPathID(t *testing.T) {
	handler := NewAPIHandler("secret-token", ActionSet{
		DeleteLogicalGroup: func(_ context.Context, groupID string) error {
			if groupID != "gpt-shared" {
				t.Fatalf("groupID = %q, want gpt-shared", groupID)
			}
			return nil
		},
	})

	request := httptestRequest(t, http.MethodDelete, "/api/logical-groups/gpt-shared", nil, "secret-token")
	response := httptestRecorder(handler, request)
	assertStatusCode(t, response, http.StatusNoContent)
}

func TestAPICreateLogicalGroupModelUsesPathGroupID(t *testing.T) {
	handler := NewAPIHandler("secret-token", ActionSet{
		CreateLogicalGroupModel: func(_ context.Context, req CreateLogicalGroupModelRequest) (LogicalGroupModelInfo, error) {
			if req.LogicalGroupID != "gpt-shared" {
				t.Fatalf("LogicalGroupID = %q, want gpt-shared", req.LogicalGroupID)
			}
			if req.PublicModel != "gpt-5.4" || req.Status != "active" {
				t.Fatalf("request = %+v, want public_model gpt-5.4 active", req)
			}
			return LogicalGroupModelInfo{
				PublicModel: req.PublicModel,
				Status:      req.Status,
			}, nil
		},
	})

	request := httptestRequest(t, http.MethodPost, "/api/logical-groups/gpt-shared/models", map[string]any{
		"public_model": "gpt-5.4",
		"status":       "active",
	}, "secret-token")
	response := httptestRecorder(handler, request)
	assertStatusCode(t, response, http.StatusCreated)
	assertJSONContains(t, response.Body().Bytes(), "logical_group_model.public_model", "gpt-5.4")
}

func TestAPIListLogicalGroupModelsUsesPathGroupID(t *testing.T) {
	handler := NewAPIHandler("secret-token", ActionSet{
		ListLogicalGroupModels: func(_ context.Context, groupID string) ([]LogicalGroupModelInfo, error) {
			if groupID != "gpt-shared" {
				t.Fatalf("groupID = %q, want gpt-shared", groupID)
			}
			return []LogicalGroupModelInfo{{PublicModel: "gpt-5.4", Status: "active"}}, nil
		},
	})

	request := httptestRequest(t, http.MethodGet, "/api/logical-groups/gpt-shared/models", nil, "secret-token")
	response := httptestRecorder(handler, request)
	assertStatusCode(t, response, http.StatusOK)
	var payload struct {
		Models []LogicalGroupModelInfo `json:"models"`
	}
	if err := json.Unmarshal(response.Body().Bytes(), &payload); err != nil {
		t.Fatalf("json.Unmarshal() error = %v", err)
	}
	if len(payload.Models) != 1 || payload.Models[0].PublicModel != "gpt-5.4" {
		t.Fatalf("models = %+v, want one row gpt-5.4", payload.Models)
	}
}

func TestAPIDeleteLogicalGroupModelUsesPathValues(t *testing.T) {
	handler := NewAPIHandler("secret-token", ActionSet{
		DeleteLogicalGroupModel: func(_ context.Context, req DeleteLogicalGroupModelRequest) error {
			if req.LogicalGroupID != "gpt-shared" || req.PublicModel != "gpt-5.4" {
				t.Fatalf("request = %+v, want gpt-shared/gpt-5.4", req)
			}
			return nil
		},
	})

	request := httptestRequest(t, http.MethodDelete, "/api/logical-groups/gpt-shared/models/gpt-5.4", nil, "secret-token")
	response := httptestRecorder(handler, request)
	assertStatusCode(t, response, http.StatusNoContent)
}

func TestAPICreateLogicalGroupRouteUsesPathGroupID(t *testing.T) {
	handler := NewAPIHandler("secret-token", ActionSet{
		CreateLogicalGroupRoute: func(_ context.Context, req CreateLogicalGroupRouteRequest) (LogicalGroupRouteInfo, error) {
			if req.LogicalGroupID != "gpt-shared" {
				t.Fatalf("LogicalGroupID = %q, want gpt-shared", req.LogicalGroupID)
			}
			return LogicalGroupRouteInfo{
				RouteID:        req.RouteID,
				LogicalGroupID: req.LogicalGroupID,
				Name:           req.Name,
				Status:         req.Status,
			}, nil
		},
	})

	request := httptestRequest(t, http.MethodPost, "/api/logical-groups/gpt-shared/routes", map[string]any{
		"route_id":        "asxs",
		"name":            "ASXS",
		"status":          "active",
		"priority":        10,
		"shadow_group_id": "gpt-shared__asxs",
		"shadow_host_id":  "remote43",
	}, "secret-token")
	response := httptestRecorder(handler, request)
	assertStatusCode(t, response, http.StatusCreated)
	assertJSONContains(t, response.Body().Bytes(), "route.logical_group_id", "gpt-shared")
	assertJSONContains(t, response.Body().Bytes(), "route.route_id", "asxs")
}

func TestAPIListLogicalGroupRoutesUsesPathGroupID(t *testing.T) {
	handler := NewAPIHandler("secret-token", ActionSet{
		ListLogicalGroupRoutes: func(_ context.Context, groupID string) ([]LogicalGroupRouteInfo, error) {
			if groupID != "gpt-shared" {
				t.Fatalf("groupID = %q, want gpt-shared", groupID)
			}
			return []LogicalGroupRouteInfo{{
				RouteID:        "asxs",
				LogicalGroupID: groupID,
				Name:           "ASXS",
				Status:         "active",
				ShadowGroupID:  "gpt-shared__asxs",
				ShadowHostID:   "remote43",
			}}, nil
		},
	})

	request := httptestRequest(t, http.MethodGet, "/api/logical-groups/gpt-shared/routes", nil, "secret-token")
	response := httptestRecorder(handler, request)
	assertStatusCode(t, response, http.StatusOK)
	var payload struct {
		Routes []LogicalGroupRouteInfo `json:"routes"`
	}
	if err := json.Unmarshal(response.Body().Bytes(), &payload); err != nil {
		t.Fatalf("json.Unmarshal() error = %v", err)
	}
	if len(payload.Routes) != 1 || payload.Routes[0].RouteID != "asxs" {
		t.Fatalf("routes = %+v, want one row asxs", payload.Routes)
	}
	if payload.Routes[0].ShadowHostID != "remote43" {
		t.Fatalf("routes[0].shadow_host_id = %q, want remote43", payload.Routes[0].ShadowHostID)
	}
}

func TestAPIUpdateLogicalGroupRouteUsesPathValues(t *testing.T) {
	handler := NewAPIHandler("secret-token", ActionSet{
		UpdateLogicalGroupRoute: func(_ context.Context, req UpdateLogicalGroupRouteRequest) (LogicalGroupRouteInfo, error) {
			if req.LogicalGroupID != "gpt-shared" || req.RouteID != "asxs" {
				t.Fatalf("request = %+v, want gpt-shared/asxs", req)
			}
			if req.Priority != 20 || req.Status != "degraded" {
				t.Fatalf("request = %+v, want priority 20 degraded", req)
			}
			return LogicalGroupRouteInfo{
				RouteID:        req.RouteID,
				LogicalGroupID: req.LogicalGroupID,
				Name:           req.Name,
				Status:         req.Status,
				Priority:       req.Priority,
				ShadowGroupID:  req.ShadowGroupID,
				ShadowHostID:   req.ShadowHostID,
			}, nil
		},
	})

	request := httptestRequest(t, http.MethodPut, "/api/logical-groups/gpt-shared/routes/asxs", map[string]any{
		"name":            "ASXS Updated",
		"status":          "degraded",
		"priority":        20,
		"shadow_group_id": "gpt-shared__asxs",
		"shadow_host_id":  "remote43",
	}, "secret-token")
	response := httptestRecorder(handler, request)
	assertStatusCode(t, response, http.StatusOK)
	assertJSONContains(t, response.Body().Bytes(), "route.route_id", "asxs")
	assertJSONContains(t, response.Body().Bytes(), "route.status", "degraded")
}

func TestAPIDeleteLogicalGroupRouteUsesPathValues(t *testing.T) {
	handler := NewAPIHandler("secret-token", ActionSet{
		DeleteLogicalGroupRoute: func(_ context.Context, req DeleteLogicalGroupRouteRequest) error {
			if req.LogicalGroupID != "gpt-shared" || req.RouteID != "asxs" {
				t.Fatalf("request = %+v, want gpt-shared/asxs", req)
			}
			return nil
		},
	})

	request := httptestRequest(t, http.MethodDelete, "/api/logical-groups/gpt-shared/routes/asxs", nil, "secret-token")
	response := httptestRecorder(handler, request)
	assertStatusCode(t, response, http.StatusNoContent)
}

func TestAPICreateLogicalGroupRouteModelUsesPathValues(t *testing.T) {
	handler := NewAPIHandler("secret-token", ActionSet{
		CreateLogicalGroupRouteModel: func(_ context.Context, req CreateLogicalGroupRouteModelRequest) (LogicalGroupRouteModelInfo, error) {
			if req.LogicalGroupID != "gpt-shared" {
				t.Fatalf("LogicalGroupID = %q, want gpt-shared", req.LogicalGroupID)
			}
			if req.RouteID != "asxs" {
				t.Fatalf("RouteID = %q, want asxs", req.RouteID)
			}
			return LogicalGroupRouteModelInfo{
				PublicModel: req.PublicModel,
				ShadowModel: req.ShadowModel,
				Status:      req.Status,
			}, nil
		},
	})

	request := httptestRequest(t, http.MethodPost, "/api/logical-groups/gpt-shared/routes/asxs/models", map[string]any{
		"public_model": "gpt-5.4",
		"shadow_model": "gpt-5.4",
		"status":       "active",
	}, "secret-token")
	response := httptestRecorder(handler, request)
	assertStatusCode(t, response, http.StatusCreated)
	assertJSONContains(t, response.Body().Bytes(), "route_model.public_model", "gpt-5.4")
}

func TestAPIListLogicalGroupRouteModelsUsesPathValues(t *testing.T) {
	handler := NewAPIHandler("secret-token", ActionSet{
		ListLogicalGroupRouteModels: func(_ context.Context, req ListLogicalGroupRouteModelsRequest) ([]LogicalGroupRouteModelInfo, error) {
			if req.LogicalGroupID != "gpt-shared" || req.RouteID != "asxs" {
				t.Fatalf("request = %+v, want gpt-shared/asxs", req)
			}
			return []LogicalGroupRouteModelInfo{{
				PublicModel: "gpt-5.4",
				ShadowModel: "gpt-5.4",
				Status:      "active",
			}}, nil
		},
	})

	request := httptestRequest(t, http.MethodGet, "/api/logical-groups/gpt-shared/routes/asxs/models", nil, "secret-token")
	response := httptestRecorder(handler, request)
	assertStatusCode(t, response, http.StatusOK)
	var payload struct {
		RouteModels []LogicalGroupRouteModelInfo `json:"route_models"`
	}
	if err := json.Unmarshal(response.Body().Bytes(), &payload); err != nil {
		t.Fatalf("json.Unmarshal() error = %v", err)
	}
	if len(payload.RouteModels) != 1 || payload.RouteModels[0].PublicModel != "gpt-5.4" {
		t.Fatalf("route_models = %+v, want one row gpt-5.4", payload.RouteModels)
	}
	if payload.RouteModels[0].ShadowModel != "gpt-5.4" {
		t.Fatalf("route_models[0].shadow_model = %q, want gpt-5.4", payload.RouteModels[0].ShadowModel)
	}
}

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

	createdGroup, err := actions.CreateLogicalGroup(ctx, CreateLogicalGroupRequest{
		LogicalGroupID:   "gpt-shared",
		DisplayName:      "GPT Shared",
		Status:           "active",
		UsageScenario:    "适合统一 GPT 产品入口",
		Recommendation:   "优先使用 gpt-5.4",
		NextStepHint:     "先创建测试 Key",
		VisibilityScope:  "login_required",
		PackageTier:      "pro",
		PurchaseCTALabel: "升级到 Pro",
		PurchaseCTAURL:   "https://sub.tksea.top/portal/upgrade/pro",
	})
	if err != nil {
		t.Fatalf("CreateLogicalGroup() error = %v", err)
	}
	if createdGroup.LogicalGroupID != "gpt-shared" {
		t.Fatalf("CreateLogicalGroup() = %+v, want logical_group_id gpt-shared", createdGroup)
	}
	if createdGroup.UsageScenario != "适合统一 GPT 产品入口" || createdGroup.Recommendation != "优先使用 gpt-5.4" || createdGroup.NextStepHint != "先创建测试 Key" {
		t.Fatalf("CreateLogicalGroup() guidance = %+v, want configured guidance fields", createdGroup)
	}
	if createdGroup.VisibilityScope != "login_required" || createdGroup.PackageTier != "pro" || createdGroup.PurchaseCTALabel != "升级到 Pro" || createdGroup.PurchaseCTAURL != "https://sub.tksea.top/portal/upgrade/pro" {
		t.Fatalf("CreateLogicalGroup() packaging = %+v, want configured packaging fields", createdGroup)
	}

	if _, err := actions.CreateLogicalGroupModel(ctx, CreateLogicalGroupModelRequest{
		LogicalGroupID: "gpt-shared",
		PublicModel:    "gpt-5.4",
	}); err != nil {
		t.Fatalf("CreateLogicalGroupModel() error = %v", err)
	}

	if _, err := actions.CreateLogicalGroupRoute(ctx, CreateLogicalGroupRouteRequest{
		LogicalGroupID: "gpt-shared",
		RouteID:        "asxs",
		Name:           "ASXS",
		Status:         "active",
		Priority:       10,
		ShadowGroupID:  "gpt-shared__asxs",
		ShadowHostID:   "remote43",
	}); err != nil {
		t.Fatalf("CreateLogicalGroupRoute() error = %v", err)
	}

	if _, err := actions.CreateLogicalGroupRouteModel(ctx, CreateLogicalGroupRouteModelRequest{
		LogicalGroupID: "gpt-shared",
		RouteID:        "asxs",
		PublicModel:    "gpt-5.4",
	}); err != nil {
		t.Fatalf("CreateLogicalGroupRouteModel() error = %v", err)
	}

	group, err := actions.GetLogicalGroup(ctx, "gpt-shared")
	if err != nil {
		t.Fatalf("GetLogicalGroup() error = %v", err)
	}
	if len(group.Models) != 1 || group.Models[0].PublicModel != "gpt-5.4" {
		t.Fatalf("GetLogicalGroup().Models = %+v, want gpt-5.4", group.Models)
	}
	if len(group.Routes) != 1 || group.Routes[0].RouteID != "asxs" {
		t.Fatalf("GetLogicalGroup().Routes = %+v, want route asxs", group.Routes)
	}
	if len(group.Routes[0].Models) != 1 || group.Routes[0].Models[0].ShadowModel != "gpt-5.4" {
		t.Fatalf("GetLogicalGroup().Routes[0].Models = %+v, want shadow gpt-5.4", group.Routes[0].Models)
	}
	if group.UsageScenario != "适合统一 GPT 产品入口" || group.Recommendation != "优先使用 gpt-5.4" || group.NextStepHint != "先创建测试 Key" {
		t.Fatalf("GetLogicalGroup() guidance = %+v, want configured guidance fields", group)
	}
	if group.VisibilityScope != "login_required" || group.PackageTier != "pro" || group.PurchaseCTALabel != "升级到 Pro" || group.PurchaseCTAURL != "https://sub.tksea.top/portal/upgrade/pro" {
		t.Fatalf("GetLogicalGroup() packaging = %+v, want configured packaging fields", group)
	}

	if _, err := actions.UpdateLogicalGroup(ctx, UpdateLogicalGroupRequest{
		LogicalGroupID:   "gpt-shared",
		DisplayName:      "GPT Shared Updated",
		Status:           "paused",
		UsageScenario:    "适合升级后的 GPT 产品入口",
		Recommendation:   "先验证高质量推理链路",
		NextStepHint:     "升级后重新申请测试 Key",
		VisibilityScope:  "entitled_only",
		PackageTier:      "enterprise",
		PurchaseCTALabel: "联系销售升级",
		PurchaseCTAURL:   "https://sub.tksea.top/portal/contact-sales",
	}); err != nil {
		t.Fatalf("UpdateLogicalGroup() error = %v", err)
	}
	if _, err := actions.UpdateLogicalGroupRoute(ctx, UpdateLogicalGroupRouteRequest{
		LogicalGroupID: "gpt-shared",
		RouteID:        "asxs",
		Name:           "ASXS Updated",
		Status:         "degraded",
		Priority:       20,
		Weight:         80,
		ShadowGroupID:  "gpt-shared__asxs",
		ShadowHostID:   "remote43",
		CooldownUntil:  "2026-05-28T16:00:00Z",
	}); err != nil {
		t.Fatalf("UpdateLogicalGroupRoute() error = %v", err)
	}

	groups, err := actions.ListLogicalGroups(ctx)
	if err != nil {
		t.Fatalf("ListLogicalGroups() error = %v", err)
	}
	if len(groups) != 1 || groups[0].DisplayName != "GPT Shared Updated" {
		t.Fatalf("ListLogicalGroups() = %+v, want updated group", groups)
	}
	if groups[0].UsageScenario != "适合升级后的 GPT 产品入口" || groups[0].Recommendation != "先验证高质量推理链路" || groups[0].NextStepHint != "升级后重新申请测试 Key" {
		t.Fatalf("ListLogicalGroups() guidance = %+v, want updated guidance fields", groups[0])
	}
	if groups[0].VisibilityScope != "entitled_only" || groups[0].PackageTier != "enterprise" || groups[0].PurchaseCTALabel != "联系销售升级" || groups[0].PurchaseCTAURL != "https://sub.tksea.top/portal/contact-sales" {
		t.Fatalf("ListLogicalGroups() packaging = %+v, want updated packaging fields", groups[0])
	}

	routeModels, err := actions.ListLogicalGroupRouteModels(ctx, ListLogicalGroupRouteModelsRequest{
		LogicalGroupID: "gpt-shared",
		RouteID:        "asxs",
	})
	if err != nil {
		t.Fatalf("ListLogicalGroupRouteModels() error = %v", err)
	}
	if len(routeModels) != 1 || routeModels[0].PublicModel != "gpt-5.4" {
		t.Fatalf("ListLogicalGroupRouteModels() = %+v, want gpt-5.4", routeModels)
	}

	if err := actions.DeleteLogicalGroupRoute(ctx, DeleteLogicalGroupRouteRequest{
		LogicalGroupID: "gpt-shared",
		RouteID:        "asxs",
	}); err != nil {
		t.Fatalf("DeleteLogicalGroupRoute() error = %v", err)
	}
	if err := actions.DeleteLogicalGroupModel(ctx, DeleteLogicalGroupModelRequest{
		LogicalGroupID: "gpt-shared",
		PublicModel:    "gpt-5.4",
	}); err != nil {
		t.Fatalf("DeleteLogicalGroupModel() error = %v", err)
	}
	if err := actions.DeleteLogicalGroup(ctx, "gpt-shared"); err != nil {
		t.Fatalf("DeleteLogicalGroup() error = %v", err)
	}

	store, err := sqlite.Open(ctx, dsn)
	if err != nil {
		t.Fatalf("sqlite.Open() error = %v", err)
	}
	defer store.Close()
	remaining, err := store.LogicalGroups().List(ctx)
	if err != nil {
		t.Fatalf("LogicalGroups().List() error = %v", err)
	}
	if len(remaining) != 0 {
		t.Fatalf("remaining logical groups = %+v, want empty", remaining)
	}
}
