//nolint:golines // ignore long lines in migration files
package migration

import (
	"fmt"
	"strings"
	"time"

	"github.com/samber/lo"
	"gorm.io/gorm"
	"gorm.io/gorm/clause"

	globaldedicatedv1 "github.com/tidbcloud/tidb-management-service/api/spec/global/dedicated/v1"
	"github.com/tidbcloud/tidb-management-service/dedicated-global-service/domain/entity"
	"github.com/tidbcloud/tidb-management-service/dedicated-global-service/infra/repoimpl/schema"
	"github.com/tidbcloud/tidb-management-service/utilities/enum"
)

var (
	provider = &schema.Provider{Name: "gcp"}

	instanceTypes = []*schema.InstanceType{
		{InstanceType: "n2d-standard-2", CPUNum: 2, MemorySizeGi: 8, CpuArch: "amd64"},
		{InstanceType: "n2d-standard-4", CPUNum: 4, MemorySizeGi: 16, CpuArch: "amd64"},
		{InstanceType: "n2d-custom-8-16384", CPUNum: 8, MemorySizeGi: 16, CpuArch: "amd64"},
		{InstanceType: "n2d-standard-8", CPUNum: 8, MemorySizeGi: 32, CpuArch: "amd64"},
		{InstanceType: "n2d-highmem-8", CPUNum: 8, MemorySizeGi: 64, CpuArch: "amd64"},
		{InstanceType: "n2d-custom-16-32768", CPUNum: 16, MemorySizeGi: 32, CpuArch: "amd64"},
		{InstanceType: "n2d-standard-16", CPUNum: 16, MemorySizeGi: 64, CpuArch: "amd64"},
		{InstanceType: "n2d-highmem-16", CPUNum: 16, MemorySizeGi: 128, CpuArch: "amd64"},
		{InstanceType: "n2d-custom-32-65536", CPUNum: 32, MemorySizeGi: 64, CpuArch: "amd64"},
		{InstanceType: "n2d-standard-32", CPUNum: 32, MemorySizeGi: 128, CpuArch: "amd64"},
	}

	pdssdStorage = &schema.StorageTemplate{VolumeType: "pd-ssd"}

	spus = []*schema.ComponentTier{
		// TiDB
		{ComponentType: "tidb", Name: "4C16G", Version: "V1", CPUNum: 4, MemorySizeGi: 16},
		{ComponentType: "tidb", Name: "8C16G", Version: "V1", CPUNum: 8, MemorySizeGi: 16},
		{ComponentType: "tidb", Name: "8C32G", Version: "V1", CPUNum: 8, MemorySizeGi: 32},
		{ComponentType: "tidb", Name: "16C32G", Version: "V1", CPUNum: 16, MemorySizeGi: 32},
		{ComponentType: "tidb", Name: "16C64G", Version: "V1", CPUNum: 16, MemorySizeGi: 64},
		{ComponentType: "tidb", Name: "32C64G", Version: "V1", CPUNum: 32, MemorySizeGi: 64},
		{ComponentType: "tidb", Name: "32C128G", Version: "V1", CPUNum: 32, MemorySizeGi: 128},

		// TiFlash
		{ComponentType: "tiflash", Name: "8C64G", Version: "V1", CPUNum: 8, MemorySizeGi: 64},
		{ComponentType: "tiflash", Name: "16C128G", Version: "V1", CPUNum: 16, MemorySizeGi: 128},
		{ComponentType: "tiflash", Name: "32C128G", Version: "V1", CPUNum: 32, MemorySizeGi: 128},

		// TiKV
		{ComponentType: "tikv", Name: "4C16G", Version: "V1", CPUNum: 4, MemorySizeGi: 16},
		{ComponentType: "tikv", Name: "8C32G", Version: "V1", CPUNum: 8, MemorySizeGi: 32},
		{ComponentType: "tikv", Name: "8C64G", Version: "V1", CPUNum: 8, MemorySizeGi: 64},
		{ComponentType: "tikv", Name: "16C64G", Version: "V1", CPUNum: 16, MemorySizeGi: 64},
		{ComponentType: "tikv", Name: "32C128G", Version: "V1", CPUNum: 32, MemorySizeGi: 128},

		// PD
		{ComponentType: "pd", Name: "2C8G", Version: "V1", CPUNum: 2, MemorySizeGi: 8},
		{ComponentType: "pd", Name: "4C16G", Version: "V1", CPUNum: 4, MemorySizeGi: 16},
		{ComponentType: "pd", Name: "16C32G", Version: "V1", CPUNum: 16, MemorySizeGi: 32},
		{ComponentType: "pd", Name: "32C64G", Version: "V1", CPUNum: 32, MemorySizeGi: 64},
	}

	skus = []*schema.ComponentTierUnit{
		// TiDB SKUs
		{Key: "gcp:tidb:n2d-standard-4", ComponentType: "tidb", DefaultNodeNum: 2},
		{Key: "gcp:tidb:n2d-custom-8-16384", ComponentType: "tidb", DefaultNodeNum: 2},
		{Key: "gcp:tidb:n2d-standard-8", ComponentType: "tidb", DefaultNodeNum: 2},
		{Key: "gcp:tidb:n2d-custom-16-32768", ComponentType: "tidb", DefaultNodeNum: 2},
		{Key: "gcp:tidb:n2d-standard-16", ComponentType: "tidb", DefaultNodeNum: 2},
		{Key: "gcp:tidb:n2d-custom-32-65536", ComponentType: "tidb", DefaultNodeNum: 2},
		{Key: "gcp:tidb:n2d-standard-32", ComponentType: "tidb", DefaultNodeNum: 2},

		// TiFlash SKUs
		{Key: "gcp:tiflash:n2d-highmem-8:pd-ssd", ComponentType: "tiflash", DefaultStorageGi: 500, MinStorageGi: 200, MaxStorageGi: 4096},
		{Key: "gcp:tiflash:n2d-highmem-16:pd-ssd", ComponentType: "tiflash", DefaultStorageGi: 500, MinStorageGi: 200, MaxStorageGi: 4096},
		{Key: "gcp:tiflash:n2d-standard-32:pd-ssd", ComponentType: "tiflash", DefaultStorageGi: 500, MinStorageGi: 200, MaxStorageGi: 8192},

		// TiKV SKUs
		{Key: "gcp:tikv:n2d-standard-4:pd-ssd", ComponentType: "tikv", DefaultNodeNum: 3, DefaultStorageGi: 500, MinStorageGi: 200, MaxStorageGi: 2048},
		{Key: "gcp:tikv:n2d-standard-8:pd-ssd", ComponentType: "tikv", DefaultNodeNum: 3, DefaultStorageGi: 500, MinStorageGi: 200, MaxStorageGi: 4096},
		{Key: "gcp:tikv:n2d-highmem-8:pd-ssd", ComponentType: "tikv", DefaultNodeNum: 3, DefaultStorageGi: 500, MinStorageGi: 200, MaxStorageGi: 4096},
		{Key: "gcp:tikv:n2d-standard-16:pd-ssd", ComponentType: "tikv", DefaultNodeNum: 3, DefaultStorageGi: 500, MinStorageGi: 200, MaxStorageGi: 4096},
		{Key: "gcp:tikv:n2d-standard-32:pd-ssd", ComponentType: "tikv", DefaultNodeNum: 3, DefaultStorageGi: 500, MinStorageGi: 200, MaxStorageGi: 4096},

		// PD SKUs
		{Key: "gcp:pd:n2d-standard-2:pd-ssd", ComponentType: "pd", DefaultNodeNum: 3, DefaultStorageGi: 50, MinStorageGi: 50, MaxStorageGi: 50},
		{Key: "gcp:pd:n2d-standard-4:pd-ssd", ComponentType: "pd", DefaultNodeNum: 3, DefaultStorageGi: 50, MinStorageGi: 50, MaxStorageGi: 50},
		{Key: "gcp:pd:n2d-custom-16-32768:pd-ssd", ComponentType: "pd", DefaultNodeNum: 3, DefaultStorageGi: 50, MinStorageGi: 50, MaxStorageGi: 50},
		{Key: "gcp:pd:n2d-custom-32-65536:pd-ssd", ComponentType: "pd", DefaultNodeNum: 3, DefaultStorageGi: 50, MinStorageGi: 50, MaxStorageGi: 50},
	}

	pdTikvBindingConditions = func(in []*schema.ComponentTierUnit) []*schema.ComponentTierUnit {
		for _, sku := range in {
			switch sku.Key {
			case "gcp:pd:n2d-standard-2:pd-ssd":
				sku.TierUnitConditions = []schema.TierUnitCondition{
					{
						Key:      globaldedicatedv1.Sku_Condition_ATTACH_TIERUNIT.String(),
						Operator: globaldedicatedv1.Sku_Condition_IN.String(),
						Values:   `["gcp:tikv:n2d-standard-4:pd-ssd"]`,
					},
				}
			case "gcp:pd:n2d-standard-4:pd-ssd":
				sku.TierUnitConditions = []schema.TierUnitCondition{
					{
						Key:      globaldedicatedv1.Sku_Condition_ATTACH_TIERUNIT.String(),
						Operator: globaldedicatedv1.Sku_Condition_IN.String(),
						Values:   `["gcp:tikv:n2d-standard-8:pd-ssd","gcp:tikv:n2d-highmem-8:pd-ssd","gcp:tikv:n2d-standard-16:pd-ssd"]`,
					},
				}
			case "gcp:pd:n2d-custom-16-32768:pd-ssd":
				sku.TierUnitConditions = []schema.TierUnitCondition{
					{
						Key:      globaldedicatedv1.Sku_Condition_ATTACH_TIERUNIT.String(),
						Operator: globaldedicatedv1.Sku_Condition_IN.String(),
						Values:   `["gcp:tikv:n2d-standard-32:pd-ssd"]`,
					},
				}
			}
		}
		return in
	}

	timestamp = time.Now()
)

