package meta

import (
	"context"
	"reflect"
	"testing"
	"time"
)

func TestMetaSchemaSpecIncludesTenantMetadataColumns(t *testing.T) {
	table := mustMetaTableSpec(t, mustMetaSpec(t), "tenants")
	tests := map[string]string{
		"display_name": "ALTER TABLE tenants ADD COLUMN display_name VARCHAR(255) NOT NULL DEFAULT ''",
		"label":        "ALTER TABLE tenants ADD COLUMN label JSON NULL",
	}
	for column, wantAddSQL := range tests {
		spec, ok := table.columns[column]
		if !ok {
			t.Fatalf("missing %s in tenants schema spec", column)
		}
		if spec.addSQL != wantAddSQL {
			t.Fatalf("%s addSQL = %q, want %q", column, spec.addSQL, wantAddSQL)
		}
		plans := plannedMetaSchemaRepairs([]metaSchemaDiff{{
			kind:       metaSchemaDiffMissingColumn,
			tableName:  "tenants",
			columnName: column,
			repairSQL:  spec.addSQL,
		}})
		if len(plans) != 1 || plans[0] != wantAddSQL {
			t.Fatalf("%s repair plans = %#v, want %q", column, plans, wantAddSQL)
		}
	}
}

func TestTenantMetadataRoundTrip(t *testing.T) {
	s := newControlStore(t)
	now := time.Now().UTC()
	wantLabel := map[string]string{"environment": "production", "team": "search"}
	if err := s.InsertTenant(context.Background(), &Tenant{
		ID: "tenant-metadata-round-trip", Status: TenantActive, Kind: TenantKindLive,
		DisplayName: "Search Production", Label: wantLabel,
		DBPasswordCipher: []byte{}, Provider: tidbCloudNativeProvider, SchemaVersion: 1,
		CreatedAt: now, UpdatedAt: now,
	}); err != nil {
		t.Fatal(err)
	}

	got, err := s.GetTenant(context.Background(), "tenant-metadata-round-trip")
	if err != nil {
		t.Fatal(err)
	}
	if got.DisplayName != "Search Production" {
		t.Fatalf("display name = %q, want Search Production", got.DisplayName)
	}
	if !reflect.DeepEqual(got.Label, wantLabel) {
		t.Fatalf("label = %#v, want %#v", got.Label, wantLabel)
	}
}

func TestListTenantsByTiDBCloudOrganizationFiltersEffectiveDisplayNameAndLabel(t *testing.T) {
	s := newControlStore(t)
	ctx := context.Background()
	now := time.Now().UTC()
	insert := func(id, displayName string, label map[string]string, createdAt time.Time) {
		t.Helper()
		if err := s.InsertTenant(ctx, &Tenant{
			ID: id, Status: TenantActive, Kind: TenantKindLive,
			DisplayName: displayName, Label: label,
			DBPasswordCipher: []byte{}, Provider: tidbCloudNativeProvider, SchemaVersion: 1,
			CreatedAt: createdAt, UpdatedAt: createdAt,
		}); err != nil {
			t.Fatal(err)
		}
		if err := s.UpsertTenantTiDBCloudOrgBinding(ctx, &TenantTiDBCloudOrgBinding{
			TenantID: id, OrganizationID: "org-metadata-filter", ClusterID: "cluster-" + id,
			CreatedAt: createdAt, UpdatedAt: createdAt,
		}); err != nil {
			t.Fatal(err)
		}
	}
	insert("tenant-id-fallback-match", "", map[string]string{"environment": "production"}, now)
	insert("tenant-explicit-match", "Production Search", map[string]string{"environment": "production"}, now.Add(time.Second))
	insert("tenant-wrong-label", "Production Archive", map[string]string{"environment": "staging"}, now.Add(2*time.Second))

	rows, err := s.ListTenantsByTiDBCloudOrganizationFiltered(ctx, "org-metadata-filter", TenantListFilter{
		DisplayNameContains: "match",
		LabelKey:            "environment",
		LabelValue:          "production",
	}, 0, 10)
	if err != nil {
		t.Fatal(err)
	}
	if len(rows) != 1 || rows[0].ID != "tenant-id-fallback-match" {
		t.Fatalf("tenant-id fallback rows = %#v, want tenant-id-fallback-match", rows)
	}

	rows, err = s.ListTenantsByTiDBCloudOrganizationFiltered(ctx, "org-metadata-filter", TenantListFilter{
		DisplayNameContains: "Production",
		LabelKey:            "environment",
		LabelValue:          "production",
	}, 0, 10)
	if err != nil {
		t.Fatal(err)
	}
	if len(rows) != 1 || rows[0].ID != "tenant-explicit-match" {
		t.Fatalf("explicit display rows = %#v, want tenant-explicit-match", rows)
	}
}

