package app

import (
	"context"
	"crypto/sha256"
	"encoding/json"
	"fmt"
	"net/http"
	"net/http/httptest"
	"strings"
	"testing"

	"sub2api-cn-relay-manager/internal/batch"
	"sub2api-cn-relay-manager/internal/pack"
	"sub2api-cn-relay-manager/internal/store/sqlite"
	"sub2api-cn-relay-manager/internal/testutil"
)

func TestBatchImportHTTP(t *testing.T) {
	t.Parallel()

	t.Run("POST create run returns run summary", func(t *testing.T) {
		t.Parallel()

		handler := NewAPIHandler("secret-token", ActionSet{
			CreateBatchImportRun: func(_ context.Context, req CreateBatchImportRunRequest) (BatchImportRunCreateResponse, error) {
				if req.HostID != "host-1" {
					t.Fatalf("HostID = %q, want host-1", req.HostID)
				}
				if req.AccessMode != "subscription" {
					t.Fatalf("AccessMode = %q, want subscription", req.AccessMode)
				}
				if len(req.SubscriptionUsers) != 1 || req.SubscriptionUsers[0] != "user-1" {
					t.Fatalf("SubscriptionUsers = %#v, want [user-1]", req.SubscriptionUsers)
				}
				if len(req.Entries) != 1 || req.Entries[0].BaseURL != "https://kimi.example.com/v1" {
					t.Fatalf("Entries = %#v, want request payload", req.Entries)
				}
				return BatchImportRunCreateResponse{
					RunID:         "run_20260522_0001",
					State:         "running",
					ResultPage:    "/batch-import/runs/run_20260522_0001",
					TotalItems:    1,
					ActiveItems:   0,
					DegradedItems: 0,
					BrokenItems:   0,
					WarningItems:  0,
				}, nil
			},
		})

		req := httptestRequest(t, http.MethodPost, "/api/batch-import/runs", map[string]any{
			"host_id":            "host-1",
			"mode":               "strict",
			"access_mode":        "subscription",
			"subscription_users": []string{"user-1"},
			"subscription_days":  30,
			"entries": []map[string]any{
				{"base_url": "https://kimi.example.com/v1", "api_key": "sk-test", "requested_models": []string{"kimi-k2.6"}},
			},
		}, "secret-token")
		res := httptestRecorder(handler, req)
		assertStatusCode(t, res, http.StatusOK)
		assertJSONContains(t, res.Body().Bytes(), "run_id", "run_20260522_0001")
		assertJSONContains(t, res.Body().Bytes(), "result_page", "/batch-import/runs/run_20260522_0001")
	})

	t.Run("subscription request requires subscription fields", func(t *testing.T) {
		t.Parallel()

		handler := NewAPIHandler("secret-token", ActionSet{
			CreateBatchImportRun: func(_ context.Context, req CreateBatchImportRunRequest) (BatchImportRunCreateResponse, error) {
				t.Fatal("CreateBatchImportRun should not be called when request is invalid")
				return BatchImportRunCreateResponse{}, nil
			},
		})

		req := httptestRequest(t, http.MethodPost, "/api/batch-import/runs", map[string]any{
			"host_id":     "host-1",
			"mode":        "strict",
			"access_mode": "subscription",
			"entries": []map[string]any{
				{"base_url": "https://kimi.example.com/v1", "api_key": "sk-test"},
			},
		}, "secret-token")
		res := httptestRecorder(handler, req)
		assertStatusCode(t, res, http.StatusBadRequest)
		assertJSONContains(t, res.Body().Bytes(), "error.code", "invalid_request")
	})

	t.Run("self service request requires probe api key", func(t *testing.T) {
		t.Parallel()

		handler := NewAPIHandler("secret-token", ActionSet{
			CreateBatchImportRun: func(_ context.Context, req CreateBatchImportRunRequest) (BatchImportRunCreateResponse, error) {
				t.Fatal("CreateBatchImportRun should not be called when request is invalid")
				return BatchImportRunCreateResponse{}, nil
			},
		})

		req := httptestRequest(t, http.MethodPost, "/api/batch-import/runs", map[string]any{
			"host_id":     "host-1",
			"mode":        "partial",
			"access_mode": "self_service",
			"entries": []map[string]any{
				{"base_url": "https://deepseek.example.com/v1", "api_key": "sk-test"},
			},
		}, "secret-token")
		res := httptestRecorder(handler, req)
		assertStatusCode(t, res, http.StatusBadRequest)
		assertJSONContains(t, res.Body().Bytes(), "error.code", "invalid_request")
		assertJSONContains(t, res.Body().Bytes(), "error.message", "probe_api_key is required when access_mode=self_service")
	})

	t.Run("create run requires host id", func(t *testing.T) {
		t.Parallel()

		handler := NewAPIHandler("secret-token", ActionSet{
			CreateBatchImportRun: func(_ context.Context, req CreateBatchImportRunRequest) (BatchImportRunCreateResponse, error) {
				t.Fatal("CreateBatchImportRun should not be called when host_id is missing")
				return BatchImportRunCreateResponse{}, nil
			},
		})

		req := httptestRequest(t, http.MethodPost, "/api/batch-import/runs", map[string]any{
			"mode":               "strict",
			"access_mode":        "subscription",
			"subscription_users": []string{"user-1"},
			"subscription_days":  30,
			"entries": []map[string]any{
				{"base_url": "https://kimi.example.com/v1", "api_key": "sk-test"},
			},
		}, "secret-token")
		res := httptestRecorder(handler, req)
		assertStatusCode(t, res, http.StatusBadRequest)
		assertJSONContains(t, res.Body().Bytes(), "error.code", "invalid_request")
		assertJSONContains(t, res.Body().Bytes(), "error.message", "host_id is required")
	})

	t.Run("create run action wires real batch pipeline", func(t *testing.T) {
		t.Parallel()

		server := httptest.NewServer(newBatchImportActionStubServer(t))
		defer server.Close()

		dsn := testutil.SQLiteTestDSN(t, "state.db", true)
		store := testutil.OpenSQLiteStore(t, dsn)
		defer closeAppTestStore(t, store)

		if _, err := store.Hosts().Create(context.Background(), sqlite.Host{
			HostID:              "host-1",
			BaseURL:             server.URL,
			HostVersion:         "0.1.126",
			CapabilityProbeJSON: "{}",
			AuthType:            "apikey",
			AuthToken:           "host-token",
		}); err != nil {
			t.Fatalf("Hosts().Create() error = %v", err)
		}

		action := buildCreateBatchImportRunAction(dsn)
		result, err := action(context.Background(), CreateBatchImportRunRequest{
			HostID:                "host-1",
			Mode:                  "strict",
			AccessMode:            "self_service",
			ConfirmWaitTimeoutSec: 1,
			ProbeAPIKey:           "gateway-key",
			Entries: []BatchImportEntryRequest{
				{
					BaseURL:         server.URL,
					APIKey:          "entry-key",
					RequestedModels: []string{"kimi-k2.6"},
				},
			},
		})
		if err != nil {
			t.Fatalf("buildCreateBatchImportRunAction() error = %v", err)
		}
		if result.State != string("completed") {
			t.Fatalf("result.State = %q, want completed", result.State)
		}
		if result.ActiveItems != 1 {
			t.Fatalf("result.ActiveItems = %d, want 1", result.ActiveItems)
		}

		run, err := store.ImportRuns().GetByRunID(context.Background(), result.RunID)
		if err != nil {
			t.Fatalf("ImportRuns().GetByRunID() error = %v", err)
		}
		if run.State != "completed" {
			t.Fatalf("run.State = %q, want completed", run.State)
		}
		items, err := store.ImportRunItems().ListByRunID(context.Background(), result.RunID)
		if err != nil {
			t.Fatalf("ImportRunItems().ListByRunID() error = %v", err)
		}
		if len(items) != 1 {
			t.Fatalf("len(items) = %d, want 1", len(items))
		}
		if items[0].CurrentStage != "done" || items[0].AccessStatus != "active" {
			t.Fatalf("item = %+v, want current_stage=done and access_status=active", items[0])
		}
	})

	t.Run("create run action reuses matched legacy account", func(t *testing.T) {
		t.Parallel()

		server := httptest.NewServer(newBatchImportActionStubServer(t))
		defer server.Close()

		dsn := testutil.SQLiteTestDSN(t, "reuse-state.db", true)
		store := testutil.OpenSQLiteStore(t, dsn)
		defer closeAppTestStore(t, store)

		hostPK := mustCreateBatchImportActionHost(t, store, server.URL)
		packPK, providerPK := mustSeedLegacyBatchImportProvider(t, store, server.URL)
		legacyBatchID := mustCreateLegacyReusableBatch(t, store, hostPK, packPK, providerPK, "entry-key", "account_1")

		action := buildCreateBatchImportRunAction(dsn)
		result, err := action(context.Background(), CreateBatchImportRunRequest{
			HostID:                "host-1",
			Mode:                  "strict",
			AccessMode:            "self_service",
			ConfirmWaitTimeoutSec: 1,
			ProbeAPIKey:           "gateway-key",
			Entries: []BatchImportEntryRequest{{
				BaseURL:         server.URL,
				APIKey:          "entry-key",
				RequestedModels: []string{"kimi-k2.6"},
			}},
		})
		if err != nil {
			t.Fatalf("buildCreateBatchImportRunAction() reuse error = %v", err)
		}
		if strings.TrimSpace(result.RunID) == "" {
			t.Fatalf("result.RunID = %q, want non-empty", result.RunID)
		}

		items, err := store.ImportRunItems().ListByRunID(context.Background(), result.RunID)
		if err != nil {
			t.Fatalf("ImportRunItems().ListByRunID() reuse error = %v", err)
		}
		if len(items) != 1 {
			t.Fatalf("len(items) = %d, want 1", len(items))
		}
		item := items[0]
		if !item.ProvisionReused || item.AccountResolution != "reused" || item.MatchedAccountState != "active" {
			t.Fatalf("reuse item = %+v, want provision_reused + reused + active", item)
		}
		if item.LegacyBatchID == nil || *item.LegacyBatchID != legacyBatchID {
			t.Fatalf("LegacyBatchID = %v, want %d", item.LegacyBatchID, legacyBatchID)
		}
		if item.CurrentStage != "done" || item.AccessStatus != "active" {
			t.Fatalf("reuse item final state = %+v, want done/active", item)
		}

		batches, err := store.ImportBatches().ListByProviderIDAndHostID(context.Background(), providerPK, hostPK)
		if err != nil {
			t.Fatalf("ImportBatches().ListByProviderIDAndHostID() error = %v", err)
		}
		if len(batches) != 1 {
			t.Fatalf("len(batches) = %d, want 1 legacy batch only", len(batches))
		}
	})

	t.Run("create run action reuses legacy account when pack provider id differs from normalized runtime id", func(t *testing.T) {
		t.Parallel()

		server := httptest.NewServer(newBatchImportActionStubServer(t))
		defer server.Close()

		dsn := testutil.SQLiteTestDSN(t, "reuse-baseurl-fallback.db", true)
		store := testutil.OpenSQLiteStore(t, dsn)
		defer closeAppTestStore(t, store)

		hostPK := mustCreateBatchImportActionHost(t, store, server.URL)
		packPK, providerPK := mustSeedLegacyBatchImportProviderWithID(t, store, server.URL, "legacy-pack-provider")
		legacyBatchID := mustCreateLegacyReusableBatch(t, store, hostPK, packPK, providerPK, "entry-key", "101")

		action := buildCreateBatchImportRunAction(dsn)
		result, err := action(context.Background(), CreateBatchImportRunRequest{
			HostID:                "host-1",
			Mode:                  "strict",
			AccessMode:            "self_service",
			ConfirmWaitTimeoutSec: 1,
			ProbeAPIKey:           "gateway-key",
			Entries: []BatchImportEntryRequest{{
				BaseURL:         server.URL,
				APIKey:          "entry-key",
				RequestedModels: []string{"kimi-k2.6"},
			}},
		})
		if err != nil {
			t.Fatalf("buildCreateBatchImportRunAction() base_url fallback reuse error = %v", err)
		}
		if strings.TrimSpace(result.RunID) == "" {
			t.Fatalf("result.RunID = %q, want non-empty", result.RunID)
		}

		items, err := store.ImportRunItems().ListByRunID(context.Background(), result.RunID)
		if err != nil {
			t.Fatalf("ImportRunItems().ListByRunID() base_url fallback error = %v", err)
		}
		if len(items) != 1 {
			t.Fatalf("len(items) = %d, want 1", len(items))
		}
		item := items[0]
		if !item.ProvisionReused || item.AccountResolution != "reused" || item.MatchedAccountState != "active" {
			t.Fatalf("reuse item = %+v, want provision_reused + reused + active", item)
		}
		if item.LegacyBatchID == nil || *item.LegacyBatchID != legacyBatchID {
			t.Fatalf("LegacyBatchID = %v, want %d", item.LegacyBatchID, legacyBatchID)
		}
		if item.CurrentStage != "done" || item.AccessStatus != "active" {
			t.Fatalf("reuse item final state = %+v, want done/active", item)
		}
	})
}