var _ = AddDBMigrateRecord(
	"release_20260120_gcp_n2d",
	func(db *gorm.DB) error {
		if err := getProvider(db); err != nil {
			return err
		}
		if err := createInstances(db); err != nil {
			return err
		}
		if err := getStorageType(db); err != nil {
			return err
		}
		if err := ensureSPUs(db); err != nil {
			return err
		}
		if err := createSKUs(db); err != nil {
			return err
		}
		if err := bindTikvToPds(db); err != nil {
			return err
		}
		return nil
	},
)

func getProvider(db *gorm.DB) error {
	if err := db.Where("name = ?", provider.Name).First(&provider).Error; err != nil {
		return err
	}
	return nil
}

func createInstances(db *gorm.DB) error {
	// Set provider ID for instance types.
	lo.ForEach(instanceTypes, func(i *schema.InstanceType, _ int) {
		i.ProviderId = uint64(provider.ID)
		i.CreatedAt = timestamp
		i.UpdatedAt = timestamp
	})

	// Create instance types.
	if err := db.Clauses(clause.OnConflict{
		Columns:   []clause.Column{{Name: "instance_type"}},
		UpdateAll: true,
	}).CreateInBatches(instanceTypes, 20).Error; err != nil {
		return err
	}

	// Re-query instance types to get their ids.
	if err := db.Where("instance_type IN ?", lo.Map(instanceTypes, func(i *schema.InstanceType, _ int) string {
		return i.InstanceType
	})).Find(&instanceTypes).Error; err != nil {
		return err
	}

	return nil
}

