package case_driver

import (
	"fmt"
	"sort"
	"strings"

	"github.com/elliotchance/pie/v2"
	"github.com/tidbcloud/dataflow-service-e2e/cases"
	"github.com/tidbcloud/dataflow-service-e2e/cases/dedicated/backup"
	"github.com/tidbcloud/dataflow-service-e2e/cases/dedicated/changefeed"
	"github.com/tidbcloud/dataflow-service-e2e/cases/dedicated/crrf"
	"github.com/tidbcloud/dataflow-service-e2e/cases/dedicated/dm"
	"github.com/tidbcloud/dataflow-service-e2e/cases/dedicated/import_data"
	"github.com/tidbcloud/dataflow-service-e2e/internal/config"
	. "github.com/tidbcloud/dataflow-service-e2e/internal/model"
)

var AllCases = map[string]cases.E2ECase{}
var CaseGroups map[string][]cases.E2ECase

func addCases(cases ...cases.E2ECase) {
	for _, c := range cases {
		if _, ok := AllCases[c.Name()]; ok {
			panic("there is a duplicate case name: " + c.Name())
		}
		AllCases[c.Name()] = c
	}

}

func RegisterDedicatedCases() {
	// add cases
	awsKafkaChangefeedSmokeCase := changefeed.NewKafkaChangefeedSmokeCase(AWS)
	gcpKafkaChangefeedSmokeCase := changefeed.NewKafkaChangefeedSmokeCase(GCP)
	azureKafkaChangefeedSmokeCase := changefeed.NewKafkaChangefeedSmokeCase(AZURE)
	addCases(awsKafkaChangefeedSmokeCase, gcpKafkaChangefeedSmokeCase, azureKafkaChangefeedSmokeCase)

	awsKafkaKernelConfiguration := changefeed.NewKafkaChangefeedKernelConfigurationCase(AWS)
	gcpKafkaKernelConfiguration := changefeed.NewKafkaChangefeedKernelConfigurationCase(GCP)
	azureKafkaKernelConfiguration := changefeed.NewKafkaChangefeedKernelConfigurationCase(AZURE)
	addCases(awsKafkaKernelConfiguration, gcpKafkaKernelConfiguration, azureKafkaKernelConfiguration)

	awsKafkaPrivateLink := changefeed.NewKafkaPrivateLinkCase(AWS)
	gcpKafkaPrivateLink := changefeed.NewKafkaPrivateLinkCase(GCP)
	azureKafkaPrivateLink := changefeed.NewKafkaPrivateLinkCase(AZURE)
	addCases(awsKafkaPrivateLink, gcpKafkaPrivateLink, azureKafkaPrivateLink)

	awsMySQLPrivateLink := changefeed.NewMySQLPrivateLinkCase(AWS)
	gcpMySQLPrivateLink := changefeed.NewMySQLPrivateLinkCase(GCP)
	azureMySQLPrivateLink := changefeed.NewMySQLPrivateLinkCase(AZURE)
	addCases(awsMySQLPrivateLink, gcpMySQLPrivateLink, azureMySQLPrivateLink)

	awsTestConnection := changefeed.NewKafkaTestConnectionCase(AWS)
	gcpTestConnection := changefeed.NewKafkaTestConnectionCase(GCP)
	azureTestConnection := changefeed.NewKafkaTestConnectionCase(AZURE)
	addCases(awsTestConnection, azureTestConnection, gcpTestConnection)

	awsKafkaValidateChangefeedParams := changefeed.NewKafkaValidateChangefeedParamsCase(AWS)
	gcpKafkaValidateChangefeedParams := changefeed.NewKafkaValidateChangefeedParamsCase(GCP)
	azureKafkaValidateChangefeedParams := changefeed.NewKafkaValidateChangefeedParamsCase(AZURE)
	addCases(awsKafkaValidateChangefeedParams, gcpKafkaValidateChangefeedParams, azureKafkaValidateChangefeedParams)

	azureKafkaAvroChangefeedCase := changefeed.NewKafkaAvroChangefeedCase(AZURE)
	addCases(azureKafkaAvroChangefeedCase)

	awsScaleChangefeedCase := changefeed.NewScaleChangefeedCase(AWS)
	gcpScaleChangefeedCase := changefeed.NewScaleChangefeedCase(GCP)
	azureScaleChangefeedCase := changefeed.NewScaleChangefeedCase(AZURE)
	addCases(awsScaleChangefeedCase, gcpScaleChangefeedCase, azureScaleChangefeedCase)

	awsMySQLChangefeedSmokeCase := changefeed.NewMySQLChangefeedSmokeCase(AWS)
	gcpMySQLChangefeedSmokeCase := changefeed.NewMySQLChangefeedSmokeCase(GCP)
	azureMySQLChangefeedSmokeCase := changefeed.NewMySQLChangefeedSmokeCase(AZURE)
	addCases(awsMySQLChangefeedSmokeCase, gcpMySQLChangefeedSmokeCase, azureMySQLChangefeedSmokeCase)

	awsS3ChangefeedSmokeCase := changefeed.NewS3ChangefeedSmokeCase(AWS)
	addCases(awsS3ChangefeedSmokeCase)

	awsServerlessChangefeedSmokeCase := changefeed.NewServerlessChangefeedSmokeCase(AWS, config.GlobalConfig.ServerlessRegion)
	addCases(awsServerlessChangefeedSmokeCase)

	gcpGcsChangefeedSmokeCase := changefeed.NewGcsChangefeedSmokeCase(GCP)
	addCases(gcpGcsChangefeedSmokeCase)

	azureBlobStorageChangefeedSmokeCase := changefeed.NewAzureBlobStorageChangefeedSmokeCase(AZURE)
	addCases(azureBlobStorageChangefeedSmokeCase)

	awsPulsarChangefeedSmokeCase := changefeed.NewPulsarChangefeedSmokeCase(AWS)
	gcpPulsarChangefeedSmokeCase := changefeed.NewPulsarChangefeedSmokeCase(GCP)
	azurePulsarChangefeedSmokeCase := changefeed.NewPulsarChangefeedSmokeCase(AZURE)
	addCases(awsPulsarChangefeedSmokeCase, gcpPulsarChangefeedSmokeCase, azurePulsarChangefeedSmokeCase)

	awsValidateAllMachineTypeCase := changefeed.NewValidateAllMachineTypeCase(AWS)
	gcpValidateAllMachineTypeCase := changefeed.NewValidateAllMachineTypeCase(GCP)
	azureValidateAllMachineTypeCase := changefeed.NewValidateAllMachineTypeCase(AZURE)
	addCases(awsValidateAllMachineTypeCase, gcpValidateAllMachineTypeCase, azureValidateAllMachineTypeCase)

	awsCdcRecreateCase := changefeed.NewCdcRecreateCase(AWS)
	gcpCdcRecreateCase := changefeed.NewCdcRecreateCase(GCP)
	azureCdcRecreateCase := changefeed.NewCdcRecreateCase(AZURE)
	addCases(awsCdcRecreateCase, gcpCdcRecreateCase, azureCdcRecreateCase)

	awsChangefeedConfigOperationsCase := changefeed.NewChangefeedConfigOperationsCase(AWS)
	gcpChangefeedConfigOperationsCase := changefeed.NewChangefeedConfigOperationsCase(GCP)
	azureChangefeedConfigOperationsCase := changefeed.NewChangefeedConfigOperationsCase(AZURE)
	addCases(awsChangefeedConfigOperationsCase, gcpChangefeedConfigOperationsCase, azureChangefeedConfigOperationsCase)

	awsTiCDCClusterConfigOperationsCase := changefeed.NewTiCDCClusterConfigOperationsCase(AWS)
	gcpTiCDCClusterConfigOperationsCase := changefeed.NewTiCDCClusterConfigOperationsCase(GCP)
	azureTiCDCClusterConfigOperationsCase := changefeed.NewTiCDCClusterConfigOperationsCase(AZURE)
	addCases(awsTiCDCClusterConfigOperationsCase, gcpTiCDCClusterConfigOperationsCase, azureTiCDCClusterConfigOperationsCase)

	// BR Backup Cases - Phase 1: Core
	awsBackupSettingSmokeCase := backup.NewBackupSettingSmokeCase(AWS)
	gcpBackupSettingSmokeCase := backup.NewBackupSettingSmokeCase(GCP)
	azureBackupSettingSmokeCase := backup.NewBackupSettingSmokeCase(AZURE)
	addCases(awsBackupSettingSmokeCase, gcpBackupSettingSmokeCase, azureBackupSettingSmokeCase)

	awsSnapshotBackupSmokeCase := backup.NewSnapshotBackupSmokeCase(AWS)
	gcpSnapshotBackupSmokeCase := backup.NewSnapshotBackupSmokeCase(GCP)
	azureSnapshotBackupSmokeCase := backup.NewSnapshotBackupSmokeCase(AZURE)
	addCases(awsSnapshotBackupSmokeCase, gcpSnapshotBackupSmokeCase, azureSnapshotBackupSmokeCase)

	awsSnapshotRestoreSmokeCase := backup.NewSnapshotRestoreSmokeCase(AWS)
	gcpSnapshotRestoreSmokeCase := backup.NewSnapshotRestoreSmokeCase(GCP)
	azureSnapshotRestoreSmokeCase := backup.NewSnapshotRestoreSmokeCase(AZURE)
	addCases(awsSnapshotRestoreSmokeCase, gcpSnapshotRestoreSmokeCase, azureSnapshotRestoreSmokeCase)

	// BR Backup Cases - Phase 2: PiTR
	awsPiTRBackupSmokeCase := backup.NewPiTRBackupSmokeCase(AWS)
	gcpPiTRBackupSmokeCase := backup.NewPiTRBackupSmokeCase(GCP)
	azurePiTRBackupSmokeCase := backup.NewPiTRBackupSmokeCase(AZURE)
	addCases(awsPiTRBackupSmokeCase, gcpPiTRBackupSmokeCase, azurePiTRBackupSmokeCase)

	awsPiTRRestoreSmokeCase := backup.NewPiTRRestoreSmokeCase(AWS)
	gcpPiTRRestoreSmokeCase := backup.NewPiTRRestoreSmokeCase(GCP)
	azurePiTRRestoreSmokeCase := backup.NewPiTRRestoreSmokeCase(AZURE)
	addCases(awsPiTRRestoreSmokeCase, gcpPiTRRestoreSmokeCase, azurePiTRRestoreSmokeCase)

	// BR Backup Cases - Phase 3: Extended Features
	awsDualRegionBackupSmokeCase := backup.NewDualRegionBackupSmokeCase(AWS)
	gcpDualRegionBackupSmokeCase := backup.NewDualRegionBackupSmokeCase(GCP)
	azureDualRegionBackupSmokeCase := backup.NewDualRegionBackupSmokeCase(AZURE)
	addCases(awsDualRegionBackupSmokeCase, gcpDualRegionBackupSmokeCase, azureDualRegionBackupSmokeCase)

	// CRRF Cases (AWS-only, but registered for all providers; non-AWS skips gracefully)
	awsCRRFEnableDisableSmokeCase := crrf.NewCRRFEnableDisableSmokeCase(AWS)
	awsCRRFRestoreSmokeCase := crrf.NewCRRFRestoreSmokeCase(AWS)
	addCases(awsCRRFEnableDisableSmokeCase, awsCRRFRestoreSmokeCase)

	awsCRRFAutoDRTriggerCase := crrf.NewCRRFAutoDRTriggerCase(AWS)
	awsCRRFAutoPiTRTriggerCase := crrf.NewCRRFAutoPiTRTriggerCase(AWS)
	addCases(awsCRRFAutoDRTriggerCase, awsCRRFAutoPiTRTriggerCase)

	awsBackupExportSmokeCase := backup.NewBackupExportSmokeCase(AWS)
	gcpBackupExportSmokeCase := backup.NewBackupExportSmokeCase(GCP)
	addCases(awsBackupExportSmokeCase, gcpBackupExportSmokeCase)

	// DM Cases
	awsDMFullMigrationSmokeCase := dm.NewDMFullMigrationSmokeCase(AWS)
	gcpDMFullMigrationSmokeCase := dm.NewDMFullMigrationSmokeCase(GCP)
	azureDMFullMigrationSmokeCase := dm.NewDMFullMigrationSmokeCase(AZURE)
	addCases(awsDMFullMigrationSmokeCase, gcpDMFullMigrationSmokeCase, azureDMFullMigrationSmokeCase)

	awsDMIncrementalMigrationSmokeCase := dm.NewDMIncrementalMigrationSmokeCase(AWS)
	gcpDMIncrementalMigrationSmokeCase := dm.NewDMIncrementalMigrationSmokeCase(GCP)
	azureDMIncrementalMigrationSmokeCase := dm.NewDMIncrementalMigrationSmokeCase(AZURE)
	addCases(awsDMIncrementalMigrationSmokeCase, gcpDMIncrementalMigrationSmokeCase, azureDMIncrementalMigrationSmokeCase)

	awsDMJobLifecycleSmokeCase := dm.NewDMJobLifecycleSmokeCase(AWS)
	gcpDMJobLifecycleSmokeCase := dm.NewDMJobLifecycleSmokeCase(GCP)
	azureDMJobLifecycleSmokeCase := dm.NewDMJobLifecycleSmokeCase(AZURE)
	addCases(awsDMJobLifecycleSmokeCase, gcpDMJobLifecycleSmokeCase, azureDMJobLifecycleSmokeCase)

	awsDMPrecheckSmokeCase := dm.NewDMPrecheckSmokeCase(AWS)
	gcpDMPrecheckSmokeCase := dm.NewDMPrecheckSmokeCase(GCP)
	azureDMPrecheckSmokeCase := dm.NewDMPrecheckSmokeCase(AZURE)
	addCases(awsDMPrecheckSmokeCase, gcpDMPrecheckSmokeCase, azureDMPrecheckSmokeCase)

	awsDMScaleSmokeCase := dm.NewDMScaleSmokeCase(AWS)
	gcpDMScaleSmokeCase := dm.NewDMScaleSmokeCase(GCP)
	azureDMScaleSmokeCase := dm.NewDMScaleSmokeCase(AZURE)
	addCases(awsDMScaleSmokeCase, gcpDMScaleSmokeCase, azureDMScaleSmokeCase)

	// Import Cases
	awsImportSmokeCase := import_data.NewImportSmokeCase(AWS)
	gcpImportSmokeCase := import_data.NewImportSmokeCase(GCP)
	azureImportSmokeCase := import_data.NewImportSmokeCase(AZURE)
	addCases(awsImportSmokeCase, gcpImportSmokeCase, azureImportSmokeCase)

	// Import data-format cases (AURORA_SNAPSHOT/PARQUET). These run only when
	// selected by name or through the automatic "all" group; they are
	// intentionally not part of the smoke/weekly groups.
	awsImportAuroraSnapshotCase := import_data.NewImportDataFormatSmokeCase(AWS, config.DataFormatAuroraSnapshot)
	awsImportParquetCase := import_data.NewImportDataFormatSmokeCase(AWS, config.DataFormatParquet)
	addCases(awsImportAuroraSnapshotCase, awsImportParquetCase)

	printAllCasesName()

	// group cases
	CaseGroups = map[string][]cases.E2ECase{
		// will be assembled automatically
		"all":              {},
		"awsAll":           {},
		"gcpAll":           {},
		"azureAll":         {},
		"smoke":            {},
		"weeklyRegression": {},

		"awsSmoke": {
			// CDC
			awsKafkaChangefeedSmokeCase,
			awsMySQLChangefeedSmokeCase,
			awsS3ChangefeedSmokeCase,
			// BR
			awsPiTRRestoreSmokeCase,
			awsBackupExportSmokeCase,
			// CRRF
			awsCRRFEnableDisableSmokeCase,
			// DM
			awsDMFullMigrationSmokeCase,
			// Import
			awsImportSmokeCase,
		},
		"gcpSmoke": {
			// CDC
			gcpKafkaChangefeedSmokeCase,
			gcpMySQLChangefeedSmokeCase,
			gcpGcsChangefeedSmokeCase,
			// BR
			gcpPiTRRestoreSmokeCase,
			gcpBackupExportSmokeCase,
			// DM
			gcpDMFullMigrationSmokeCase,
			// Import
			gcpImportSmokeCase,
		},
		"azureSmoke": {
			// CDC
			azureKafkaChangefeedSmokeCase,
			azureMySQLChangefeedSmokeCase,
			// BR
			azurePiTRRestoreSmokeCase,
			// DM
			azureDMFullMigrationSmokeCase,
			// Import
			azureImportSmokeCase,
		},
		"awsWeeklyRegression": {
			// CDC
			awsKafkaChangefeedSmokeCase,
			awsMySQLChangefeedSmokeCase,
			awsS3ChangefeedSmokeCase,
			awsPulsarChangefeedSmokeCase,
			awsServerlessChangefeedSmokeCase,
			awsKafkaPrivateLink,
			awsMySQLPrivateLink,
			// BR
			awsBackupSettingSmokeCase,
			awsSnapshotBackupSmokeCase,
			awsSnapshotRestoreSmokeCase,
			awsPiTRBackupSmokeCase,
			awsPiTRRestoreSmokeCase,
			awsDualRegionBackupSmokeCase,
			awsBackupExportSmokeCase,
			// CRRF
			awsCRRFEnableDisableSmokeCase,
			awsCRRFRestoreSmokeCase,
			// DM
			awsDMFullMigrationSmokeCase,
			awsDMIncrementalMigrationSmokeCase,
			awsDMJobLifecycleSmokeCase,
			awsDMPrecheckSmokeCase,
			awsDMScaleSmokeCase,
			// Import
			awsImportSmokeCase,
		},
		"gcpWeeklyRegression": {
			// CDC
			gcpKafkaChangefeedSmokeCase,
			gcpMySQLChangefeedSmokeCase,
			gcpGcsChangefeedSmokeCase,
			gcpPulsarChangefeedSmokeCase,
			gcpKafkaPrivateLink,
			gcpMySQLPrivateLink,
			// BR
			gcpBackupSettingSmokeCase,
			gcpSnapshotBackupSmokeCase,
			gcpSnapshotRestoreSmokeCase,
			gcpPiTRBackupSmokeCase,
			gcpPiTRRestoreSmokeCase,
			gcpDualRegionBackupSmokeCase,
			gcpBackupExportSmokeCase,
			// DM
			gcpDMFullMigrationSmokeCase,
			gcpDMIncrementalMigrationSmokeCase,
			gcpDMJobLifecycleSmokeCase,
			gcpDMPrecheckSmokeCase,
			gcpDMScaleSmokeCase,
			// Import
			gcpImportSmokeCase,
		},
		"azureWeeklyRegression": {
			// CDC
			azureKafkaChangefeedSmokeCase,
			azureMySQLChangefeedSmokeCase,
			azurePulsarChangefeedSmokeCase,
			azureKafkaPrivateLink,
			// azureMySQLPrivateLink,
			// BR
			azureBackupSettingSmokeCase,
			azureSnapshotBackupSmokeCase,
			azureSnapshotRestoreSmokeCase,
			azurePiTRBackupSmokeCase,
			azurePiTRRestoreSmokeCase,
			azureDualRegionBackupSmokeCase,
			// DM
			azureDMFullMigrationSmokeCase,
			azureDMIncrementalMigrationSmokeCase,
			azureDMJobLifecycleSmokeCase,
			azureDMPrecheckSmokeCase,
			azureDMScaleSmokeCase,
			// Import
			azureImportSmokeCase,
		},
		"awsNewRegion": {
			// CDC
			awsKafkaChangefeedSmokeCase,
			awsKafkaKernelConfiguration,
			awsTestConnection,
			awsKafkaValidateChangefeedParams,
			awsMySQLChangefeedSmokeCase,
			awsPulsarChangefeedSmokeCase,
			awsScaleChangefeedCase,
			awsValidateAllMachineTypeCase,
			awsCdcRecreateCase,
			awsChangefeedConfigOperationsCase,
			awsTiCDCClusterConfigOperationsCase,
			// BR
			awsBackupSettingSmokeCase,
			awsSnapshotBackupSmokeCase,
			awsSnapshotRestoreSmokeCase,
			awsPiTRBackupSmokeCase,
			awsPiTRRestoreSmokeCase,
			// DM
			awsDMFullMigrationSmokeCase,
			awsDMIncrementalMigrationSmokeCase,
			awsDMJobLifecycleSmokeCase,
			awsDMPrecheckSmokeCase,
			awsDMScaleSmokeCase,
			// Import
			awsImportSmokeCase,
		},
		"gcpNewRegion": {
			// CDC
			gcpKafkaChangefeedSmokeCase,
			gcpKafkaKernelConfiguration,
			gcpTestConnection,
			gcpKafkaValidateChangefeedParams,
			gcpMySQLChangefeedSmokeCase,
			gcpPulsarChangefeedSmokeCase,
			gcpScaleChangefeedCase,
			gcpValidateAllMachineTypeCase,
			gcpCdcRecreateCase,
			gcpChangefeedConfigOperationsCase,
			gcpTiCDCClusterConfigOperationsCase,
			// BR
			gcpBackupSettingSmokeCase,
			gcpSnapshotBackupSmokeCase,
			gcpSnapshotRestoreSmokeCase,
			gcpPiTRBackupSmokeCase,
			gcpPiTRRestoreSmokeCase,
			// DM
			gcpDMFullMigrationSmokeCase,
			gcpDMIncrementalMigrationSmokeCase,
			gcpDMJobLifecycleSmokeCase,
			gcpDMPrecheckSmokeCase,
			gcpDMScaleSmokeCase,
			// Import
			gcpImportSmokeCase,
		},
		"azureNewRegion": {
			// CDC
			azureKafkaChangefeedSmokeCase,
			azureKafkaAvroChangefeedCase,
			azureKafkaKernelConfiguration,
			azureTestConnection,
			azureKafkaValidateChangefeedParams,
			azureMySQLChangefeedSmokeCase,
			azurePulsarChangefeedSmokeCase,
			azureScaleChangefeedCase,
			azureValidateAllMachineTypeCase,
			azureCdcRecreateCase,
			azureChangefeedConfigOperationsCase,
			azureTiCDCClusterConfigOperationsCase,
			// BR
			azureBackupSettingSmokeCase,
			azureSnapshotBackupSmokeCase,
			azureSnapshotRestoreSmokeCase,
			azurePiTRBackupSmokeCase,
			azurePiTRRestoreSmokeCase,
			// DM
			azureDMFullMigrationSmokeCase,
			azureDMIncrementalMigrationSmokeCase,
			azureDMJobLifecycleSmokeCase,
			azureDMPrecheckSmokeCase,
			azureDMScaleSmokeCase,
			// Import
			azureImportSmokeCase,
		},
	}

	// auto group
	for _, v := range AllCases {
		CaseGroups["all"] = append(CaseGroups["all"], v)
		reqs := pie.Filter(pie.Values(v.DeclareDependencyRequirements()), func(it DependencyRequirement) bool {
			return it.Type() == TiDBDedicated
		})

		if pie.All(reqs, func(value DependencyRequirement) bool { return value.(*TiDBDedicatedRequirement).CloudProvider == AWS }) {
			CaseGroups["awsAll"] = append(CaseGroups["awsAll"], v)
		}
		if pie.All(reqs, func(value DependencyRequirement) bool { return value.(*TiDBDedicatedRequirement).CloudProvider == GCP }) {
			CaseGroups["gcpAll"] = append(CaseGroups["gcpAll"], v)
		}
		if pie.All(reqs, func(value DependencyRequirement) bool {
			return value.(*TiDBDedicatedRequirement).CloudProvider == AZURE
		}) {
			CaseGroups["azureAll"] = append(CaseGroups["azureAll"], v)
		}
	}
	CaseGroups["smoke"] = append(CaseGroups["smoke"], CaseGroups["awsSmoke"]...)
	CaseGroups["smoke"] = append(CaseGroups["smoke"], CaseGroups["gcpSmoke"]...)
	CaseGroups["smoke"] = append(CaseGroups["smoke"], CaseGroups["azureSmoke"]...)
	CaseGroups["weeklyRegression"] = append(CaseGroups["weeklyRegression"], CaseGroups["awsWeeklyRegression"]...)
	CaseGroups["weeklyRegression"] = append(CaseGroups["weeklyRegression"], CaseGroups["gcpWeeklyRegression"]...)
	CaseGroups["weeklyRegression"] = append(CaseGroups["weeklyRegression"], CaseGroups["azureWeeklyRegression"]...)

	printCaseGroups()

	// filter cases by domain
	filterCasesByDomain()
}