func TestBatchImportWrapperFunctions(t *testing.T) {
	t.Parallel()

	t.Run("handleCreateBatchImportRun requires action", func(t *testing.T) {
		t.Parallel()
		req := httptestRequest(t, http.MethodPost, "/api/batch-import/runs", map[string]any{}, "")
		rec := &responseRecorder{header: map[string][]string{}}
		handleCreateBatchImportRun(rec, req, nil)
		assertStatusCode(t, rec, http.StatusInternalServerError)
		assertJSONContains(t, rec.Body().Bytes(), "error.code", "server_misconfigured")
	})

	t.Run("handleCreateBatchImportRun classifies action error", func(t *testing.T) {
		t.Parallel()
		req := httptestRequest(t, http.MethodPost, "/api/batch-import/runs", map[string]any{
			"host_id":       "host-1",
			"mode":          "strict",
			"access_mode":   "self_service",
			"probe_api_key": "probe-key",
			"entries": []map[string]any{
				{"base_url": "https://kimi.example.com/v1", "api_key": "sk-test"},
			},
		}, "")
		rec := &responseRecorder{header: map[string][]string{}}
		handleCreateBatchImportRun(rec, req, func(context.Context, CreateBatchImportRunRequest) (BatchImportRunCreateResponse, error) {
			return BatchImportRunCreateResponse{}, fmt.Errorf("host x not found")
		})
		assertStatusCode(t, rec, http.StatusNotFound)
		assertJSONContains(t, rec.Body().Bytes(), "error.code", "not_found")
	})

	t.Run("handleListBatchImportRuns requires action", func(t *testing.T) {
		t.Parallel()
		req := httptestRequest(t, http.MethodGet, "/api/batch-import/runs", nil, "")
		rec := &responseRecorder{header: map[string][]string{}}
		handleListBatchImportRuns(rec, req, nil)
		assertStatusCode(t, rec, http.StatusInternalServerError)
		assertJSONContains(t, rec.Body().Bytes(), "error.code", "server_misconfigured")
	})

	t.Run("handleListBatchImportRuns returns empty array", func(t *testing.T) {
		t.Parallel()
		req := httptestRequest(t, http.MethodGet, "/api/batch-import/runs?limit=5", nil, "")
		rec := &responseRecorder{header: map[string][]string{}}
		handleListBatchImportRuns(rec, req, func(_ context.Context, got ListBatchImportRunsRequest) (ListBatchImportRunsResponse, error) {
			if got.Limit != 5 {
				t.Fatalf("ListBatchImportRunsRequest.Limit = %d, want 5", got.Limit)
			}
			return ListBatchImportRunsResponse{}, nil
		})
		assertStatusCode(t, rec, http.StatusOK)
		runs, ok := decodeTopLevelArray(t, rec.Body().Bytes(), "runs")
		if !ok || len(runs) != 0 {
			t.Fatalf("runs = %#v, want empty array", runs)
		}
	})
}

