package provision

import (
	"context"
	"errors"
	"fmt"
	"reflect"
	"strings"
	"testing"

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

func TestImportServiceImportSubscriptionFlow(t *testing.T) {
	host := &fakeHostAdapter{
		batchAccounts: []sub2api.AccountRef{{ID: "account_1"}, {ID: "account_2"}},
		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"}},
	}

	svc := NewImportService(host)
	report, err := svc.Import(context.Background(), ImportRequest{
		Provider: sampleProviderManifest(),
		Mode:     ImportModePartial,
		Access: AccessRequest{
			Mode:          AccessModeSubscription,
			ProbeAPIKey:   "user-key",
			Subscriptions: []SubscriptionTarget{{UserID: "user_1", DurationDays: 30}},
		},
		Keys: []string{" key-1 ", "key-2", "key-1"},
	})
	if err != nil {
		t.Fatalf("Import() error = %v", err)
	}

	if report.BatchStatus != BatchStatusSucceeded {
		t.Fatalf("BatchStatus = %q, want %q", report.BatchStatus, BatchStatusSucceeded)
	}
	if report.ProviderStatus != ProviderStatusActive {
		t.Fatalf("ProviderStatus = %q, want %q", report.ProviderStatus, ProviderStatusActive)
	}
	if report.AccessStatus != AccessStatusSubscriptionReady {
		t.Fatalf("AccessStatus = %q, want %q", report.AccessStatus, AccessStatusSubscriptionReady)
	}
	if !reflect.DeepEqual(report.AcceptedKeys, []string{"key-1", "key-2"}) {
		t.Fatalf("AcceptedKeys = %#v, want deduped normalized keys", report.AcceptedKeys)
	}
	if len(host.assignedSubscriptions) != 1 {
		t.Fatalf("assigned subscriptions = %d, want 1", len(host.assignedSubscriptions))
	}
	if host.createGroupReq.SubscriptionType != "subscription" {
		t.Fatalf("CreateGroup subscription_type = %q, want %q", host.createGroupReq.SubscriptionType, "subscription")
	}
	if host.createGroupReq.Platform != "openai" {
		t.Fatalf("CreateGroup platform = %q, want %q", host.createGroupReq.Platform, "openai")
	}
	if host.gatewayProbe.ExpectedModel != "deepseek-chat" {
		t.Fatalf("gateway probe model = %q, want %q", host.gatewayProbe.ExpectedModel, "deepseek-chat")
	}
	if host.testedModels["account_1"] != "deepseek-chat" || host.testedModels["account_2"] != "deepseek-chat" {
		t.Fatalf("testedModels = %#v, want deepseek-chat for all created accounts", host.testedModels)
	}
}

func TestImportServiceStrictModeFailsWhenAnyAccountProbeFails(t *testing.T) {
	host := &fakeHostAdapter{
		batchAccounts: []sub2api.AccountRef{{ID: "account_1"}, {ID: "account_2"}},
		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"}},
		},
	}

	svc := NewImportService(host)
	report, err := svc.Import(context.Background(), ImportRequest{
		Provider: sampleProviderManifest(),
		Mode:     ImportModeStrict,
		Access:   AccessRequest{Mode: AccessModeSelfService, ProbeAPIKey: "user-key"},
		Keys:     []string{"key-1", "key-2"},
	})
	if err == nil {
		t.Fatal("Import() error = nil, want strict mode failure")
	}
	if report.BatchStatus != BatchStatusFailed {
		t.Fatalf("BatchStatus = %q, want %q", report.BatchStatus, BatchStatusFailed)
	}
	if report.ProviderStatus != ProviderStatusFailed {
		t.Fatalf("ProviderStatus = %q, want %q", report.ProviderStatus, ProviderStatusFailed)
	}
}

func TestImportServiceRejectsUnknownMode(t *testing.T) {
	svc := NewImportService(&fakeHostAdapter{})
	_, err := svc.Import(context.Background(), ImportRequest{
		Provider: sampleProviderManifest(),
		Mode:     "unknown",
		Access:   AccessRequest{Mode: AccessModeSelfService},
		Keys:     []string{"key-1"},
	})
	if err == nil {
		t.Fatal("Import() error = nil, want mode validation error")
	}
}

func TestImportServiceMarksBrokenWhenCompletionSmokeFails(t *testing.T) {
	host := &fakeHostAdapter{
		batchAccounts: []sub2api.AccountRef{{ID: "account_1"}},
		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"}},
		completionResult: sub2api.GatewayCompletionResult{OK: false, StatusCode: 502, ContentType: "application/json", BodyPreview: `{"error":"upstream_error"}`},
	}

	report, err := NewImportService(host).Import(context.Background(), ImportRequest{
		Provider: sampleProviderManifest(),
		Mode:     ImportModePartial,
		Access:   AccessRequest{Mode: AccessModeSelfService, ProbeAPIKey: "user-key"},
		Keys:     []string{"key-1"},
	})
	if err != nil {
		t.Fatalf("Import() error = %v", err)
	}
	if report.BatchStatus != BatchStatusPartial {
		t.Fatalf("BatchStatus = %q, want %q", report.BatchStatus, BatchStatusPartial)
	}
	if report.ProviderStatus != ProviderStatusDegraded {
		t.Fatalf("ProviderStatus = %q, want %q", report.ProviderStatus, ProviderStatusDegraded)
	}
	if report.AccessStatus != AccessStatusBroken {
		t.Fatalf("AccessStatus = %q, want %q", report.AccessStatus, AccessStatusBroken)
	}
}

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

	report, err := NewImportService(host).Import(context.Background(), ImportRequest{
		Provider: sampleProviderManifest(),
		Mode:     ImportModePartial,
		Access:   AccessRequest{Mode: AccessModeSelfService, ProbeAPIKey: "user-key"},
		Keys:     []string{"key-1"},
	})
	if err != nil {
		t.Fatalf("Import() error = %v", err)
	}
	if report.BatchStatus != BatchStatusSucceeded {
		t.Fatalf("BatchStatus = %q, want %q", report.BatchStatus, BatchStatusSucceeded)
	}
	if report.ProviderStatus != ProviderStatusActive {
		t.Fatalf("ProviderStatus = %q, want %q", report.ProviderStatus, ProviderStatusActive)
	}
	if report.AccessStatus != AccessStatusSelfServiceReady {
		t.Fatalf("AccessStatus = %q, want %q", report.AccessStatus, AccessStatusSelfServiceReady)
	}
	if len(report.Accounts) != 1 {
		t.Fatalf("Accounts len = %d, want 1", len(report.Accounts))
	}
	if got := report.Accounts[0].ValidationStatus(); got != AccountStatusWarning {
		t.Fatalf("ValidationStatus = %q, want %q", got, AccountStatusWarning)
	}
}