func TestClaimSharedTenantPoolMembershipPersistsMetadata(t *testing.T) {
	s := newControlStore(t)
	ctx := context.Background()
	now := time.Now().UTC()
	const tenantID = "shared-metadata-claim"
	if err := s.InsertTenant(ctx, &Tenant{
		ID: tenantID, Status: TenantActive, Kind: TenantKindLive,
		Provider: tidbCloudNativeSharedProvider, DBPasswordCipher: []byte{}, SchemaVersion: 1,
		CreatedAt: now, UpdatedAt: now,
	}); err != nil {
		t.Fatal(err)
	}
	fsID, err := s.ResolveFsID(ctx, tenantID)
	if err != nil {
		t.Fatal(err)
	}
	dbID, err := s.RegisterSharedDB(ctx, &SharedDB{
		TiDBCloudOrganizationID: "org-shared-metadata", Host: "shared.example.com", Port: 4000,
		User: "root", PasswordCipher: []byte("cipher"), Name: "shared_metadata", MaxTenants: 100,
	})
	if err != nil {
		t.Fatal(err)
	}
	if err := s.UpsertTenantPlacement(ctx, &TenantPlacement{
		FsID: fsID, DbID: dbID, Placement: PlacementShared, SchemaShape: SchemaShapeShared,
	}); err != nil {
		t.Fatal(err)
	}
	if err := s.UpsertTenantPoolMembership(ctx, &TenantPoolMembership{
		TenantID: tenantID, TiDBCloudOrganizationID: "org-shared-metadata", PoolID: "pool-shared-metadata",
		PoolStatus: TenantPoolBindingFree, CreatedAt: now, UpdatedAt: now,
	}); err != nil {
		t.Fatal(err)
	}
	if err := s.SetQuotaConfig(ctx, &QuotaConfig{
		TenantID: tenantID, MaxStorageBytes: 100, MaxFileSizeBytes: 10, MaxFileCount: 7,
	}); err != nil {
		t.Fatal(err)
	}
	key := &APIKey{
		ID: "key-shared-metadata", TenantID: tenantID, KeyName: "default",
		JWTCiphertext: []byte("cipher"), JWTHash: "hash-shared-metadata", TokenVersion: 1,
		Status: APIKeyActive, ScopeKind: APIKeyScopeKindOwner,
		IssuedAt: now, CreatedAt: now, UpdatedAt: now,
	}
	if err := s.ClaimSharedTenantPoolMembershipWithMetadata(ctx, tenantID, "pool-shared-metadata", QuotaConfigPatch{}, key, TenantMetadata{
		DisplayName: "Shared Search", Label: map[string]string{"environment": "production"},
	}); err != nil {
		t.Fatal(err)
	}

	got, err := s.GetTenant(ctx, tenantID)
	if err != nil {
		t.Fatal(err)
	}
	if got.DisplayName != "Shared Search" || got.Label["environment"] != "production" {
		t.Fatalf("tenant metadata = %#v", got)
	}
	membership, err := s.GetTenantPoolMembership(ctx, tenantID)
	if err != nil {
		t.Fatal(err)
	}
	if membership.PoolStatus != TenantPoolBindingUsed || membership.UsedAt == nil {
		t.Fatalf("membership = %#v, want used", membership)
	}
	if _, err := s.ResolveByAPIKeyHash(ctx, key.JWTHash); err != nil {
		t.Fatalf("resolve claimed owner key: %v", err)
	}
}