func TestBatchImportRejectsOversizedJSONBody(t *testing.T) {
	t.Parallel()

	handler := NewAPIHandler("secret-token", ActionSet{
		CreateBatchImportRun: func(_ context.Context, req CreateBatchImportRunRequest) (BatchImportRunCreateResponse, error) {
			t.Fatal("CreateBatchImportRun should not be called for oversized body")
			return BatchImportRunCreateResponse{}, nil
		},
	})

	payload := `{"host_id":"host-1","mode":"strict","access_mode":"self_service","probe_api_key":"probe-key","entries":[{"base_url":"https://kimi.example.com/v1","api_key":"` + strings.Repeat("x", int(maxJSONBodyBytes)) + `"}]}`
	req := httptest.NewRequest(http.MethodPost, "/api/batch-import/runs", strings.NewReader(payload))
	req.Header.Set("Authorization", "Bearer secret-token")
	res := httptestRecorder(handler, req)
	assertStatusCode(t, res, http.StatusRequestEntityTooLarge)
	assertJSONContains(t, res.Body().Bytes(), "error.code", "request_too_large")
}

func newBatchImportActionStubServer(t *testing.T) http.Handler {
	t.Helper()

	mux := http.NewServeMux()
	mux.HandleFunc("/api/v1/admin/system/version", func(w http.ResponseWriter, r *http.Request) {
		if !requireBatchImportActionAdminToken(t, w, r) {
			return
		}
		writeJSON(w, http.StatusOK, map[string]any{"data": map[string]any{"version": "0.1.126"}})
	})
	mux.HandleFunc("/api/v1/admin/groups", func(w http.ResponseWriter, r *http.Request) {
		if !requireBatchImportActionAdminToken(t, w, r) {
			return
		}
		switch r.Method {
		case http.MethodGet:
			writeJSON(w, http.StatusOK, map[string]any{"data": []map[string]any{}})
		case http.MethodPost:
			writeJSON(w, http.StatusCreated, map[string]any{"data": map[string]any{"id": "group_1", "name": "batch-import-group"}})
		default:
			http.NotFound(w, r)
		}
	})
	mux.HandleFunc("/api/v1/admin/channels", func(w http.ResponseWriter, r *http.Request) {
		if !requireBatchImportActionAdminToken(t, w, r) {
			return
		}
		switch r.Method {
		case http.MethodGet:
			writeJSON(w, http.StatusOK, map[string]any{"data": []map[string]any{}})
		case http.MethodPost:
			writeJSON(w, http.StatusCreated, map[string]any{"data": map[string]any{"id": "channel_1", "name": "batch-import-channel"}})
		default:
			http.NotFound(w, r)
		}
	})
	mux.HandleFunc("/api/v1/admin/payment/plans", func(w http.ResponseWriter, r *http.Request) {
		if !requireBatchImportActionAdminToken(t, w, r) {
			return
		}
		switch r.Method {
		case http.MethodGet:
			writeJSON(w, http.StatusOK, map[string]any{"data": []map[string]any{}})
		case http.MethodPost:
			writeJSON(w, http.StatusCreated, map[string]any{"data": map[string]any{"id": "plan_1", "name": "batch-import-plan"}})
		default:
			http.NotFound(w, r)
		}
	})
	mux.HandleFunc("/api/v1/admin/accounts", func(w http.ResponseWriter, r *http.Request) {
		if !requireBatchImportActionAdminToken(t, w, r) {
			return
		}
		writeJSON(w, http.StatusOK, map[string]any{"data": map[string]any{"items": []map[string]any{}, "pages": 1}})
	})
	mux.HandleFunc("/api/v1/admin/accounts/batch", func(w http.ResponseWriter, r *http.Request) {
		if !requireBatchImportActionAdminToken(t, w, r) {
			return
		}
		writeJSON(w, http.StatusOK, map[string]any{"data": []map[string]any{{"id": "account_1", "name": "batch-import-01"}}})
	})
	mux.HandleFunc("/api/v1/admin/accounts/__probe__/test", func(w http.ResponseWriter, r *http.Request) {
		if !requireBatchImportActionAdminToken(t, w, r) {
			return
		}
		writeJSON(w, http.StatusBadRequest, map[string]any{"error": "probe only"})
	})
	mux.HandleFunc("/api/v1/admin/accounts/__probe__/models", func(w http.ResponseWriter, r *http.Request) {
		if !requireBatchImportActionAdminToken(t, w, r) {
			return
		}
		writeJSON(w, http.StatusBadRequest, map[string]any{"error": "probe only"})
	})
	mux.HandleFunc("/api/v1/admin/accounts/account_1/test", func(w http.ResponseWriter, r *http.Request) {
		if !requireBatchImportActionAdminToken(t, w, r) {
			return
		}
		w.Header().Set("Content-Type", "text/event-stream")
		w.WriteHeader(http.StatusOK)
		_, _ = w.Write([]byte("event: result\n"))
		_, _ = w.Write([]byte("data: {\"status\":\"passed\",\"message\":\"smoke passed\",\"ok\":true}\n\n"))
	})
	mux.HandleFunc("/api/v1/admin/accounts/account_1/models", func(w http.ResponseWriter, r *http.Request) {
		if !requireBatchImportActionAdminToken(t, w, r) {
			return
		}
		writeJSON(w, http.StatusOK, map[string]any{"data": map[string]any{"items": []map[string]any{{"id": "kimi-k2.6", "display_name": "Kimi K2.6", "type": "chat"}}}})
	})
	mux.HandleFunc("/api/v1/admin/accounts/101/test", func(w http.ResponseWriter, r *http.Request) {
		if !requireBatchImportActionAdminToken(t, w, r) {
			return
		}
		w.Header().Set("Content-Type", "text/event-stream")
		w.WriteHeader(http.StatusOK)
		_, _ = w.Write([]byte("event: result\n"))
		_, _ = w.Write([]byte("data: {\"status\":\"passed\",\"message\":\"smoke passed\",\"ok\":true}\n\n"))
	})
	mux.HandleFunc("/api/v1/admin/accounts/101/models", func(w http.ResponseWriter, r *http.Request) {
		if !requireBatchImportActionAdminToken(t, w, r) {
			return
		}
		writeJSON(w, http.StatusOK, map[string]any{"data": map[string]any{"items": []map[string]any{{"id": "kimi-k2.6", "display_name": "Kimi K2.6", "type": "chat"}}}})
	})
	mux.HandleFunc("/api/v1/admin/subscriptions/assign", func(w http.ResponseWriter, r *http.Request) {
		if !requireBatchImportActionAdminToken(t, w, r) {
			return
		}
		switch r.Method {
		case http.MethodGet:
			writeJSON(w, http.StatusBadRequest, map[string]any{"error": "probe only"})
		case http.MethodPost:
			writeJSON(w, http.StatusOK, map[string]any{"data": map[string]any{"id": "subscription_1"}})
		default:
			http.NotFound(w, r)
		}
	})
	mux.HandleFunc("/v1/models", func(w http.ResponseWriter, r *http.Request) {
		switch strings.TrimSpace(r.Header.Get("Authorization")) {
		case "Bearer entry-key", "Bearer gateway-key":
			writeJSON(w, http.StatusOK, map[string]any{"data": []map[string]any{{"id": "kimi-k2.6"}}})
		default:
			w.WriteHeader(http.StatusUnauthorized)
			_, _ = w.Write([]byte(`{"error":"unauthorized"}`))
		}
	})
	mux.HandleFunc("/v1/responses", func(w http.ResponseWriter, r *http.Request) {
		if strings.TrimSpace(r.Header.Get("Authorization")) != "Bearer entry-key" {
			w.WriteHeader(http.StatusUnauthorized)
			_, _ = w.Write([]byte(`{"error":"unauthorized"}`))
			return
		}
		w.WriteHeader(http.StatusForbidden)
		_, _ = w.Write([]byte(`{"error":"responses unsupported"}`))
	})
	mux.HandleFunc("/v1/chat/completions", func(w http.ResponseWriter, r *http.Request) {
		switch strings.TrimSpace(r.Header.Get("Authorization")) {
		case "Bearer entry-key", "Bearer gateway-key":
			writeJSON(w, http.StatusOK, map[string]any{
				"id": "chatcmpl_batch_import",
				"choices": []map[string]any{{
					"index": 0,
					"message": map[string]any{
						"role":    "assistant",
						"content": "pong",
					},
				}},
			})
		default:
			w.WriteHeader(http.StatusUnauthorized)
			_, _ = w.Write([]byte(`{"error":"unauthorized"}`))
		}
	})

	return mux
}