func TestImportServiceTreatsTransientProbeFailureAsAdvisoryWhenGatewaySucceeds(t *testing.T) {
	host := &fakeHostAdapter{
		batchAccounts: []sub2api.AccountRef{{ID: "account_1", Name: "deepseek-01"}},
		testResults: map[string]sub2api.ProbeResult{
			"account_1": {
				OK:      false,
				Status:  "failed",
				Message: "API returned 429: {\"error\":{\"message\":\"Rate limited (429); user=1997/2000 model=49/50; daily_exhausted=False\",\"type\":\"rate_limit_error\",\"code\":\"rate_limit_exceeded\"}}",
			},
		},
		models: map[string][]sub2api.AccountModel{
			"account_1": {{ID: "deepseek-chat"}},
		},
		gatewayResult: sub2api.GatewayAccessResult{
			OK:               true,
			StatusCode:       200,
			HasExpectedModel: true,
			Models:           []string{"deepseek-chat"},
			CompletionOK:     true,
			CompletionStatus: 200,
			CompletionType:   "application/json",
		},
	}

	report, err := NewImportService(host).Import(context.Background(), ImportRequest{
		Provider: sampleProviderManifest(),
		Mode:     ImportModePartial,
		Access:   AccessRequest{Mode: AccessModeSelfService, ProbeAPIKey: "user-key"},
		Keys:     []string{"key-1"},
	})
	if err != nil {
		t.Fatalf("Import() error = %v", err)
	}
	if report.BatchStatus != BatchStatusSucceeded {
		t.Fatalf("BatchStatus = %q, want %q", report.BatchStatus, BatchStatusSucceeded)
	}
	if report.ProviderStatus != ProviderStatusActive {
		t.Fatalf("ProviderStatus = %q, want %q", report.ProviderStatus, ProviderStatusActive)
	}
	if report.AccessStatus != AccessStatusSelfServiceReady {
		t.Fatalf("AccessStatus = %q, want %q", report.AccessStatus, AccessStatusSelfServiceReady)
	}
	if got := report.Accounts[0].ValidationStatus(); got != AccountStatusWarning {
		t.Fatalf("ValidationStatus = %q, want %q", got, AccountStatusWarning)
	}
}

func TestImportServiceTreatsForbiddenProbeRaceAsAdvisoryWhenGatewaySucceeds(t *testing.T) {
	host := &fakeHostAdapter{
		batchAccounts: []sub2api.AccountRef{{ID: "account_1", Name: "kimi-a7m-01"}},
		testResults: map[string]sub2api.ProbeResult{
			"account_1": {
				OK:      false,
				Status:  "failed",
				Message: "API returned 403: Forbidden",
			},
		},
		models: map[string][]sub2api.AccountModel{
			"account_1": {{ID: "deepseek-chat"}},
		},
		gatewayResult: sub2api.GatewayAccessResult{
			OK:               true,
			StatusCode:       200,
			HasExpectedModel: true,
			Models:           []string{"deepseek-chat"},
			CompletionOK:     true,
			CompletionStatus: 200,
			CompletionType:   "application/json",
		},
	}

	report, err := NewImportService(host).Import(context.Background(), ImportRequest{
		Provider: sampleProviderManifest(),
		Mode:     ImportModePartial,
		Access:   AccessRequest{Mode: AccessModeSelfService, ProbeAPIKey: "user-key"},
		Keys:     []string{"key-1"},
	})
	if err != nil {
		t.Fatalf("Import() error = %v", err)
	}
	if report.BatchStatus != BatchStatusSucceeded {
		t.Fatalf("BatchStatus = %q, want %q", report.BatchStatus, BatchStatusSucceeded)
	}
	if report.ProviderStatus != ProviderStatusActive {
		t.Fatalf("ProviderStatus = %q, want %q", report.ProviderStatus, ProviderStatusActive)
	}
	if report.AccessStatus != AccessStatusSelfServiceReady {
		t.Fatalf("AccessStatus = %q, want %q", report.AccessStatus, AccessStatusSelfServiceReady)
	}
	if got := report.Accounts[0].ValidationStatus(); got != AccountStatusWarning {
		t.Fatalf("ValidationStatus = %q, want %q", got, AccountStatusWarning)
	}
}

