package clusterrecovery

import (
	"context"
	"fmt"
	"testing"
	"time"

	"gorm.io/driver/sqlite"
	"gorm.io/gorm"

	"github.com/pingcap-inc/clinic/pkg/datasource/view"
	"github.com/pingcap-inc/clinic/pkg/db"
)

func TestGormStoreReadSnapshotFindsActiveOrgAndUnscopedCluster(t *testing.T) {
	gormDB := recoveryTestDB(t)
	org := db.Organization{Name: "masked-org", DbaasOrgID: 10392, OrgType: string(view.CloudDataSource)}
	if err := gormDB.Create(&org).Error; err != nil {
		t.Fatal(err)
	}
	cluster := db.Cluster{
		ClusterID:   "10914645658854034992",
		ClusterType: "cloud",
		OrgID:       org.ID,
		TenantID:    10392,
		Status:      "active",
	}
	if err := gormDB.Create(&cluster).Error; err != nil {
		t.Fatal(err)
	}
	if err := gormDB.Model(&db.Cluster{}).Where("cluster_id = ?", cluster.ClusterID).Update("deleted_at", time.Now().UTC()).Error; err != nil {
		t.Fatal(err)
	}
	snapshot, err := NewGormStore(gormDB).ReadSnapshot(context.Background(), ObjectKindMgmtCluster, 10392, cluster.ClusterID)
	if err != nil {
		t.Fatalf("ReadSnapshot() error = %v", err)
	}
	if snapshot.Organization == nil || snapshot.Organization.ID != org.ID {
		t.Fatalf("organization = %+v", snapshot.Organization)
	}
	if snapshot.Cluster == nil || !snapshot.Cluster.DeletedAt.Valid {
		t.Fatalf("unscoped cluster = %+v", snapshot.Cluster)
	}
	var count int64
	if err := gormDB.Unscoped().Model(&db.Cluster{}).Count(&count).Error; err != nil {
		t.Fatal(err)
	}
	if count != 1 {
		t.Fatalf("read-only snapshot changed row count to %d", count)
	}
}

func TestGormStoreReadSnapshotIgnoresDeletedOrganization(t *testing.T) {
	gormDB := recoveryTestDB(t)
	org := db.Organization{Name: "masked-org", DbaasOrgID: 10392, OrgType: string(view.CloudDataSource)}
	if err := gormDB.Create(&org).Error; err != nil {
		t.Fatal(err)
	}
	if err := gormDB.Delete(&org).Error; err != nil {
		t.Fatal(err)
	}
	snapshot, err := NewGormStore(gormDB).ReadSnapshot(context.Background(), ObjectKindMgmtCluster, 10392, "10914645658854034992")
	if err != nil {
		t.Fatalf("ReadSnapshot() error = %v", err)
	}
	if snapshot.Organization != nil {
		t.Fatalf("deleted organization was returned: %+v", snapshot.Organization)
	}
}

func TestGormStoreReadSnapshotAllowsTenantZeroOnlyForResourcePool(t *testing.T) {
	gormDB := recoveryTestDB(t)
	org := db.Organization{Name: "shared-multi-tenant", DbaasOrgID: 0, OrgType: string(view.CloudDataSource)}
	if err := gormDB.Create(&org).Error; err != nil {
		t.Fatal(err)
	}

	snapshot, err := NewGormStore(gormDB).ReadSnapshot(context.Background(), ObjectKindResourcePool, 0, "631176026126286850")
	if err != nil {
		t.Fatalf("ReadSnapshot() shared pool error = %v", err)
	}
	if snapshot.Organization == nil || snapshot.Organization.ID != org.ID || snapshot.Cluster != nil {
		t.Fatalf("unexpected shared pool snapshot: %+v", snapshot)
	}

	if _, err := NewGormStore(gormDB).ReadSnapshot(context.Background(), ObjectKindMgmtCluster, 0, "1"); err == nil {
		t.Fatal("tenant-zero mgmt snapshot was accepted")
	}
}

func TestDiffOwnedFieldsIncludesZeroValuesAndHashesRawData(t *testing.T) {
	before := &db.Cluster{Status: "active", Name: "private-name", StartTime: 10}
	after := &db.Cluster{Status: "", Name: "private-name", StartTime: 0}
	changes, err := diffOwnedFields(ObjectKindMgmtCluster, before, after)
	if err != nil {
		t.Fatal(err)
	}
	fields := changeFieldNames(changes)
	if len(fields) != 2 || fields[0] != "start_time" || fields[1] != "status" {
		t.Fatalf("fields = %v", fields)
	}
	for _, change := range changes {
		if change.BeforeHash == "active" || change.BeforeHash == "10" || change.AfterHash == "" || !isSHA256(change.BeforeHash) || !isSHA256(change.AfterHash) {
			t.Fatalf("change leaks raw data or lacks hashes: %+v", change)
		}
	}
}

func TestClusterRecordChecksumChangesOnNonOwnedField(t *testing.T) {
	cluster := &db.Cluster{
		Model:       gorm.Model{ID: 1, CreatedAt: time.Now().UTC(), UpdatedAt: time.Now().UTC()},
		ClusterID:   "1",
		ClusterType: "cloud",
		TenantID:    2,
		OrgID:       3,
		Branches:    db.StringSlice{"branch-a"},
	}
	before, err := clusterRecordChecksum(cluster)
	if err != nil {
		t.Fatal(err)
	}
	cluster.Branches = db.StringSlice{"branch-b"}
	after, err := clusterRecordChecksum(cluster)
	if err != nil {
		t.Fatal(err)
	}
	if before == after {
		t.Fatal("full row checksum ignored non-owned field drift")
	}
}

func recoveryTestDB(t *testing.T) *gorm.DB {
	t.Helper()
	dsn := fmt.Sprintf("file:recovery-%d?mode=memory&cache=shared", time.Now().UnixNano())
	gormDB, err := gorm.Open(sqlite.Open(dsn), &gorm.Config{DisableForeignKeyConstraintWhenMigrating: true})
	if err != nil {
		t.Fatal(err)
	}
	if err := gormDB.AutoMigrate(&db.Organization{}, &db.Cluster{}); err != nil {
		t.Fatal(err)
	}
	return gormDB
}