func requireBatchImportActionAdminToken(t *testing.T, w http.ResponseWriter, r *http.Request) bool {
	t.Helper()
	if strings.TrimSpace(r.Header.Get("x-api-key")) != "host-token" {
		w.WriteHeader(http.StatusUnauthorized)
		_, _ = w.Write([]byte(`{"error":"unauthorized"}`))
		return false
	}
	return true
}

func mustCreateBatchImportActionHost(t *testing.T, store *sqlite.DB, baseURL string) int64 {
	t.Helper()

	hostPK, err := store.Hosts().Create(context.Background(), sqlite.Host{
		HostID:              "host-1",
		BaseURL:             baseURL,
		HostVersion:         "0.1.126",
		CapabilityProbeJSON: "{}",
		AuthType:            "apikey",
		AuthToken:           "host-token",
	})
	if err != nil {
		t.Fatalf("Hosts().Create() error = %v", err)
	}
	return hostPK
}

func mustSeedLegacyBatchImportProvider(t *testing.T, store *sqlite.DB, baseURL string) (int64, int64) {
	t.Helper()

	return mustSeedLegacyBatchImportProviderWithID(t, store, baseURL, batch.NormalizeProviderID(baseURL))
}

func mustSeedLegacyBatchImportProviderWithID(t *testing.T, store *sqlite.DB, baseURL, providerID string) (int64, int64) {
	t.Helper()

	packPK, err := store.Packs().Create(context.Background(), sqlite.Pack{
		PackID:       "seed-pack",
		Version:      "1.0.0",
		Checksum:     "seed-pack@1.0.0",
		Vendor:       "test",
		TargetHost:   "sub2api",
		ManifestJSON: `{"pack_id":"seed-pack","version":"1.0.0","target_host":"sub2api"}`,
	})
	if err != nil {
		t.Fatalf("Packs().Create() error = %v", err)
	}

	providerManifest := pack.ProviderManifest{
		ProviderID:     strings.TrimSpace(providerID),
		DisplayName:    "Legacy Reuse Provider",
		BaseURL:        baseURL,
		Platform:       "openai",
		AccountType:    "apikey",
		DefaultModels:  []string{"kimi-k2.6"},
		SmokeTestModel: "kimi-k2.6",
		GroupTemplate: pack.GroupTemplate{
			Name:           "legacy-group",
			RateMultiplier: 1,
		},
		ChannelTemplate: pack.ChannelTemplate{
			Name:         "legacy-channel",
			ModelMapping: map[string]string{"kimi-k2.6": "kimi-k2.6"},
		},
		PlanTemplate: pack.PlanTemplate{
			Name:         "legacy-plan",
			Price:        1,
			ValidityDays: 30,
			ValidityUnit: "day",
		},
		Import: pack.ImportOptions{
			SupportsMultiKey: true,
			SupportsStrict:   true,
			SupportsPartial:  true,
		},
	}
	manifestJSON, err := json.Marshal(providerManifest)
	if err != nil {
		t.Fatalf("json.Marshal(providerManifest) error = %v", err)
	}
	defaultModelsJSON, err := json.Marshal(providerManifest.DefaultModels)
	if err != nil {
		t.Fatalf("json.Marshal(defaultModels) error = %v", err)
	}
	channelTemplateJSON, err := json.Marshal(providerManifest.ChannelTemplate)
	if err != nil {
		t.Fatalf("json.Marshal(channelTemplate) error = %v", err)
	}

	providerPK, err := store.Providers().Create(context.Background(), sqlite.Provider{
		PackID:              packPK,
		ProviderID:          providerManifest.ProviderID,
		DisplayName:         providerManifest.DisplayName,
		BaseURL:             providerManifest.BaseURL,
		Platform:            providerManifest.Platform,
		AccountType:         providerManifest.AccountType,
		DefaultModelsJSON:   string(defaultModelsJSON),
		SmokeTestModel:      providerManifest.SmokeTestModel,
		ChannelTemplateJSON: string(channelTemplateJSON),
		ManifestJSON:        string(manifestJSON),
	})
	if err != nil {
		t.Fatalf("Providers().Create() error = %v", err)
	}
	return packPK, providerPK
}