func TestImportServiceTreatsBare404ProbeAsAdvisoryWhenGatewaySucceeds(t *testing.T) {
	host := &fakeHostAdapter{
		batchAccounts: []sub2api.AccountRef{{ID: "account_1", Name: "deepseek-01"}},
		testResults: map[string]sub2api.ProbeResult{
			"account_1": {
				OK:      false,
				Status:  "failed",
				Message: "API returned 404:",
			},
		},
		models: map[string][]sub2api.AccountModel{
			"account_1": {{ID: "deepseek-chat"}},
		},
		gatewayResult: sub2api.GatewayAccessResult{
			OK:               true,
			StatusCode:       200,
			HasExpectedModel: true,
			Models:           []string{"deepseek-chat"},
			CompletionOK:     true,
			CompletionStatus: 200,
			CompletionType:   "application/json",
		},
	}

	report, err := NewImportService(host).Import(context.Background(), ImportRequest{
		Provider: sampleProviderManifest(),
		Mode:     ImportModePartial,
		Access:   AccessRequest{Mode: AccessModeSelfService, ProbeAPIKey: "user-key"},
		Keys:     []string{"key-1"},
	})
	if err != nil {
		t.Fatalf("Import() error = %v", err)
	}
	if report.BatchStatus != BatchStatusSucceeded {
		t.Fatalf("BatchStatus = %q, want %q", report.BatchStatus, BatchStatusSucceeded)
	}
	if report.ProviderStatus != ProviderStatusActive {
		t.Fatalf("ProviderStatus = %q, want %q", report.ProviderStatus, ProviderStatusActive)
	}
	if report.AccessStatus != AccessStatusSelfServiceReady {
		t.Fatalf("AccessStatus = %q, want %q", report.AccessStatus, AccessStatusSelfServiceReady)
	}
	if got := report.Accounts[0].ValidationStatus(); got != AccountStatusWarning {
		t.Fatalf("ValidationStatus = %q, want %q", got, AccountStatusWarning)
	}
}

func TestImportServiceRetriesTransientGatewayCompletionFailure(t *testing.T) {
	host := &fakeHostAdapter{
		batchAccounts: []sub2api.AccountRef{{ID: "account_1", Name: "kimi-a7m-01"}},
		testResults: map[string]sub2api.ProbeResult{
			"account_1": {
				OK:      false,
				Status:  "failed",
				Message: "API returned 403: Forbidden",
			},
		},
		models: map[string][]sub2api.AccountModel{
			"account_1": {{ID: "deepseek-chat"}},
		},
		gatewayResult: sub2api.GatewayAccessResult{
			OK:               true,
			StatusCode:       200,
			HasExpectedModel: true,
			Models:           []string{"deepseek-chat"},
		},
		completionResults: []sub2api.GatewayCompletionResult{
			{OK: false, StatusCode: 503, ContentType: "application/json", BodyPreview: `{"error":{"message":"Service temporarily unavailable","type":"api_error"}}`},
			{OK: true, StatusCode: 200, ContentType: "application/json"},
		},
	}

	report, err := NewImportService(host).Import(context.Background(), ImportRequest{
		Provider: sampleProviderManifest(),
		Mode:     ImportModePartial,
		Access:   AccessRequest{Mode: AccessModeSelfService, ProbeAPIKey: "user-key"},
		Keys:     []string{"key-1"},
	})
	if err != nil {
		t.Fatalf("Import() error = %v", err)
	}
	if report.BatchStatus != BatchStatusSucceeded {
		t.Fatalf("BatchStatus = %q, want %q", report.BatchStatus, BatchStatusSucceeded)
	}
	if report.ProviderStatus != ProviderStatusActive {
		t.Fatalf("ProviderStatus = %q, want %q", report.ProviderStatus, ProviderStatusActive)
	}
	if report.AccessStatus != AccessStatusSelfServiceReady {
		t.Fatalf("AccessStatus = %q, want %q", report.AccessStatus, AccessStatusSelfServiceReady)
	}
	if !report.Gateway.CompletionOK || report.Gateway.CompletionStatus != 200 {
		t.Fatalf("Gateway completion = %+v, want retried success", report.Gateway)
	}
	if host.completionCalls != 2 {
		t.Fatalf("completion calls = %d, want 2", host.completionCalls)
	}
}

