package provision

import (
	"context"
	"testing"

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

func TestReconcileServiceReturnsActiveAfterProbeRerun(t *testing.T) {
	store := openProvisionTestStore(t)
	defer closeProvisionTestStore(t, store)

	host := &fakeHostAdapter{
		batchAccounts: []sub2api.AccountRef{{ID: "account_1", Name: "deepseek-01"}, {ID: "account_2", Name: "deepseek-02"}},
		testResults: map[string]sub2api.ProbeResult{
			"account_1": {OK: true, Status: "passed"},
			"account_2": {OK: true, Status: "passed"},
		},
		models: map[string][]sub2api.AccountModel{
			"account_1": {{ID: "deepseek-chat"}},
			"account_2": {{ID: "deepseek-chat"}},
		},
		gatewayResult: sub2api.GatewayAccessResult{OK: true, StatusCode: 200, HasExpectedModel: true, Models: []string{"deepseek-chat"}},
	}

	batchID := seedRuntimeImportForReconcile(t, store, host)
	host.managedSnapshot = sub2api.ManagedResourceSnapshot{
		Groups:   []sub2api.NamedResource{{ID: "group_1", Name: "DeepSeek 默认分组-self-service"}},
		Channels: []sub2api.NamedResource{{ID: "channel_1", Name: "DeepSeek 默认渠道-self-service"}},
		Accounts: []sub2api.NamedResource{{ID: "account_1", Name: "deepseek-01"}, {ID: "account_2", Name: "deepseek-02"}},
	}

	result, err := NewReconcileService(store, host).Reconcile(context.Background(), ReconcileRequest{
		HostID:            "host-1",
		HostBaseURL:       "https://sub2api.example.com",
		AccessProbeAPIKey: "user-key",
		Pack:              pack.LoadedPack{Manifest: pack.Manifest{PackID: "openai-cn-pack", Version: "1.0.0", TargetHost: "sub2api", MinHostVersion: "0.1.126", MaxHostVersion: "0.2.x"}},
		Provider:          sampleProviderManifest(),
	})
	if err != nil {
		t.Fatalf("Reconcile() error = %v", err)
	}
	if result.BatchID != batchID {
		t.Fatalf("BatchID = %d, want %d", result.BatchID, batchID)
	}
	if result.Status != "active" {
		t.Fatalf("Status = %q, want active", result.Status)
	}
	if result.ProbeFailureCount != 0 {
		t.Fatalf("ProbeFailureCount = %d, want 0", result.ProbeFailureCount)
	}
	if result.AccessStatus != AccessStatusSelfServiceReady {
		t.Fatalf("AccessStatus = %q, want %q", result.AccessStatus, AccessStatusSelfServiceReady)
	}
	if got := queryCount(t, store.SQLDB(), "probe_results"); got != 4 {
		t.Fatalf("probe_results row count = %d, want 4 after rerun", got)
	}
	if got := queryCount(t, store.SQLDB(), "access_closure_records"); got != 2 {
		t.Fatalf("access_closure_records row count = %d, want 2 after rerun", got)
	}
}

func TestReconcileServiceReturnsDegradedWhenProbeRerunFails(t *testing.T) {
	store := openProvisionTestStore(t)
	defer closeProvisionTestStore(t, store)

	host := &fakeHostAdapter{
		batchAccounts: []sub2api.AccountRef{{ID: "account_1", Name: "deepseek-01"}, {ID: "account_2", Name: "deepseek-02"}},
		testResults: map[string]sub2api.ProbeResult{
			"account_1": {OK: true, Status: "passed"},
			"account_2": {OK: false, Status: "failed", Message: "bad key"},
		},
		models: map[string][]sub2api.AccountModel{
			"account_1": {{ID: "deepseek-chat"}},
			"account_2": {{ID: "deepseek-chat"}},
		},
		gatewayResult: sub2api.GatewayAccessResult{OK: true, StatusCode: 200, HasExpectedModel: true, Models: []string{"deepseek-chat"}},
	}

	seedRuntimeImportForReconcile(t, store, host)
	host.managedSnapshot = sub2api.ManagedResourceSnapshot{
		Groups:   []sub2api.NamedResource{{ID: "group_1", Name: "DeepSeek 默认分组-self-service"}},
		Channels: []sub2api.NamedResource{{ID: "channel_1", Name: "DeepSeek 默认渠道-self-service"}},
		Accounts: []sub2api.NamedResource{{ID: "account_1", Name: "deepseek-01"}, {ID: "account_2", Name: "deepseek-02"}},
	}

	result, err := NewReconcileService(store, host).Reconcile(context.Background(), ReconcileRequest{
		HostID:            "host-1",
		HostBaseURL:       "https://sub2api.example.com",
		AccessProbeAPIKey: "user-key",
		Pack:              pack.LoadedPack{Manifest: pack.Manifest{PackID: "openai-cn-pack", Version: "1.0.0", TargetHost: "sub2api", MinHostVersion: "0.1.126", MaxHostVersion: "0.2.x"}},
		Provider:          sampleProviderManifest(),
	})
	if err != nil {
		t.Fatalf("Reconcile() error = %v", err)
	}
	if result.Status != "degraded" {
		t.Fatalf("Status = %q, want degraded", result.Status)
	}
	if result.ProbeFailureCount != 1 {
		t.Fatalf("ProbeFailureCount = %d, want 1", result.ProbeFailureCount)
	}
}

func TestReconcileServiceIgnoresAdvisoryProbeFailureWhenModelsAndGatewayAreHealthy(t *testing.T) {
	store := openProvisionTestStore(t)
	defer closeProvisionTestStore(t, store)

	host := &fakeHostAdapter{
		batchAccounts: []sub2api.AccountRef{{ID: "account_1", Name: "deepseek-01"}, {ID: "account_2", Name: "deepseek-02"}},
		testResults: map[string]sub2api.ProbeResult{
			"account_1": {
				OK:      false,
				Status:  "failed",
				Message: "账号本身可正常使用，但当前测试接口仅支持 Responses API 路径。请直接通过实际 API 调用验证。",
			},
			"account_2": {
				OK:      false,
				Status:  "failed",
				Message: "账号本身可正常使用，但当前测试接口仅支持 Responses API 路径。请直接通过实际 API 调用验证。",
			},
		},
		models: map[string][]sub2api.AccountModel{
			"account_1": {{ID: "deepseek-chat"}},
			"account_2": {{ID: "deepseek-chat"}},
		},
		gatewayResult: sub2api.GatewayAccessResult{
			OK:               true,
			StatusCode:       200,
			HasExpectedModel: true,
			Models:           []string{"deepseek-chat"},
			CompletionOK:     true,
			CompletionStatus: 200,
		},
	}

	seedRuntimeImportForReconcile(t, store, host)
	host.managedSnapshot = sub2api.ManagedResourceSnapshot{
		Groups:   []sub2api.NamedResource{{ID: "group_1", Name: "DeepSeek 默认分组-self-service"}},
		Channels: []sub2api.NamedResource{{ID: "channel_1", Name: "DeepSeek 默认渠道-self-service"}},
		Accounts: []sub2api.NamedResource{{ID: "account_1", Name: "deepseek-01"}, {ID: "account_2", Name: "deepseek-02"}},
	}

	result, err := NewReconcileService(store, host).Reconcile(context.Background(), ReconcileRequest{
		HostID:            "host-1",
		HostBaseURL:       "https://sub2api.example.com",
		AccessProbeAPIKey: "user-key",
		Pack:              pack.LoadedPack{Manifest: pack.Manifest{PackID: "openai-cn-pack", Version: "1.0.0", TargetHost: "sub2api", MinHostVersion: "0.1.126", MaxHostVersion: "0.2.x"}},
		Provider:          sampleProviderManifest(),
	})
	if err != nil {
		t.Fatalf("Reconcile() error = %v", err)
	}
	if result.Status != "active" {
		t.Fatalf("Status = %q, want active", result.Status)
	}
	if result.ProbeFailureCount != 0 {
		t.Fatalf("ProbeFailureCount = %d, want 0", result.ProbeFailureCount)
	}
}

func TestReconcileServiceRepairsOpenAIResponsesCapabilityMismatch(t *testing.T) {
	store := openProvisionTestStore(t)
	defer closeProvisionTestStore(t, store)

	host := &fakeHostAdapter{
		batchAccounts: []sub2api.AccountRef{{ID: "account_1", Name: "deepseek-01"}, {ID: "account_2", Name: "deepseek-02"}},
		testResults: map[string]sub2api.ProbeResult{
			"account_1": {OK: false, Status: "failed", Message: "API returned 403: Forbidden"},
			"account_2": {OK: false, Status: "failed", Message: "API returned 403: Forbidden"},
		},
		models: map[string][]sub2api.AccountModel{
			"account_1": {{ID: "deepseek-chat"}},
			"account_2": {{ID: "deepseek-chat"}},
		},
		gatewayResult: sub2api.GatewayAccessResult{OK: true, StatusCode: 200, HasExpectedModel: true, Models: []string{"deepseek-chat"}},
		completionResults: []sub2api.GatewayCompletionResult{
			{OK: false, StatusCode: 502, ContentType: "application/json", BodyPreview: `{"error":{"message":"Upstream service temporarily unavailable","type":"upstream_error"}}`},
		},
		completionAfterRepair: &sub2api.GatewayCompletionResult{OK: true, StatusCode: 200, ContentType: "application/json"},
	}

	seedRuntimeImportForReconcile(t, store, host)
	host.managedSnapshot = sub2api.ManagedResourceSnapshot{
		Groups:   []sub2api.NamedResource{{ID: "group_1", Name: "DeepSeek 默认分组-self-service"}},
		Channels: []sub2api.NamedResource{{ID: "channel_1", Name: "DeepSeek 默认渠道-self-service"}},
		Accounts: []sub2api.NamedResource{{ID: "account_1", Name: "deepseek-01"}, {ID: "account_2", Name: "deepseek-02"}},
	}

	result, err := NewReconcileService(store, host).Reconcile(context.Background(), ReconcileRequest{
		HostID:            "host-1",
		HostBaseURL:       "https://sub2api.example.com",
		AccessProbeAPIKey: "user-key",
		Pack:              pack.LoadedPack{Manifest: pack.Manifest{PackID: "openai-cn-pack", Version: "1.0.0", TargetHost: "sub2api", MinHostVersion: "0.1.126", MaxHostVersion: "0.2.x"}},
		Provider:          sampleProviderManifest(),
	})
	if err != nil {
		t.Fatalf("Reconcile() error = %v", err)
	}
	if result.Status != "active" {
		t.Fatalf("Status = %q, want active after repair", result.Status)
	}
	if host.disableResponsesCalls != 1 {
		t.Fatalf("disable responses calls = %d, want 1", host.disableResponsesCalls)
	}
	if len(host.disabledResponsesAccountIDs) != 2 {
		t.Fatalf("disabled responses account ids = %v, want both accounts", host.disabledResponsesAccountIDs)
	}
	if host.clearTempUnschedulableCalls != 1 {
		t.Fatalf("clear temp unschedulable calls = %d, want 1", host.clearTempUnschedulableCalls)
	}
}

func TestReconcileServiceReturnsDriftedWhenManagedResourceMissing(t *testing.T) {
	store := openProvisionTestStore(t)
	defer closeProvisionTestStore(t, store)

	host := &fakeHostAdapter{
		batchAccounts: []sub2api.AccountRef{{ID: "account_1", Name: "deepseek-01"}, {ID: "account_2", Name: "deepseek-02"}},
		testResults: map[string]sub2api.ProbeResult{
			"account_1": {OK: true, Status: "passed"},
			"account_2": {OK: true, Status: "passed"},
		},
		models: map[string][]sub2api.AccountModel{
			"account_1": {{ID: "deepseek-chat"}},
			"account_2": {{ID: "deepseek-chat"}},
		},
		gatewayResult: sub2api.GatewayAccessResult{OK: true, StatusCode: 200, HasExpectedModel: true, Models: []string{"deepseek-chat"}},
	}

	seedRuntimeImportForReconcile(t, store, host)
	host.managedSnapshot = sub2api.ManagedResourceSnapshot{
		Groups:   []sub2api.NamedResource{{ID: "group_1", Name: "DeepSeek 默认分组-self-service"}},
		Channels: []sub2api.NamedResource{{ID: "channel_1", Name: "DeepSeek 默认渠道-self-service"}},
		Accounts: []sub2api.NamedResource{{ID: "account_1", Name: "deepseek-01"}},
	}

	result, err := NewReconcileService(store, host).Reconcile(context.Background(), ReconcileRequest{
		HostID:            "host-1",
		HostBaseURL:       "https://sub2api.example.com",
		AccessProbeAPIKey: "user-key",
		Pack:              pack.LoadedPack{Manifest: pack.Manifest{PackID: "openai-cn-pack", Version: "1.0.0", TargetHost: "sub2api", MinHostVersion: "0.1.126", MaxHostVersion: "0.2.x"}},
		Provider:          sampleProviderManifest(),
	})
	if err != nil {
		t.Fatalf("Reconcile() error = %v", err)
	}
	if result.Status != "drifted" {
		t.Fatalf("Status = %q, want drifted", result.Status)
	}
	if result.MissingCount != 1 {
		t.Fatalf("MissingCount = %d, want 1", result.MissingCount)
	}
}

func TestReconcileServiceIgnoresSubscriptionPlanForSelfServiceBatch(t *testing.T) {
	store := openProvisionTestStore(t)
	defer closeProvisionTestStore(t, store)

	host := &fakeHostAdapter{
		batchAccounts: []sub2api.AccountRef{{ID: "account_1", Name: "deepseek-01"}, {ID: "account_2", Name: "deepseek-02"}},
		testResults: map[string]sub2api.ProbeResult{
			"account_1": {OK: true, Status: "passed"},
			"account_2": {OK: true, Status: "passed"},
		},
		models: map[string][]sub2api.AccountModel{
			"account_1": {{ID: "deepseek-chat"}},
			"account_2": {{ID: "deepseek-chat"}},
		},
		gatewayResult: sub2api.GatewayAccessResult{OK: true, StatusCode: 200, HasExpectedModel: true, Models: []string{"deepseek-chat"}},
	}

	seedRuntimeImportForReconcile(t, store, host)
	host.managedSnapshot = sub2api.ManagedResourceSnapshot{
		Groups:   []sub2api.NamedResource{{ID: "group_1", Name: "DeepSeek 默认分组-self-service"}},
		Channels: []sub2api.NamedResource{{ID: "channel_1", Name: "DeepSeek 默认渠道-self-service"}},
		Plans:    []sub2api.NamedResource{{ID: "plan_1", Name: "DeepSeek 默认套餐"}},
		Accounts: []sub2api.NamedResource{{ID: "account_1", Name: "deepseek-01"}, {ID: "account_2", Name: "deepseek-02"}},
	}

	result, err := NewReconcileService(store, host).Reconcile(context.Background(), ReconcileRequest{
		HostID:            "host-1",
		HostBaseURL:       "https://sub2api.example.com",
		AccessProbeAPIKey: "user-key",
		Pack:              pack.LoadedPack{Manifest: pack.Manifest{PackID: "openai-cn-pack", Version: "1.0.0", TargetHost: "sub2api", MinHostVersion: "0.1.126", MaxHostVersion: "0.2.x"}},
		Provider:          sampleProviderManifest(),
	})
	if err != nil {
		t.Fatalf("Reconcile() error = %v", err)
	}
	if result.Status != "active" {
		t.Fatalf("Status = %q, want active", result.Status)
	}
	if result.ExtraCount != 0 {
		t.Fatalf("ExtraCount = %d, want 0", result.ExtraCount)
	}
	if host.listManagedReq.PlanName != "" {
		t.Fatalf("PlanName = %q, want empty for self_service reconcile", host.listManagedReq.PlanName)
	}
}

func TestReconcileServicePassesAccountNamePrefixToManagedResourceSnapshot(t *testing.T) {
	store := openProvisionTestStore(t)
	defer closeProvisionTestStore(t, store)

	host := &fakeHostAdapter{
		batchAccounts: []sub2api.AccountRef{{ID: "account_1", Name: "deepseek-01"}, {ID: "account_2", Name: "deepseek-02"}},
		testResults: map[string]sub2api.ProbeResult{
			"account_1": {OK: true, Status: "passed"},
			"account_2": {OK: true, Status: "passed"},
		},
		models: map[string][]sub2api.AccountModel{
			"account_1": {{ID: "deepseek-chat"}},
			"account_2": {{ID: "deepseek-chat"}},
		},
		gatewayResult: sub2api.GatewayAccessResult{OK: true, StatusCode: 200, HasExpectedModel: true, Models: []string{"deepseek-chat"}},
	}

	seedRuntimeImportForReconcile(t, store, host)
	host.managedSnapshot = sub2api.ManagedResourceSnapshot{
		Groups:   []sub2api.NamedResource{{ID: "group_1", Name: "DeepSeek 默认分组-self-service"}},
		Channels: []sub2api.NamedResource{{ID: "channel_1", Name: "DeepSeek 默认渠道-self-service"}},
		Accounts: []sub2api.NamedResource{{ID: "account_1", Name: "deepseek-01"}, {ID: "account_2", Name: "deepseek-02"}},
	}

	provider := sampleProviderManifest()
	if _, err := NewReconcileService(store, host).Reconcile(context.Background(), ReconcileRequest{
		HostID:            "host-1",
		HostBaseURL:       "https://sub2api.example.com",
		AccessProbeAPIKey: "user-key",
		Pack:              pack.LoadedPack{Manifest: pack.Manifest{PackID: "openai-cn-pack", Version: "1.0.0", TargetHost: "sub2api", MinHostVersion: "0.1.126", MaxHostVersion: "0.2.x"}},
		Provider:          provider,
	}); err != nil {
		t.Fatalf("Reconcile() error = %v", err)
	}
	if host.listManagedReq.AccountNamePrefix != "deepseek-" {
		t.Fatalf("AccountNamePrefix = %q, want %q", host.listManagedReq.AccountNamePrefix, "deepseek-")
	}
}

func TestReconcileServiceIgnoresSharedResourceReuseAcrossBatches(t *testing.T) {
	store := openProvisionTestStore(t)
	defer closeProvisionTestStore(t, store)

	host := &fakeHostAdapter{
		batchAccounts: []sub2api.AccountRef{{ID: "account_1", Name: "deepseek-01"}, {ID: "account_2", Name: "deepseek-02"}},
		testResults: map[string]sub2api.ProbeResult{
			"account_1": {OK: true, Status: "passed"},
			"account_2": {OK: true, Status: "passed"},
			"account_3": {OK: true, Status: "passed"},
			"account_4": {OK: true, Status: "passed"},
		},
		models: map[string][]sub2api.AccountModel{
			"account_1": {{ID: "deepseek-chat"}},
			"account_2": {{ID: "deepseek-chat"}},
			"account_3": {{ID: "deepseek-chat"}},
			"account_4": {{ID: "deepseek-chat"}},
		},
		gatewayResult: sub2api.GatewayAccessResult{OK: true, StatusCode: 200, HasExpectedModel: true, Models: []string{"deepseek-chat"}},
	}

	seedRuntimeImportForReconcile(t, store, host)
	ctx := context.Background()
	packRow, err := store.Packs().GetByPackID(ctx, "openai-cn-pack")
	if err != nil {
		t.Fatalf("Packs().GetByPackID() error = %v", err)
	}
	providerRow, err := store.Providers().GetByPackIDAndProviderID(ctx, packRow.ID, sampleProviderManifest().ProviderID)
	if err != nil {
		t.Fatalf("Providers().GetByPackIDAndProviderID() error = %v", err)
	}
	hostRow, err := store.Hosts().GetByHostID(ctx, "host-1")
	if err != nil {
		t.Fatalf("Hosts().GetByHostID() error = %v", err)
	}
	latestBatchID, err := store.ImportBatches().Create(ctx, sqlite.ImportBatch{HostID: hostRow.ID, PackID: packRow.ID, ProviderID: providerRow.ID, Mode: ImportModePartial, BatchStatus: BatchStatusSucceeded, AccessStatus: AccessStatusSelfServiceReady})
	if err != nil {
		t.Fatalf("ImportBatches().Create() error = %v", err)
	}
	if _, err := store.ImportBatchItems().Create(ctx, sqlite.ImportBatchItem{BatchID: latestBatchID, KeyFingerprint: "key-3", AccountStatus: "passed", ProbeSummaryJSON: `{"account_id":"account_3"}`}); err != nil {
		t.Fatalf("ImportBatchItems().Create() error = %v", err)
	}
	if _, err := store.ImportBatchItems().Create(ctx, sqlite.ImportBatchItem{BatchID: latestBatchID, KeyFingerprint: "key-4", AccountStatus: "passed", ProbeSummaryJSON: `{"account_id":"account_4"}`}); err != nil {
		t.Fatalf("ImportBatchItems().Create() error = %v", err)
	}
	if _, err := store.ManagedResources().Create(ctx, sqlite.ManagedResource{BatchID: latestBatchID, HostID: hostRow.ID, ResourceType: "account", HostResourceID: "account_3", ResourceName: "deepseek-03"}); err != nil {
		t.Fatalf("ManagedResources().Create(account_3) error = %v", err)
	}
	if _, err := store.ManagedResources().Create(ctx, sqlite.ManagedResource{BatchID: latestBatchID, HostID: hostRow.ID, ResourceType: "account", HostResourceID: "account_4", ResourceName: "deepseek-04"}); err != nil {
		t.Fatalf("ManagedResources().Create(account_4) error = %v", err)
	}
	if _, err := store.AccessClosures().Create(ctx, sqlite.AccessClosureRecord{BatchID: latestBatchID, ClosureType: AccessModeSelfService, Status: AccessStatusSelfServiceReady, DetailsJSON: "{}"}); err != nil {
		t.Fatalf("AccessClosures().Create() error = %v", err)
	}

	host.managedSnapshot = sub2api.ManagedResourceSnapshot{
		Groups:   []sub2api.NamedResource{{ID: "group_1", Name: "DeepSeek 默认分组-self-service"}},
		Channels: []sub2api.NamedResource{{ID: "channel_1", Name: "DeepSeek 默认渠道-self-service"}},
		Accounts: []sub2api.NamedResource{{ID: "account_3", Name: "deepseek-03"}, {ID: "account_4", Name: "deepseek-04"}},
	}

	result, err := NewReconcileService(store, host).Reconcile(ctx, ReconcileRequest{
		HostID:            "host-1",
		HostBaseURL:       "https://sub2api.example.com",
		AccessProbeAPIKey: "user-key",
		Pack:              pack.LoadedPack{Manifest: pack.Manifest{PackID: "openai-cn-pack", Version: "1.0.0", TargetHost: "sub2api", MinHostVersion: "0.1.126", MaxHostVersion: "0.2.x"}},
		Provider:          sampleProviderManifest(),
	})
	if err != nil {
		t.Fatalf("Reconcile() error = %v", err)
	}
	if result.BatchID != latestBatchID {
		t.Fatalf("BatchID = %d, want latest batch %d", result.BatchID, latestBatchID)
	}
	if result.Status != "active" {
		t.Fatalf("Status = %q, want active when only shared resources are reused", result.Status)
	}
	if result.MissingCount != 0 || result.ExtraCount != 0 {
		t.Fatalf("Managed resource diff = (%d, %d), want (0, 0)", result.MissingCount, result.ExtraCount)
	}
}

func TestReconcileServiceClassifiesHistoricalAccountsAsStaleNoise(t *testing.T) {
	store := openProvisionTestStore(t)
	defer closeProvisionTestStore(t, store)

	host := &fakeHostAdapter{
		testResults: map[string]sub2api.ProbeResult{
			"account_3": {OK: true, Status: "passed"},
			"account_4": {OK: true, Status: "passed"},
		},
		models: map[string][]sub2api.AccountModel{
			"account_3": {{ID: "deepseek-chat"}},
			"account_4": {{ID: "deepseek-chat"}},
		},
		gatewayResult: sub2api.GatewayAccessResult{OK: true, StatusCode: 200, HasExpectedModel: true, Models: []string{"deepseek-chat"}},
	}

	seedRuntimeImportForReconcile(t, store, host)
	ctx := context.Background()
	packRow, err := store.Packs().GetByPackID(ctx, "openai-cn-pack")
	if err != nil {
		t.Fatalf("Packs().GetByPackID() error = %v", err)
	}
	providerRow, err := store.Providers().GetByPackIDAndProviderID(ctx, packRow.ID, sampleProviderManifest().ProviderID)
	if err != nil {
		t.Fatalf("Providers().GetByPackIDAndProviderID() error = %v", err)
	}
	hostRow, err := store.Hosts().GetByHostID(ctx, "host-1")
	if err != nil {
		t.Fatalf("Hosts().GetByHostID() error = %v", err)
	}
	latestBatchID, err := store.ImportBatches().Create(ctx, sqlite.ImportBatch{HostID: hostRow.ID, PackID: packRow.ID, ProviderID: providerRow.ID, Mode: ImportModePartial, BatchStatus: BatchStatusSucceeded, AccessStatus: AccessStatusSelfServiceReady})
	if err != nil {
		t.Fatalf("ImportBatches().Create() error = %v", err)
	}
	for _, item := range []struct {
		keyFingerprint string
		accountID      string
		accountName    string
	}{
		{keyFingerprint: "key-3", accountID: "account_3", accountName: "deepseek-03"},
		{keyFingerprint: "key-4", accountID: "account_4", accountName: "deepseek-04"},
	} {
		if _, err := store.ImportBatchItems().Create(ctx, sqlite.ImportBatchItem{BatchID: latestBatchID, KeyFingerprint: item.keyFingerprint, AccountStatus: "passed", ProbeSummaryJSON: `{"account_id":"` + item.accountID + `"}`}); err != nil {
			t.Fatalf("ImportBatchItems().Create(%s) error = %v", item.accountID, err)
		}
		if _, err := store.ManagedResources().Create(ctx, sqlite.ManagedResource{BatchID: latestBatchID, HostID: hostRow.ID, ResourceType: "account", HostResourceID: item.accountID, ResourceName: item.accountName}); err != nil {
			t.Fatalf("ManagedResources().Create(%s) error = %v", item.accountID, err)
		}
	}
	if _, err := store.AccessClosures().Create(ctx, sqlite.AccessClosureRecord{BatchID: latestBatchID, ClosureType: AccessModeSelfService, Status: AccessStatusSelfServiceReady, DetailsJSON: "{}"}); err != nil {
		t.Fatalf("AccessClosures().Create() error = %v", err)
	}

	host.managedSnapshot = sub2api.ManagedResourceSnapshot{
		Groups:   []sub2api.NamedResource{{ID: "group_1", Name: "DeepSeek 默认分组-self-service"}},
		Channels: []sub2api.NamedResource{{ID: "channel_1", Name: "DeepSeek 默认渠道-self-service"}},
		Accounts: []sub2api.NamedResource{{ID: "account_1", Name: "deepseek-01"}, {ID: "account_2", Name: "deepseek-02"}, {ID: "account_3", Name: "deepseek-03"}, {ID: "account_4", Name: "deepseek-04"}},
	}

	result, err := NewReconcileService(store, host).Reconcile(ctx, ReconcileRequest{
		HostID:            "host-1",
		HostBaseURL:       "https://sub2api.example.com",
		AccessProbeAPIKey: "user-key",
		Pack:              pack.LoadedPack{Manifest: pack.Manifest{PackID: "openai-cn-pack", Version: "1.0.0", TargetHost: "sub2api", MinHostVersion: "0.1.126", MaxHostVersion: "0.2.x"}},
		Provider:          sampleProviderManifest(),
	})
	if err != nil {
		t.Fatalf("Reconcile() error = %v", err)
	}
	if result.BatchID != latestBatchID {
		t.Fatalf("BatchID = %d, want latest batch %d", result.BatchID, latestBatchID)
	}
	if result.Status != "active" {
		t.Fatalf("Status = %q, want active when only historical same-prefix accounts remain (missing=%d extra=%d stale=%d summary=%#v)", result.Status, result.MissingCount, result.ExtraCount, result.StaleNoiseCount, result.Summary)
	}
	if result.ExtraCount != 0 {
		t.Fatalf("ExtraCount = %d, want 0 after stale-noise classification", result.ExtraCount)
	}
	if result.StaleNoiseCount != 2 {
		t.Fatalf("StaleNoiseCount = %d, want 2", result.StaleNoiseCount)
	}
	if got, ok := result.Summary["stale_noise_count"].(int); !ok || got != 2 {
		t.Fatalf("Summary[stale_noise_count] = %#v, want int(2)", result.Summary["stale_noise_count"])
	}
	accounts, ok := result.Summary["stale_noise_accounts"].([]sub2api.NamedResource)
	if !ok {
		t.Fatalf("Summary[stale_noise_accounts] type = %T, want []sub2api.NamedResource", result.Summary["stale_noise_accounts"])
	}
	if len(accounts) != 2 || accounts[0].ID != "account_1" || accounts[1].ID != "account_2" {
		t.Fatalf("Summary[stale_noise_accounts] = %#v, want historical accounts 1 and 2", accounts)
	}
}

func TestReconcileServiceRejectsRolledBackLatestBatch(t *testing.T) {
	store := openProvisionTestStore(t)
	defer closeProvisionTestStore(t, store)

	host := &fakeHostAdapter{
		batchAccounts: []sub2api.AccountRef{{ID: "account_1", Name: "deepseek-01"}},
		testResults: map[string]sub2api.ProbeResult{
			"account_1": {OK: true, Status: "passed"},
		},
		models: map[string][]sub2api.AccountModel{
			"account_1": {{ID: "deepseek-chat"}},
		},
		gatewayResult: sub2api.GatewayAccessResult{OK: true, StatusCode: 200, HasExpectedModel: true, Models: []string{"deepseek-chat"}},
	}

	batchID := seedRuntimeImportForReconcile(t, store, host)
	if err := store.ImportBatches().UpdateStatus(context.Background(), batchID, BatchStatusRolledBack, AccessStatusBroken); err != nil {
		t.Fatalf("UpdateStatus() error = %v", err)
	}
	_, err := NewReconcileService(store, host).Reconcile(context.Background(), ReconcileRequest{
		HostID:            "host-1",
		HostBaseURL:       "https://sub2api.example.com",
		AccessProbeAPIKey: "user-key",
		Pack:              pack.LoadedPack{Manifest: pack.Manifest{PackID: "openai-cn-pack", Version: "1.0.0", TargetHost: "sub2api", MinHostVersion: "0.1.126", MaxHostVersion: "0.2.x"}},
		Provider:          sampleProviderManifest(),
	})
	if err == nil || err.Error() != "latest import batch is rolled_back; run import again before reconcile" {
		t.Fatalf("Reconcile() error = %v, want rolled_back guard", err)
	}
}

func TestDeriveHealthyAccessStatus(t *testing.T) {
	tests := []struct {
		name        string
		closureType string
		want        string
	}{
		{name: "subscription", closureType: AccessModeSubscription, want: AccessStatusSubscriptionReady},
		{name: "self-service", closureType: AccessModeSelfService, want: AccessStatusSelfServiceReady},
		{name: "unknown", closureType: "other", want: "unknown"},
	}
	for _, tc := range tests {
		t.Run(tc.name, func(t *testing.T) {
			if got := deriveHealthyAccessStatus(tc.closureType); got != tc.want {
				t.Fatalf("deriveHealthyAccessStatus(%q) = %q, want %q", tc.closureType, got, tc.want)
			}
		})
	}
}

func seedRuntimeImportForReconcile(t *testing.T, store *sqlite.DB, host *fakeHostAdapter) int64 {
	t.Helper()
	seedProvisionHost(t, store, "host-1", "https://sub2api.example.com")
	result, err := NewRuntimeImportService(store, host).Import(context.Background(), RuntimeImportRequest{
		HostID:      "host-1",
		HostBaseURL: "https://sub2api.example.com",
		Pack: pack.LoadedPack{
			Manifest: pack.Manifest{PackID: "openai-cn-pack", Version: "1.0.0", TargetHost: "sub2api", MinHostVersion: "0.1.126", MaxHostVersion: "0.2.x"},
			Checksum: "checksum-1",
		},
		Provider: sampleProviderManifest(),
		Mode:     ImportModePartial,
		Keys:     []string{"key-1", "key-2"},
		Access: AccessRequest{
			Mode:        AccessModeSelfService,
			ProbeAPIKey: "user-key",
		},
	})
	if err != nil {
		t.Fatalf("seed RuntimeImportService.Import() error = %v", err)
	}
	return result.BatchID
}
