package provision

import (
	"context"
	"strings"
	"testing"

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

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

	ctx := context.Background()
	hostID, err := store.Hosts().Create(ctx, sqlite.Host{HostID: "host-1", BaseURL: "https://sub2api.example.com", HostVersion: "0.1.126", CapabilityProbeJSON: `{"supports_batch_accounts":true}`})
	if err != nil {
		t.Fatalf("Hosts().Create() error = %v", err)
	}
	packID, err := store.Packs().Create(ctx, sqlite.Pack{PackID: "openai-cn-pack", Version: "1.0.0", Checksum: "checksum-1"})
	if err != nil {
		t.Fatalf("Packs().Create() error = %v", err)
	}
	providerID, err := store.Providers().Create(ctx, sqlite.Provider{PackID: packID, ProviderID: "deepseek", DisplayName: "DeepSeek", BaseURL: "https://api.deepseek.com", Platform: "openai"})
	if err != nil {
		t.Fatalf("Providers().Create() error = %v", err)
	}
	batchID, err := store.ImportBatches().Create(ctx, sqlite.ImportBatch{HostID: hostID, PackID: packID, ProviderID: providerID, Mode: ImportModeStrict, BatchStatus: BatchStatusSucceeded, AccessStatus: AccessStatusSelfServiceReady})
	if err != nil {
		t.Fatalf("ImportBatches().Create() error = %v", err)
	}
	if _, err := store.ManagedResources().Create(ctx, sqlite.ManagedResource{BatchID: batchID, HostID: hostID, ResourceType: "group", HostResourceID: "group-1", ResourceName: "deepseek-group"}); err != nil {
		t.Fatalf("ManagedResources().Create(group) error = %v", err)
	}
	if _, err := store.ManagedResources().Create(ctx, sqlite.ManagedResource{BatchID: batchID, HostID: hostID, ResourceType: "account", HostResourceID: "account-1", ResourceName: "deepseek-account-1"}); err != nil {
		t.Fatalf("ManagedResources().Create(account) error = %v", err)
	}
	if _, err := store.AccessClosures().Create(ctx, sqlite.AccessClosureRecord{BatchID: batchID, ClosureType: AccessModeSelfService, Status: AccessStatusSelfServiceReady, DetailsJSON: `{"ok":true}`}); err != nil {
		t.Fatalf("AccessClosures().Create() error = %v", err)
	}
	if _, err := store.ReconcileRuns().Create(ctx, sqlite.ReconcileRun{BatchID: batchID, HostID: hostID, ProviderID: providerID, Status: "drifted", SummaryJSON: `{"missing_count":1}`}); err != nil {
		t.Fatalf("ReconcileRuns().Create() error = %v", err)
	}

	snapshot, err := NewProviderStatusService(store).GetStatus(ctx, ProviderQuery{ProviderID: "deepseek", HostID: "host-1"})
	if err != nil {
		t.Fatalf("GetStatus() error = %v", err)
	}
	if snapshot.Host.HostID != "host-1" {
		t.Fatalf("Host.HostID = %q, want host-1", snapshot.Host.HostID)
	}
	if snapshot.Pack.PackID != "openai-cn-pack" {
		t.Fatalf("Pack.PackID = %q, want openai-cn-pack", snapshot.Pack.PackID)
	}
	if snapshot.Provider.ProviderID != "deepseek" {
		t.Fatalf("Provider.ProviderID = %q, want deepseek", snapshot.Provider.ProviderID)
	}
	if snapshot.ProviderStatus != ProviderStatusActive {
		t.Fatalf("ProviderStatus = %q, want %q", snapshot.ProviderStatus, ProviderStatusActive)
	}
	if snapshot.LatestAccessStatus != AccessStatusSelfServiceReady {
		t.Fatalf("LatestAccessStatus = %q, want %q", snapshot.LatestAccessStatus, AccessStatusSelfServiceReady)
	}
	if snapshot.LatestReconcileStatus != "drifted" {
		t.Fatalf("LatestReconcileStatus = %q, want drifted", snapshot.LatestReconcileStatus)
	}
	if got := len(snapshot.ManagedResources); got != 2 {
		t.Fatalf("len(ManagedResources) = %d, want 2", got)
	}
	if got, ok := snapshot.LatestReconcileSummary["missing_count"].(float64); !ok || got != 1 {
		t.Fatalf("LatestReconcileSummary[missing_count] = %#v, want 1", snapshot.LatestReconcileSummary["missing_count"])
	}
}

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

	ctx := context.Background()
	hostID, err := store.Hosts().Create(ctx, sqlite.Host{HostID: "host-1", BaseURL: "https://sub2api.example.com", HostVersion: "0.1.126", CapabilityProbeJSON: `{}`})
	if err != nil {
		t.Fatalf("Hosts().Create() error = %v", err)
	}
	packID, err := store.Packs().Create(ctx, sqlite.Pack{PackID: "openai-cn-pack", Version: "1.0.0", Checksum: "checksum-1"})
	if err != nil {
		t.Fatalf("Packs().Create() error = %v", err)
	}
	providerID, err := store.Providers().Create(ctx, sqlite.Provider{PackID: packID, ProviderID: "deepseek", DisplayName: "DeepSeek", BaseURL: "https://api.deepseek.com", Platform: "openai"})
	if err != nil {
		t.Fatalf("Providers().Create() error = %v", err)
	}
	firstBatch, err := store.ImportBatches().Create(ctx, sqlite.ImportBatch{HostID: hostID, PackID: packID, ProviderID: providerID, Mode: ImportModePartial, BatchStatus: BatchStatusSucceeded, AccessStatus: AccessStatusSelfServiceReady})
	if err != nil {
		t.Fatalf("ImportBatches().Create(first) error = %v", err)
	}
	if _, err := store.ManagedResources().Create(ctx, sqlite.ManagedResource{BatchID: firstBatch, HostID: hostID, ResourceType: "group", HostResourceID: "group-1", ResourceName: "deepseek-group"}); err != nil {
		t.Fatalf("ManagedResources().Create(group) error = %v", err)
	}
	secondBatch, err := store.ImportBatches().Create(ctx, sqlite.ImportBatch{HostID: hostID, PackID: packID, ProviderID: providerID, Mode: ImportModePartial, BatchStatus: BatchStatusSucceeded, AccessStatus: AccessStatusSelfServiceReady})
	if err != nil {
		t.Fatalf("ImportBatches().Create(second) error = %v", err)
	}
	if _, err := store.ManagedResources().Create(ctx, sqlite.ManagedResource{BatchID: secondBatch, HostID: hostID, ResourceType: "channel", HostResourceID: "channel-2", ResourceName: "deepseek-channel"}); err != nil {
		t.Fatalf("ManagedResources().Create(channel) error = %v", err)
	}
	if _, err := store.AccessClosures().Create(ctx, sqlite.AccessClosureRecord{BatchID: secondBatch, ClosureType: AccessModeSelfService, Status: AccessStatusSelfServiceReady, DetailsJSON: `{"ok":true}`}); err != nil {
		t.Fatalf("AccessClosures().Create() error = %v", err)
	}

	snapshot, err := NewProviderStatusService(store).GetStatus(ctx, ProviderQuery{ProviderID: "deepseek", HostID: "host-1"})
	if err != nil {
		t.Fatalf("GetStatus() error = %v", err)
	}
	if got := len(snapshot.ManagedResources); got != 1 {
		t.Fatalf("len(ManagedResources) = %d, want latest batch resources only", got)
	}
	if snapshot.ManagedResources[0].HostResourceID != "channel-2" {
		t.Fatalf("HostResourceID = %q, want channel-2", snapshot.ManagedResources[0].HostResourceID)
	}
}

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

	ctx := context.Background()
	hostA, err := store.Hosts().Create(ctx, sqlite.Host{HostID: "host-a", BaseURL: "https://a.example.com", HostVersion: "0.1.126", CapabilityProbeJSON: `{}`})
	if err != nil {
		t.Fatalf("Hosts().Create(host-a) error = %v", err)
	}
	hostB, err := store.Hosts().Create(ctx, sqlite.Host{HostID: "host-b", BaseURL: "https://b.example.com", HostVersion: "0.1.126", CapabilityProbeJSON: `{}`})
	if err != nil {
		t.Fatalf("Hosts().Create(host-b) error = %v", err)
	}
	packID, err := store.Packs().Create(ctx, sqlite.Pack{PackID: "openai-cn-pack", Version: "1.0.0", Checksum: "checksum-1"})
	if err != nil {
		t.Fatalf("Packs().Create() error = %v", err)
	}
	providerID, err := store.Providers().Create(ctx, sqlite.Provider{PackID: packID, ProviderID: "deepseek", DisplayName: "DeepSeek", BaseURL: "https://api.deepseek.com", Platform: "openai"})
	if err != nil {
		t.Fatalf("Providers().Create() error = %v", err)
	}
	batchA, err := store.ImportBatches().Create(ctx, sqlite.ImportBatch{HostID: hostA, PackID: packID, ProviderID: providerID, Mode: ImportModePartial, BatchStatus: BatchStatusSucceeded, AccessStatus: AccessStatusSelfServiceReady})
	if err != nil {
		t.Fatalf("ImportBatches().Create(host-a) error = %v", err)
	}
	batchB, err := store.ImportBatches().Create(ctx, sqlite.ImportBatch{HostID: hostB, PackID: packID, ProviderID: providerID, Mode: ImportModePartial, BatchStatus: BatchStatusSucceeded, AccessStatus: AccessStatusSelfServiceReady})
	if err != nil {
		t.Fatalf("ImportBatches().Create(host-b) error = %v", err)
	}
	if _, err := store.ReconcileRuns().Create(ctx, sqlite.ReconcileRun{BatchID: batchA, HostID: hostA, ProviderID: providerID, Status: "drifted", SummaryJSON: `{"host":"a"}`}); err != nil {
		t.Fatalf("ReconcileRuns().Create(host-a) error = %v", err)
	}
	if _, err := store.ReconcileRuns().Create(ctx, sqlite.ReconcileRun{BatchID: batchB, HostID: hostB, ProviderID: providerID, Status: "active", SummaryJSON: `{"host":"b"}`}); err != nil {
		t.Fatalf("ReconcileRuns().Create(host-b) error = %v", err)
	}

	snapshot, err := NewProviderStatusService(store).GetStatus(ctx, ProviderQuery{ProviderID: "deepseek", HostID: "host-a"})
	if err != nil {
		t.Fatalf("GetStatus() error = %v", err)
	}
	if snapshot.LatestReconcileStatus != "drifted" {
		t.Fatalf("LatestReconcileStatus = %q, want drifted for host-a", snapshot.LatestReconcileStatus)
	}
}

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

	ctx := context.Background()
	pack1, err := store.Packs().Create(ctx, sqlite.Pack{PackID: "pack-a", Version: "1.0.0", Checksum: "checksum-a"})
	if err != nil {
		t.Fatalf("Packs().Create(pack-a) error = %v", err)
	}
	pack2, err := store.Packs().Create(ctx, sqlite.Pack{PackID: "pack-b", Version: "1.0.0", Checksum: "checksum-b"})
	if err != nil {
		t.Fatalf("Packs().Create(pack-b) error = %v", err)
	}
	if _, err := store.Providers().Create(ctx, sqlite.Provider{PackID: pack1, ProviderID: "deepseek", DisplayName: "DeepSeek A", BaseURL: "https://a.example.com", Platform: "openai"}); err != nil {
		t.Fatalf("Providers().Create(pack-a) error = %v", err)
	}
	if _, err := store.Providers().Create(ctx, sqlite.Provider{PackID: pack2, ProviderID: "deepseek", DisplayName: "DeepSeek B", BaseURL: "https://b.example.com", Platform: "openai"}); err != nil {
		t.Fatalf("Providers().Create(pack-b) error = %v", err)
	}

	_, err = NewProviderStatusService(store).GetStatus(ctx, ProviderQuery{ProviderID: "deepseek"})
	if err == nil || err.Error() != `provider "deepseek" exists in multiple packs; pack_id is required` {
		t.Fatalf("GetStatus() error = %v, want ambiguous provider error", err)
	}
}

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

	ctx := context.Background()
	packID, err := store.Packs().Create(ctx, sqlite.Pack{PackID: "openai-cn-pack", Version: "1.0.0", Checksum: "checksum-1"})
	if err != nil {
		t.Fatalf("Packs().Create() error = %v", err)
	}
	if _, err := store.Providers().Create(ctx, sqlite.Provider{
		PackID: packID, ProviderID: "deepseek", DisplayName: "DeepSeek", BaseURL: "https://api.deepseek.com", Platform: "openai",
	}); err != nil {
		t.Fatalf("Providers().Create() error = %v", err)
	}

	_, err = NewProviderStatusService(store).GetStatus(ctx, ProviderQuery{ProviderID: "deepseek"})
	if err == nil || err.Error() != "latest import batch not found for provider" {
		t.Fatalf("GetStatus() error = %v, want latest batch not found", err)
	}
}

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

	ctx := context.Background()
	hostA, err := store.Hosts().Create(ctx, sqlite.Host{HostID: "host-a", BaseURL: "https://a.example.com", HostVersion: "0.1.126", CapabilityProbeJSON: `{}`})
	if err != nil {
		t.Fatalf("Hosts().Create(host-a) error = %v", err)
	}
	hostB, err := store.Hosts().Create(ctx, sqlite.Host{HostID: "host-b", BaseURL: "https://b.example.com", HostVersion: "0.1.126", CapabilityProbeJSON: `{}`})
	if err != nil {
		t.Fatalf("Hosts().Create(host-b) error = %v", err)
	}
	packID, err := store.Packs().Create(ctx, sqlite.Pack{PackID: "openai-cn-pack", Version: "1.0.0", Checksum: "checksum-1"})
	if err != nil {
		t.Fatalf("Packs().Create() error = %v", err)
	}
	providerID, err := store.Providers().Create(ctx, sqlite.Provider{
		PackID: packID, ProviderID: "deepseek", DisplayName: "DeepSeek", BaseURL: "https://api.deepseek.com", Platform: "openai",
	})
	if err != nil {
		t.Fatalf("Providers().Create() error = %v", err)
	}
	if _, err := store.ImportBatches().Create(ctx, sqlite.ImportBatch{
		HostID: hostA, PackID: packID, ProviderID: providerID, Mode: ImportModePartial, BatchStatus: BatchStatusSucceeded, AccessStatus: AccessStatusSelfServiceReady,
	}); err != nil {
		t.Fatalf("ImportBatches().Create(host-a) error = %v", err)
	}
	if _, err := store.ImportBatches().Create(ctx, sqlite.ImportBatch{
		HostID: hostB, PackID: packID, ProviderID: providerID, Mode: ImportModePartial, BatchStatus: BatchStatusSucceeded, AccessStatus: AccessStatusSelfServiceReady,
	}); err != nil {
		t.Fatalf("ImportBatches().Create(host-b) error = %v", err)
	}

	_, err = NewProviderStatusService(store).GetStatus(ctx, ProviderQuery{ProviderID: "deepseek"})
	if err == nil || err.Error() != "provider exists on multiple hosts; host_id is required" {
		t.Fatalf("GetStatus() error = %v, want host_id required", err)
	}
}

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

	ctx := context.Background()
	hostID := seedProvisionHost(t, store, "host-1", "https://sub2api.example.com")
	packID, err := store.Packs().Create(ctx, sqlite.Pack{PackID: "openai-cn-pack", Version: "1.0.0", Checksum: "checksum-1"})
	if err != nil {
		t.Fatalf("Packs().Create() error = %v", err)
	}
	providerID, err := store.Providers().Create(ctx, sqlite.Provider{
		PackID: packID, ProviderID: "deepseek", DisplayName: "DeepSeek", BaseURL: "https://api.deepseek.com", Platform: "openai",
	})
	if err != nil {
		t.Fatalf("Providers().Create() error = %v", err)
	}
	batchID, err := store.ImportBatches().Create(ctx, sqlite.ImportBatch{
		HostID: hostID, PackID: packID, ProviderID: providerID, Mode: ImportModePartial, BatchStatus: BatchStatusSucceeded, AccessStatus: AccessStatusSelfServiceReady,
	})
	if err != nil {
		t.Fatalf("ImportBatches().Create() error = %v", err)
	}
	if _, err := store.AccessClosures().Create(ctx, sqlite.AccessClosureRecord{
		BatchID: batchID, ClosureType: AccessModeSelfService, Status: AccessStatusSelfServiceReady, DetailsJSON: `{"ok":true}`,
	}); err != nil {
		t.Fatalf("AccessClosures().Create() error = %v", err)
	}
	if _, err := store.ReconcileRuns().Create(ctx, sqlite.ReconcileRun{
		BatchID: batchID, HostID: hostID, ProviderID: providerID, Status: "active", SummaryJSON: `{"missing_count":`,
	}); err != nil {
		t.Fatalf("ReconcileRuns().Create() error = %v", err)
	}

	_, err = NewProviderStatusService(store).GetStatus(ctx, ProviderQuery{ProviderID: "deepseek", HostID: "host-1"})
	if err == nil || !strings.Contains(err.Error(), "decode reconcile summary") {
		t.Fatalf("GetStatus() error = %v, want decode reconcile summary failure", err)
	}
}

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

	ctx := context.Background()
	hostID := seedProvisionHost(t, store, "host-1", "https://sub2api.example.com")
	packID, err := store.Packs().Create(ctx, sqlite.Pack{PackID: "openai-cn-pack", Version: "1.0.0", Checksum: "checksum-1"})
	if err != nil {
		t.Fatalf("Packs().Create() error = %v", err)
	}
	providerID, err := store.Providers().Create(ctx, sqlite.Provider{
		PackID: packID, ProviderID: "deepseek", DisplayName: "DeepSeek", BaseURL: "https://api.deepseek.com", Platform: "openai",
	})
	if err != nil {
		t.Fatalf("Providers().Create() error = %v", err)
	}
	batchID, err := store.ImportBatches().Create(ctx, sqlite.ImportBatch{
		HostID: hostID, PackID: packID, ProviderID: providerID, Mode: ImportModePartial, BatchStatus: BatchStatusPartial, AccessStatus: AccessStatusSubscriptionReady,
	})
	if err != nil {
		t.Fatalf("ImportBatches().Create() error = %v", err)
	}
	if _, err := store.AccessClosures().Create(ctx, sqlite.AccessClosureRecord{
		BatchID: batchID, ClosureType: AccessModeSubscription, Status: AccessStatusSubscriptionReady, DetailsJSON: `{"completion_ok":true}`,
	}); err != nil {
		t.Fatalf("AccessClosures().Create() error = %v", err)
	}
	if _, err := store.ReconcileRuns().Create(ctx, sqlite.ReconcileRun{
		BatchID: batchID, HostID: hostID, ProviderID: providerID, Status: ProviderStatusActive, SummaryJSON: `{"access_status":"subscription_ready"}`,
	}); err != nil {
		t.Fatalf("ReconcileRuns().Create() error = %v", err)
	}

	snapshot, err := NewProviderStatusService(store).GetStatus(ctx, ProviderQuery{ProviderID: "deepseek", HostID: "host-1"})
	if err != nil {
		t.Fatalf("GetStatus() error = %v", err)
	}
	if snapshot.ProviderStatus != ProviderStatusActive {
		t.Fatalf("ProviderStatus = %q, want %q", snapshot.ProviderStatus, ProviderStatusActive)
	}
}

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

	ctx := context.Background()
	hostID := seedProvisionHost(t, store, "host-1", "https://sub2api.example.com")
	packID, err := store.Packs().Create(ctx, sqlite.Pack{PackID: "openai-cn-pack", Version: "1.0.0", Checksum: "checksum-1"})
	if err != nil {
		t.Fatalf("Packs().Create() error = %v", err)
	}
	providerID, err := store.Providers().Create(ctx, sqlite.Provider{
		PackID: packID, ProviderID: "deepseek", DisplayName: "DeepSeek", BaseURL: "https://api.deepseek.com", Platform: "openai",
	})
	if err != nil {
		t.Fatalf("Providers().Create() error = %v", err)
	}
	batchID, err := store.ImportBatches().Create(ctx, sqlite.ImportBatch{
		HostID: hostID, PackID: packID, ProviderID: providerID, Mode: ImportModePartial, BatchStatus: BatchStatusRolledBack, AccessStatus: AccessStatusSubscriptionReady,
	})
	if err != nil {
		t.Fatalf("ImportBatches().Create() error = %v", err)
	}
	if _, err := store.AccessClosures().Create(ctx, sqlite.AccessClosureRecord{
		BatchID: batchID, ClosureType: AccessModeSubscription, Status: AccessStatusSubscriptionReady, DetailsJSON: `{"completion_ok":true}`,
	}); err != nil {
		t.Fatalf("AccessClosures().Create() error = %v", err)
	}
	if _, err := store.ReconcileRuns().Create(ctx, sqlite.ReconcileRun{
		BatchID: batchID, HostID: hostID, ProviderID: providerID, Status: ProviderStatusActive, SummaryJSON: `{"access_status":"subscription_ready"}`,
	}); err != nil {
		t.Fatalf("ReconcileRuns().Create() error = %v", err)
	}

	snapshot, err := NewProviderStatusService(store).GetStatus(ctx, ProviderQuery{ProviderID: "deepseek", HostID: "host-1"})
	if err != nil {
		t.Fatalf("GetStatus() error = %v", err)
	}
	if snapshot.ProviderStatus != ProviderStatusFailed {
		t.Fatalf("ProviderStatus = %q, want %q", snapshot.ProviderStatus, ProviderStatusFailed)
	}
}