func TestImportServiceRepairsOpenAIResponsesCapabilityMismatchAfterInstall(t *testing.T) {
	host := &fakeHostAdapter{
		batchAccounts: []sub2api.AccountRef{{ID: "account_1", Name: "kimi-a7m-01"}},
		testResults: map[string]sub2api.ProbeResult{
			"account_1": {
				OK:      false,
				Status:  "failed",
				Message: "API returned 403: Forbidden",
			},
		},
		models: map[string][]sub2api.AccountModel{
			"account_1": {{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"},
	}

	report, err := NewImportService(host).Import(context.Background(), ImportRequest{
		Provider: sampleProviderManifest(),
		Mode:     ImportModePartial,
		Access:   AccessRequest{Mode: AccessModeSelfService, ProbeAPIKey: "user-key"},
		Keys:     []string{"key-1"},
	})
	if err != nil {
		t.Fatalf("Import() error = %v", err)
	}
	if !report.Gateway.CompletionOK || report.Gateway.CompletionStatus != 200 {
		t.Fatalf("Gateway completion = %+v, want repaired success", report.Gateway)
	}
	if host.disableResponsesCalls != 1 {
		t.Fatalf("disable responses calls = %d, want 1", host.disableResponsesCalls)
	}
	if len(host.disabledResponsesAccountIDs) != 1 || host.disabledResponsesAccountIDs[0] != "account_1" {
		t.Fatalf("disabled responses account ids = %v, want [account_1]", host.disabledResponsesAccountIDs)
	}
	if host.clearTempUnschedulableCalls != 1 {
		t.Fatalf("clear temp unschedulable calls = %d, want 1", host.clearTempUnschedulableCalls)
	}
	if len(host.clearedTempUnschedulableAccountIDs) != 1 || host.clearedTempUnschedulableAccountIDs[0] != "account_1" {
		t.Fatalf("cleared temp unschedulable account ids = %v, want [account_1]", host.clearedTempUnschedulableAccountIDs)
	}
}

func TestImportServicePreemptivelyDisablesResponsesForChatOnlyProvider(t *testing.T) {
	host := &fakeHostAdapter{
		batchAccounts: []sub2api.AccountRef{{ID: "account_1", Name: "kimi-a7m-01"}},
		testResults: map[string]sub2api.ProbeResult{
			"account_1": {OK: true, Status: "passed"},
		},
		models: map[string][]sub2api.AccountModel{
			"account_1": {{ID: "kimi-k2.6"}},
		},
		gatewayResult: sub2api.GatewayAccessResult{
			OK:               true,
			StatusCode:       200,
			HasExpectedModel: true,
			Models:           []string{"kimi-k2.6"},
			CompletionOK:     true,
			CompletionStatus: 200,
		},
	}

	report, err := NewImportService(host).Import(context.Background(), ImportRequest{
		Provider: sampleChatOnlyProviderManifest(),
		Mode:     ImportModePartial,
		Access:   AccessRequest{Mode: AccessModeSelfService, ProbeAPIKey: "user-key"},
		Keys:     []string{"key-1"},
	})
	if err != nil {
		t.Fatalf("Import() error = %v", err)
	}
	if report.BatchStatus != BatchStatusSucceeded {
		t.Fatalf("BatchStatus = %q, want %q", report.BatchStatus, BatchStatusSucceeded)
	}
	if host.disableResponsesCalls != 1 {
		t.Fatalf("disable responses calls = %d, want 1", host.disableResponsesCalls)
	}
	if len(host.disabledResponsesAccountIDs) != 1 || host.disabledResponsesAccountIDs[0] != "account_1" {
		t.Fatalf("disabled responses account ids = %v, want [account_1]", host.disabledResponsesAccountIDs)
	}
	if host.clearTempUnschedulableCalls != 1 {
		t.Fatalf("clear temp unschedulable calls = %d, want 1", host.clearTempUnschedulableCalls)
	}
	if got := host.callSequence; len(got) == 0 || got[0] != "disable_responses" {
		t.Fatalf("callSequence = %v, want preemptive capability repair before gateway validation", got)
	}
}

func TestImportServiceStrictModeRollsBackCreatedResources(t *testing.T) {
	host := &fakeHostAdapter{
		batchAccounts: []sub2api.AccountRef{{ID: "account_1"}, {ID: "account_2"}},
		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"}},
		},
	}

	svc := NewImportService(host)
	_, err := svc.Import(context.Background(), ImportRequest{
		Provider: sampleProviderManifest(),
		Mode:     ImportModeStrict,
		Access:   AccessRequest{Mode: AccessModeSelfService, ProbeAPIKey: "user-key"},
		Keys:     []string{"key-1", "key-2"},
	})
	if err == nil {
		t.Fatal("Import() error = nil, want strict mode failure")
	}

	want := []string{"account:account_2", "account:account_1", "channel:channel_1", "group:group_1"}
	if !reflect.DeepEqual(host.deletedResources, want) {
		t.Fatalf("deleted resources = %#v, want %#v", host.deletedResources, want)
	}
}

func TestImportReusesExistingGroup(t *testing.T) {
	host := &fakeHostAdapter{
		batchAccounts: []sub2api.AccountRef{{ID: "account_1", Name: "deepseek-01"}},
		testResults: map[string]sub2api.ProbeResult{
			"account_1": {OK: true, Status: "ready"},
		},
		models: map[string][]sub2api.AccountModel{
			"account_1": {{ID: "deepseek-chat"}},
		},
		gatewayResult: sub2api.GatewayAccessResult{OK: true, StatusCode: 200, HasExpectedModel: true, Models: []string{"deepseek-chat"}},
		managedSnapshot: sub2api.ManagedResourceSnapshot{
			Groups: []sub2api.NamedResource{{ID: "group_existing", Name: "DeepSeek 默认分组-self-service"}},
		},
	}

	svc := NewImportService(host)
	report, err := svc.Import(context.Background(), ImportRequest{
		Provider: sampleProviderManifest(),
		Mode:     ImportModePartial,
		Access:   AccessRequest{Mode: AccessModeSelfService, ProbeAPIKey: "user-key"},
		Keys:     []string{"key-1"},
	})
	if err != nil {
		t.Fatalf("Import() error = %v", err)
	}
	if report.Group.ID != "group_existing" {
		t.Fatalf("Group.ID = %q, want reused group_existing", report.Group.ID)
	}
	if host.createGroupCalls != 0 {
		t.Fatalf("CreateGroup() calls = %d, want 0 when group already exists", host.createGroupCalls)
	}
	if host.createChannelCalls != 1 {
		t.Fatalf("CreateChannel() calls = %d, want 1", host.createChannelCalls)
	}
}

func TestImportCreatesChannelWithManifestModelMapping(t *testing.T) {
	host := &fakeHostAdapter{
		batchAccounts: []sub2api.AccountRef{{ID: "account_1", Name: "deepseek-01"}},
		testResults: map[string]sub2api.ProbeResult{
			"account_1": {OK: true, Status: "ready"},
		},
		models: map[string][]sub2api.AccountModel{
			"account_1": {{ID: "deepseek-chat"}},
		},
		gatewayResult: sub2api.GatewayAccessResult{OK: true, StatusCode: 200, HasExpectedModel: true, Models: []string{"deepseek-chat"}},
	}

	_, err := NewImportService(host).Import(context.Background(), ImportRequest{
		Provider: sampleProviderManifest(),
		Mode:     ImportModePartial,
		Access:   AccessRequest{Mode: AccessModeSelfService, ProbeAPIKey: "user-key"},
		Keys:     []string{"key-1"},
	})
	if err != nil {
		t.Fatalf("Import() error = %v", err)
	}
	if host.createChannelReq.Name != "DeepSeek 默认渠道-self-service" {
		t.Fatalf("CreateChannel().Name = %q, want DeepSeek 默认渠道-self-service", host.createChannelReq.Name)
	}
	if len(host.createChannelReq.GroupIDs) != 1 || host.createChannelReq.GroupIDs[0] != "group_1" {
		t.Fatalf("CreateChannel().GroupIDs = %v, want [group_1]", host.createChannelReq.GroupIDs)
	}
	if got := host.createChannelReq.ModelMapping["deepseek-chat"]; got != "deepseek-chat" {
		t.Fatalf("CreateChannel().ModelMapping = %+v, want deepseek-chat passthrough", host.createChannelReq.ModelMapping)
	}
	if !host.createChannelReq.RestrictModels {
		t.Fatal("CreateChannel().RestrictModels = false, want true")
	}
	if host.createChannelReq.BillingModelSource != "channel_mapped" {
		t.Fatalf("CreateChannel().BillingModelSource = %q, want channel_mapped", host.createChannelReq.BillingModelSource)
	}
	if len(host.createChannelReq.ModelPricing) != 1 {
		t.Fatalf("CreateChannel().ModelPricing len = %d, want 1", len(host.createChannelReq.ModelPricing))
	}
	if len(host.createChannelReq.ModelPricing[0].Models) != 2 {
		t.Fatalf("CreateChannel().ModelPricing[0].Models = %v, want default model coverage", host.createChannelReq.ModelPricing[0].Models)
	}
	if host.createChannelReq.ModelPricing[0].BillingMode != "token" {
		t.Fatalf("CreateChannel().ModelPricing[0].BillingMode = %q, want token", host.createChannelReq.ModelPricing[0].BillingMode)
	}
	if len(host.batchCreateReq.Accounts) != 1 {
		t.Fatalf("BatchCreateAccounts().Accounts len = %d, want 1", len(host.batchCreateReq.Accounts))
	}
	credentials := host.batchCreateReq.Accounts[0].Credentials
	switch rawMapping := credentials["model_mapping"].(type) {
	case map[string]string:
		if got := rawMapping["deepseek-chat"]; got != "deepseek-chat" {
			t.Fatalf("BatchCreateAccounts().Credentials.model_mapping = %+v, want deepseek-chat passthrough", rawMapping)
		}
	case map[string]any:
		if got, _ := rawMapping["deepseek-chat"].(string); got != "deepseek-chat" {
			t.Fatalf("BatchCreateAccounts().Credentials.model_mapping = %+v, want deepseek-chat passthrough", rawMapping)
		}
	default:
		t.Fatalf("BatchCreateAccounts().Credentials = %+v, want model_mapping map", credentials)
	}
}

func sampleProviderManifest() pack.ProviderManifest {
	return pack.ProviderManifest{
		ProviderID:      "deepseek",
		DisplayName:     "DeepSeek OpenAI Compatible",
		BaseURL:         "https://api.deepseek.com",
		Platform:        "openai",
		AccountType:     "apikey",
		DefaultModels:   []string{"deepseek-chat", "deepseek-reasoner"},
		SmokeTestModel:  "deepseek-chat",
		GroupTemplate:   pack.GroupTemplate{Name: "DeepSeek 默认分组", RateMultiplier: 1},
		ChannelTemplate: pack.ChannelTemplate{Name: "DeepSeek 默认渠道", ModelMapping: map[string]string{"deepseek-chat": "deepseek-chat"}},
		PlanTemplate:    pack.PlanTemplate{Name: "DeepSeek 默认套餐", Price: 19.9, ValidityDays: 30, ValidityUnit: "day"},
	}
}

func sampleChatOnlyProviderManifest() pack.ProviderManifest {
	provider := sampleProviderManifest()
	provider.ProviderID = "kimi-a7m"
	provider.DisplayName = "Kimi A7M OpenAI Compatible"
	provider.BaseURL = "https://kimi.a7m.com.cn/v1"
	provider.DefaultModels = []string{"kimi-k2.6"}
	provider.SmokeTestModel = "kimi-k2.6"
	provider.ChannelTemplate = pack.ChannelTemplate{Name: "Kimi A7M 默认渠道", ModelMapping: map[string]string{"kimi-k2.6": "kimi-k2.6"}}
	provider.GroupTemplate = pack.GroupTemplate{Name: "Kimi A7M 默认分组", RateMultiplier: 1}
	provider.PlanTemplate = pack.PlanTemplate{Name: "Kimi A7M 默认套餐", Price: 19.9, ValidityDays: 30, ValidityUnit: "day"}
	provider.ForceDisableOpenAIResponsesAPI = true
	return provider
}

func TestImportServicePreemptiveResponsesRepairHappensBeforeAccountProbe(t *testing.T) {
	host := &fakeHostAdapter{
		batchAccounts: []sub2api.AccountRef{{ID: "account_1", Name: "kimi-a7m-01"}},
		testResults: map[string]sub2api.ProbeResult{
			"account_1": {OK: true, Status: "passed"},
		},
		models: map[string][]sub2api.AccountModel{
			"account_1": {{ID: "kimi-k2.6"}},
		},
		gatewayResult: sub2api.GatewayAccessResult{
			OK:               true,
			StatusCode:       200,
			HasExpectedModel: true,
			Models:           []string{"kimi-k2.6"},
			CompletionOK:     true,
			CompletionStatus: 200,
		},
	}

	report, err := NewImportService(host).Import(context.Background(), ImportRequest{
		Provider: sampleChatOnlyProviderManifest(),
		Mode:     ImportModePartial,
		Access:   AccessRequest{Mode: AccessModeSelfService, ProbeAPIKey: "user-key"},
		Keys:     []string{"key-1"},
	})
	if err != nil {
		t.Fatalf("Import() error = %v", err)
	}
	if report.BatchStatus != BatchStatusSucceeded {
		t.Fatalf("BatchStatus = %q, want %q", report.BatchStatus, BatchStatusSucceeded)
	}
	wantSequence := []string{"disable_responses", "clear_temp_unschedulable", "testAccount:account_1", "gateway", "completion"}
	if !reflect.DeepEqual(host.callSequence, wantSequence) {
		t.Fatalf("callSequence = %v, want %v", host.callSequence, wantSequence)
	}
	if host.disableResponsesCalls != 1 {
		t.Fatalf("disable responses calls = %d, want 1", host.disableResponsesCalls)
	}
	if len(host.disabledResponsesAccountIDs) != 1 || host.disabledResponsesAccountIDs[0] != "account_1" {
		t.Fatalf("disabled responses account ids = %v, want [account_1]", host.disabledResponsesAccountIDs)
	}
	if host.clearTempUnschedulableCalls != 1 {
		t.Fatalf("clear temp unschedulable calls = %d, want 1", host.clearTempUnschedulableCalls)
	}
}

func TestImportReconcilesExistingChannelConfiguration(t *testing.T) {
	host := &fakeHostAdapter{
		batchAccounts: []sub2api.AccountRef{{ID: "account_1", Name: "deepseek-01"}},
		testResults: map[string]sub2api.ProbeResult{
			"account_1": {OK: true, Status: "ready"},
		},
		models: map[string][]sub2api.AccountModel{
			"account_1": {{ID: "deepseek-chat"}},
		},
		gatewayResult: sub2api.GatewayAccessResult{OK: true, StatusCode: 200, HasExpectedModel: true, Models: []string{"deepseek-chat"}},
		managedSnapshot: sub2api.ManagedResourceSnapshot{
			Groups:   []sub2api.NamedResource{{ID: "group_existing", Name: "DeepSeek 默认分组-self-service"}},
			Channels: []sub2api.NamedResource{{ID: "channel_existing", Name: "DeepSeek 默认渠道-self-service"}},
		},
	}

	_, err := NewImportService(host).Import(context.Background(), ImportRequest{
		Provider: sampleProviderManifest(),
		Mode:     ImportModePartial,
		Access:   AccessRequest{Mode: AccessModeSelfService, ProbeAPIKey: "user-key"},
		Keys:     []string{"key-1"},
	})
	if err != nil {
		t.Fatalf("Import() error = %v", err)
	}
	if host.createChannelCalls != 0 {
		t.Fatalf("CreateChannel() calls = %d, want 0 when channel already exists", host.createChannelCalls)
	}
	if host.updateChannelCalls != 1 {
		t.Fatalf("UpdateChannel() calls = %d, want 1", host.updateChannelCalls)
	}
	if host.updateChannelID != "channel_existing" {
		t.Fatalf("UpdateChannel() id = %q, want channel_existing", host.updateChannelID)
	}
	if len(host.updateChannelReq.ModelPricing) != 1 {
		t.Fatalf("UpdateChannel().ModelPricing len = %d, want 1", len(host.updateChannelReq.ModelPricing))
	}
}

func TestImportDeletesExistingProviderAccountsBeforeGatewayClosure(t *testing.T) {
	host := &fakeHostAdapter{
		batchAccounts: []sub2api.AccountRef{{ID: "account_new_1", Name: "deepseek-01"}},
		testResults: map[string]sub2api.ProbeResult{
			"account_new_1": {OK: true, Status: "passed"},
		},
		models: map[string][]sub2api.AccountModel{
			"account_new_1": {{ID: "deepseek-chat"}},
		},
		gatewayResult: sub2api.GatewayAccessResult{OK: true, StatusCode: 200, HasExpectedModel: true, Models: []string{"deepseek-chat"}},
		managedSnapshot: sub2api.ManagedResourceSnapshot{
			Accounts: []sub2api.NamedResource{{ID: "account_old_1", Name: "deepseek-01"}, {ID: "account_old_2", Name: "deepseek-02"}},
		},
	}

	_, err := NewImportService(host).Import(context.Background(), ImportRequest{
		Provider: sampleProviderManifest(),
		Mode:     ImportModePartial,
		Access:   AccessRequest{Mode: AccessModeSelfService, ProbeAPIKey: "user-key"},
		Keys:     []string{"key-1"},
	})
	if err != nil {
		t.Fatalf("Import() error = %v", err)
	}
	if host.listManagedReq.AccountNamePrefix != "deepseek-" {
		t.Fatalf("AccountNamePrefix = %q, want %q", host.listManagedReq.AccountNamePrefix, "deepseek-")
	}
	wantDeleted := []string{"account:account_old_2", "account:account_old_1"}
	if !reflect.DeepEqual(host.deletedResources, wantDeleted) {
		t.Fatalf("deleted resources = %#v, want %#v", host.deletedResources, wantDeleted)
	}
	if !reflect.DeepEqual(host.callSequence, []string{"testAccount:account_new_1", "deleteAccount:account_old_2", "deleteAccount:account_old_1", "gateway", "completion"}) {
		t.Fatalf("call sequence = %#v, want stale-account cleanup before gateway probe", host.callSequence)
	}
}

func TestImportKeepsExistingAccountsWhenReplacementValidationFails(t *testing.T) {
	host := &fakeHostAdapter{
		batchAccounts: []sub2api.AccountRef{{ID: "account_new_1", Name: "deepseek-01"}},
		testResults: map[string]sub2api.ProbeResult{
			"account_new_1": {OK: false, Status: "failed", Message: "bad key"},
		},
		models: map[string][]sub2api.AccountModel{
			"account_new_1": {{ID: "deepseek-chat"}},
		},
		gatewayResult: sub2api.GatewayAccessResult{OK: true, StatusCode: 200, HasExpectedModel: true, Models: []string{"deepseek-chat"}},
		managedSnapshot: sub2api.ManagedResourceSnapshot{
			Accounts: []sub2api.NamedResource{{ID: "account_old_1", Name: "deepseek-01"}},
		},
	}

	report, err := NewImportService(host).Import(context.Background(), ImportRequest{
		Provider: sampleProviderManifest(),
		Mode:     ImportModePartial,
		Access:   AccessRequest{Mode: AccessModeSelfService, ProbeAPIKey: "user-key"},
		Keys:     []string{"key-1"},
	})
	if err != nil {
		t.Fatalf("Import() error = %v", err)
	}
	if report.BatchStatus != BatchStatusPartial {
		t.Fatalf("BatchStatus = %q, want %q", report.BatchStatus, BatchStatusPartial)
	}
	if len(host.deletedResources) != 0 {
		t.Fatalf("deleted resources = %#v, want no stale-account cleanup when replacement validation fails", host.deletedResources)
	}
}

type fakeHostAdapter struct {
	batchAccounts                      []sub2api.AccountRef
	batchCreateReq                     sub2api.BatchCreateAccountsRequest
	testResults                        map[string]sub2api.ProbeResult
	models                             map[string][]sub2api.AccountModel
	gatewayResult                      sub2api.GatewayAccessResult
	batchCreateErr                     error
	assignErr                          error
	gatewayErr                         error
	hostVersion                        string
	assignedSubscriptions              []sub2api.AssignSubscriptionRequest
	gatewayProbe                       sub2api.GatewayAccessCheckRequest
	completionProbe                    sub2api.GatewayCompletionCheckRequest
	deletedResources                   []string
	managedSnapshot                    sub2api.ManagedResourceSnapshot
	listManagedReq                     sub2api.ListManagedResourcesRequest
	createGroupCalls                   int
	createChannelCalls                 int
	updateChannelCalls                 int
	createPlanCalls                    int
	createGroupReq                     sub2api.CreateGroupRequest
	createChannelReq                   sub2api.CreateChannelRequest
	updateChannelID                    string
	updateChannelReq                   sub2api.CreateChannelRequest
	callSequence                       []string
	completionCalls                    int
	completionResults                  []sub2api.GatewayCompletionResult
	completionResult                   sub2api.GatewayCompletionResult
	completionAfterRepair              *sub2api.GatewayCompletionResult
	completionErr                      error
	testedModels                       map[string]string
	disableResponsesCalls              int
	disabledResponsesAccountIDs        []string
	clearTempUnschedulableCalls        int
	clearedTempUnschedulableAccountIDs []string
	deleteErrors                       map[string]error
}

func (f *fakeHostAdapter) GetHostVersion(context.Context) (string, error) {
	if strings.TrimSpace(f.hostVersion) == "" {
		return "0.1.126", nil
	}
	return f.hostVersion, nil
}
func (f *fakeHostAdapter) ProbeCapabilities(context.Context) (sub2api.HostCapabilities, error) {
	return sub2api.HostCapabilities{}, nil
}
func (f *fakeHostAdapter) CreateGroup(_ context.Context, req sub2api.CreateGroupRequest) (sub2api.GroupRef, error) {
	f.createGroupCalls++
	f.createGroupReq = req
	return sub2api.GroupRef{ID: "group_1", Name: "g"}, nil
}
func (f *fakeHostAdapter) DeleteGroup(_ context.Context, groupID string) error {
	if err := f.deleteErrors["group:"+groupID]; err != nil {
		return err
	}
	f.deletedResources = append(f.deletedResources, "group:"+groupID)
	return nil
}
func (f *fakeHostAdapter) CreateChannel(_ context.Context, req sub2api.CreateChannelRequest) (sub2api.ChannelRef, error) {
	f.createChannelCalls++
	f.createChannelReq = req
	return sub2api.ChannelRef{ID: "channel_1", Name: "c"}, nil
}
func (f *fakeHostAdapter) UpdateChannel(_ context.Context, channelID string, req sub2api.CreateChannelRequest) error {
	f.updateChannelCalls++
	f.updateChannelID = channelID
	f.updateChannelReq = req
	return nil
}
func (f *fakeHostAdapter) DeleteChannel(_ context.Context, channelID string) error {
	if err := f.deleteErrors["channel:"+channelID]; err != nil {
		return err
	}
	f.deletedResources = append(f.deletedResources, "channel:"+channelID)
	return nil
}
func (f *fakeHostAdapter) CreatePlan(context.Context, sub2api.CreatePlanRequest) (sub2api.PlanRef, error) {
	f.createPlanCalls++
	return sub2api.PlanRef{ID: "plan_1", Name: "p"}, nil
}
func (f *fakeHostAdapter) DeletePlan(_ context.Context, planID string) error {
	if err := f.deleteErrors["plan:"+planID]; err != nil {
		return err
	}
	f.deletedResources = append(f.deletedResources, "plan:"+planID)
	return nil
}
func (f *fakeHostAdapter) CreateAccount(context.Context, sub2api.CreateAccountRequest) (sub2api.AccountRef, error) {
	return sub2api.AccountRef{}, errors.New("unused")
}
func (f *fakeHostAdapter) BatchCreateAccounts(_ context.Context, req sub2api.BatchCreateAccountsRequest) ([]sub2api.AccountRef, error) {
	f.batchCreateReq = req
	if f.batchCreateErr != nil {
		return nil, f.batchCreateErr
	}
	return f.batchAccounts, nil
}
func (f *fakeHostAdapter) DeleteAccount(_ context.Context, accountID string) error {
	if err := f.deleteErrors["account:"+accountID]; err != nil {
		return err
	}
	f.callSequence = append(f.callSequence, "deleteAccount:"+accountID)
	f.deletedResources = append(f.deletedResources, "account:"+accountID)
	return nil
}
func (f *fakeHostAdapter) TestAccount(_ context.Context, accountID, modelID string) (sub2api.ProbeResult, error) {
	f.callSequence = append(f.callSequence, "testAccount:"+accountID)
	if f.testedModels == nil {
		f.testedModels = map[string]string{}
	}
	f.testedModels[accountID] = modelID
	result, ok := f.testResults[accountID]
	if !ok {
		return sub2api.ProbeResult{}, fmt.Errorf("missing test result for %s", accountID)
	}
	return result, nil
}
func (f *fakeHostAdapter) GetAccountModels(_ context.Context, accountID string) ([]sub2api.AccountModel, error) {
	models, ok := f.models[accountID]
	if !ok {
		return nil, fmt.Errorf("missing models for %s", accountID)
	}
	return models, nil
}
func (f *fakeHostAdapter) EnsureSubscriptionAccess(_ context.Context, req sub2api.EnsureSubscriptionAccessRequest) (sub2api.SubscriptionAccessRef, error) {
	return sub2api.SubscriptionAccessRef{UserID: req.UserSelector, APIKey: "managed-subscription-key"}, nil
}
func (f *fakeHostAdapter) AssignSubscription(_ context.Context, req sub2api.AssignSubscriptionRequest) (sub2api.SubscriptionRef, error) {
	if f.assignErr != nil {
		return sub2api.SubscriptionRef{}, f.assignErr
	}
	f.assignedSubscriptions = append(f.assignedSubscriptions, req)
	return sub2api.SubscriptionRef{ID: "subscription_1"}, nil
}
func (f *fakeHostAdapter) CheckGatewayAccess(_ context.Context, req sub2api.GatewayAccessCheckRequest) (sub2api.GatewayAccessResult, error) {
	f.callSequence = append(f.callSequence, "gateway")
	f.gatewayProbe = req
	if f.gatewayErr != nil {
		return sub2api.GatewayAccessResult{}, f.gatewayErr
	}
	return f.gatewayResult, nil
}
func (f *fakeHostAdapter) CheckGatewayCompletion(_ context.Context, req sub2api.GatewayCompletionCheckRequest) (sub2api.GatewayCompletionResult, error) {
	f.callSequence = append(f.callSequence, "completion")
	f.completionProbe = req
	f.completionCalls++
	if f.completionErr != nil {
		return sub2api.GatewayCompletionResult{}, f.completionErr
	}
	if f.disableResponsesCalls > 0 && f.completionAfterRepair != nil {
		return *f.completionAfterRepair, nil
	}
	if len(f.completionResults) > 0 {
		idx := f.completionCalls - 1
		if idx >= len(f.completionResults) {
			idx = len(f.completionResults) - 1
		}
		return f.completionResults[idx], nil
	}
	if f.completionResult.StatusCode == 0 && !f.completionResult.OK {
		return sub2api.GatewayCompletionResult{OK: true, StatusCode: 200, ContentType: "application/json"}, nil
	}
	return f.completionResult, nil
}
func (f *fakeHostAdapter) DisableOpenAIResponsesAPI(_ context.Context, accountIDs []string) error {
	f.callSequence = append(f.callSequence, "disable_responses")
	f.disableResponsesCalls++
	f.disabledResponsesAccountIDs = append([]string(nil), accountIDs...)
	return nil
}

func (f *fakeHostAdapter) ClearTempUnschedulable(_ context.Context, accountIDs []string) error {
	f.callSequence = append(f.callSequence, "clear_temp_unschedulable")
	f.clearTempUnschedulableCalls++
	f.clearedTempUnschedulableAccountIDs = append([]string(nil), accountIDs...)
	return nil
}
func (f *fakeHostAdapter) ListManagedResources(_ context.Context, req sub2api.ListManagedResourcesRequest) (sub2api.ManagedResourceSnapshot, error) {
	f.listManagedReq = req
	return sub2api.ManagedResourceSnapshot{
		Groups:   filterNamedResourcesByExactName(f.managedSnapshot.Groups, req.GroupName),
		Channels: filterNamedResourcesByExactName(f.managedSnapshot.Channels, req.ChannelName),
		Plans:    filterNamedResourcesByExactName(f.managedSnapshot.Plans, req.PlanName),
		Accounts: filterNamedResourcesByPrefix(f.managedSnapshot.Accounts, req.AccountNamePrefix),
	}, nil
}

func filterNamedResourcesByExactName(resources []sub2api.NamedResource, expected string) []sub2api.NamedResource {
	expected = strings.TrimSpace(expected)
	if expected == "" {
		return nil
	}
	filtered := make([]sub2api.NamedResource, 0, len(resources))
	for _, resource := range resources {
		if strings.TrimSpace(resource.Name) == expected {
			filtered = append(filtered, resource)
		}
	}
	return filtered
}

func filterNamedResourcesByPrefix(resources []sub2api.NamedResource, prefix string) []sub2api.NamedResource {
	prefix = strings.TrimSpace(prefix)
	if prefix == "" {
		return resources
	}
	filtered := make([]sub2api.NamedResource, 0, len(resources))
	for _, resource := range resources {
		if strings.HasPrefix(strings.TrimSpace(resource.Name), prefix) {
			filtered = append(filtered, resource)
		}
	}
	return filtered
}