func filterCasesByDomain() {
	domain := config.GlobalConfig.Domain
	fmt.Printf("Filtering cases by domain: %s\n", domain)

	// "all" keeps every domain; skip the domain tag filter entirely.
	if domain == config.DomainAll {
		fmt.Printf("domain is %q, keeping all cases across all domains\n", domain)
		return
	}

	// filter AllCases
	filteredCases := map[string]cases.E2ECase{}
	for name, c := range AllCases {
		tags := c.Tags()
		if caseDomain, ok := tags[cases.TagKeyDomain]; ok && caseDomain == domain {
			filteredCases[name] = c
		}
	}
	AllCases = filteredCases

	// filter CaseGroups
	for groupName, groupCases := range CaseGroups {
		filteredGroupCases := []cases.E2ECase{}
		for _, c := range groupCases {
			tags := c.Tags()
			if caseDomain, ok := tags[cases.TagKeyDomain]; ok && caseDomain == domain {
				filteredGroupCases = append(filteredGroupCases, c)
			}
		}
		CaseGroups[groupName] = filteredGroupCases
	}

	fmt.Printf("After filtering: %d cases remain\n", len(AllCases))
}

func printAllCasesName() {
	cases := pie.Keys(AllCases)
	sort.Strings(cases)
	for k, v := range cases {
		cases[k] = fmt.Sprintf("%d.%s", k+1, v)
	}
	fmt.Printf("All cases:\n%s\n--------\n", strings.Join(cases, "\n"))
}

func printCaseGroups() {
	group2CaseNames := map[string][]string{}
	for groupName, cases := range CaseGroups {
		if _, ok := group2CaseNames[groupName]; !ok {
			group2CaseNames[groupName] = []string{}
		}
		for _, ca := range cases {
			group2CaseNames[groupName] = append(group2CaseNames[groupName], ca.Name())
		}
	}

	// sorted group name
	groups := pie.Keys(group2CaseNames)
	groups = pie.SortUsing(groups, func(a, b string) bool {
		return reverse(strings.ToLower(a)) < reverse(strings.ToLower(b))
	})

	for _, g := range groups {
		names := group2CaseNames[g]
		fmt.Printf("Case group: %s, case number: %d\n", g, len(names))
		names = pie.SortUsing(names, func(a, b string) bool { return a < b })
		for _, n := range names {
			fmt.Printf("- %s\n", n)
		}
	}
}

func reverse(s string) string {
	runes := []rune(s)
	for i, j := 0, len(runes)-1; i < j; i, j = i+1, j-1 {
		runes[i], runes[j] = runes[j], runes[i]
	}
	return string(runes)
}