func getStorageType(db *gorm.DB) error {
	if err := db.Where("volume_type = ?", pdssdStorage.VolumeType).First(&pdssdStorage).Error; err != nil {
		return err
	}
	return nil
}

func ensureSPUs(db *gorm.DB) error {
	// Set display name and key for SPUs.
	lo.ForEach(spus, func(spu *schema.ComponentTier, _ int) {
		spu.DisplayName = fmt.Sprintf("%d vCPU, %d GiB", spu.CPUNum, spu.MemorySizeGi)
		ct, _ := enum.FromString[globaldedicatedv1.ComponentType](spu.ComponentType)
		spu.Key = entity.BuildSpuKey(globaldedicatedv1.ComponentType(ct), spu.Name, "")
	})

	// Create SPUs.
	if err := db.Clauses(clause.OnConflict{
		Columns:   []clause.Column{{Name: "key"}},
		UpdateAll: true,
	}).CreateInBatches(spus, 20).Error; err != nil {
		return err
	}

	// Re-query SPUs to get their ids.
	if err := db.Where("`key` IN ?", lo.Map(spus, func(i *schema.ComponentTier, _ int) string {
		return i.Key
	})).Find(&spus).Error; err != nil {
		return err
	}

	return nil
}

func createSKUs(db *gorm.DB) error {
	// Set provider ID and component tier ID for SKUs.
	// If the component has storage, set the storage template ID.
	lo.ForEach(skus, func(sku *schema.ComponentTierUnit, _ int) {
		sku.ProviderId = uint64(provider.ID)
		instance, _ := lo.Find(instanceTypes, func(i *schema.InstanceType) bool {
			return strings.Contains(sku.Key, i.InstanceType)
		})
		sku.InstanceTypeId = uint64(instance.ID)
		spu, _ := lo.Find(spus, func(s *schema.ComponentTier) bool {
			return s.ComponentType == sku.ComponentType && s.CPUNum == instance.CPUNum && s.MemorySizeGi == instance.MemorySizeGi
		})
		sku.ComponentTierId = uint64(spu.ID)
		// tikv, tiflash, and pd components require a storage template ID.
		if sku.ComponentType == "tikv" || sku.ComponentType == "tiflash" || sku.ComponentType == "pd" {
			sku.StorageTemplateId = lo.ToPtr(uint64(pdssdStorage.ID))
		}
	})

	// Create SKUs.
	if err := db.
		Omit(clause.Associations).
		Clauses(clause.OnConflict{
			Columns:   []clause.Column{{Name: "key"}},
			UpdateAll: true,
		}).CreateInBatches(skus, 20).Error; err != nil {
		return err
	}

	// Re-query SKUs to get their ids.
	if err := db.Where("`key` IN ?", lo.Map(skus, func(i *schema.ComponentTierUnit, _ int) string {
		return i.Key
	})).Find(&skus).Error; err != nil {
		return err
	}

	return nil
}

func bindTikvToPds(db *gorm.DB) error {
	// Apply binding conditions to PD SKUs.
	pdTikvBindingConditions(skus)

	// Get all PD SKUs that have conditions.
	pdSkus := lo.Filter(skus, func(sku *schema.ComponentTierUnit, _ int) bool {
		return sku.ComponentType == "pd" && len(sku.TierUnitConditions) > 0
	})

	// Set the component tier unit ID for each condition.
	conditions := lo.Map(pdSkus, func(sku *schema.ComponentTierUnit, _ int) schema.TierUnitCondition {
		condition := sku.TierUnitConditions[0]
		condition.ComponentTierUnitId = uint64(sku.ID)
		return condition
	})

	// Save all conditions.
	if err := db.Clauses(clause.OnConflict{
		Columns:   []clause.Column{{Name: "key"}, {Name: "component_tier_unit_id"}},
		UpdateAll: true,
	}).CreateInBatches(conditions, 20).Error; err != nil {
		return err
	}

	return nil
}