func mustCreateLegacyReusableBatch(t *testing.T, store *sqlite.DB, hostPK int64, packPK int64, providerPK int64, apiKey string, accountID string) int64 {
	t.Helper()

	batchID, err := store.ImportBatches().Create(context.Background(), sqlite.ImportBatch{
		HostID:       hostPK,
		PackID:       packPK,
		ProviderID:   providerPK,
		Mode:         "strict",
		BatchStatus:  "succeeded",
		AccessStatus: "self_service_ready",
	})
	if err != nil {
		t.Fatalf("ImportBatches().Create() error = %v", err)
	}

	if _, err := store.ImportBatchItems().Create(context.Background(), sqlite.ImportBatchItem{
		BatchID:          batchID,
		KeyFingerprint:   fullSHA256Fingerprint(apiKey),
		AccountStatus:    "passed",
		ProbeSummaryJSON: fmt.Sprintf(`{"account_id":"%s"}`, accountID),
	}); err != nil {
		t.Fatalf("ImportBatchItems().Create() error = %v", err)
	}

	if _, err := store.ManagedResources().Create(context.Background(), sqlite.ManagedResource{
		BatchID:        batchID,
		HostID:         hostPK,
		ResourceType:   "account",
		HostResourceID: accountID,
		ResourceName:   "legacy-account",
	}); err != nil {
		t.Fatalf("ManagedResources().Create(account) error = %v", err)
	}

	return batchID
}

func fullSHA256Fingerprint(value string) string {
	sum := sha256.Sum256([]byte(strings.TrimSpace(value)))
	return fmt.Sprintf("sha256:%x", sum[:])
}
