package migration

import (
	"encoding/json"
	"errors"
	"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"
)

const (
	release20260226GcpTidbSkuKey = "gcp:tidb:n2-custom-64-131072"
	release20260226GcpTikvSkuKey = "gcp:tikv:n2-standard-64:pd-ssd:0:0"
	release20260226GcpPdSkuKey   = "gcp:pd:n2-custom-32-65536:pd-ssd:0:0"
)

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

	release20260226GcpInstanceTypes = []*schema.InstanceType{
		{InstanceType: "n2-custom-64-131072", CPUNum: 64, MemorySizeGi: 128, CpuArch: "amd64"},
		{InstanceType: "n2-standard-64", CPUNum: 64, MemorySizeGi: 256, CpuArch: "amd64"},
	}

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

	release20260226GcpSpus = []*schema.ComponentTier{
		{ComponentType: "tidb", Name: "64C128G", Version: "V1", CPUNum: 64, MemorySizeGi: 128},
		{ComponentType: "tikv", Name: "64C256G", Version: "V1", CPUNum: 64, MemorySizeGi: 256},
	}

	release20260226GcpSkus = []*schema.ComponentTierUnit{
		{Key: release20260226GcpTidbSkuKey, ComponentType: "tidb", Priority: 999, DefaultNodeNum: 2},
		{
			Key:              release20260226GcpTikvSkuKey,
			ComponentType:    "tikv",
			Priority:         999,
			DefaultNodeNum:   3,
			DefaultStorageGi: 500,
			MinStorageGi:     200,
			MaxStorageGi:     4096,
			DefaultIops:      0,
		},
	}

	release20260226GcpTimestamp = time.Now()
)

// Add new GCP n2 SKUs:
//   - TiDB 64C128G: n2-custom-64-131072
//   - TiKV 64C256G: n2-standard-64
var _ = AddDBMigrateRecord(
	"release_20260226_gcp",
	func(db *gorm.DB) error {
		if err := getProviderForRelease20260226Gcp(db); err != nil {
			return err
		}
		if err := createInstancesForRelease20260226Gcp(db); err != nil {
			return err
		}
		if err := getStorageTypeForRelease20260226Gcp(db); err != nil {
			return err
		}
		if err := ensureSPUsForRelease20260226Gcp(db); err != nil {
			return err
		}
		if err := createSKUsForRelease20260226Gcp(db); err != nil {
			return err
		}
		if err := bindTikvToPdForRelease20260226Gcp(db); err != nil {
			return err
		}
		return nil
	},
)

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

func createInstancesForRelease20260226Gcp(db *gorm.DB) error {
	lo.ForEach(release20260226GcpInstanceTypes, func(i *schema.InstanceType, _ int) {
		i.ProviderId = uint64(release20260226GcpProvider.ID)
		i.CreatedAt = release20260226GcpTimestamp
		i.UpdatedAt = release20260226GcpTimestamp
	})

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

	if err := db.Where("instance_type IN ?", lo.Map(release20260226GcpInstanceTypes, func(i *schema.InstanceType, _ int) string {
		return i.InstanceType
	})).Find(&release20260226GcpInstanceTypes).Error; err != nil {
		return err
	}

	return nil
}

func getStorageTypeForRelease20260226Gcp(db *gorm.DB) error {
	if err := db.Where("provider_id = ? AND volume_type = ?", release20260226GcpProvider.ID, release20260226GcpPdssdStorage.VolumeType).
		First(&release20260226GcpPdssdStorage).Error; err != nil {
		return err
	}
	return nil
}

func ensureSPUsForRelease20260226Gcp(db *gorm.DB) error {
	lo.ForEach(release20260226GcpSpus, 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, "")
	})

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

	if err := db.Where("`key` IN ?", lo.Map(release20260226GcpSpus, func(i *schema.ComponentTier, _ int) string {
		return i.Key
	})).Find(&release20260226GcpSpus).Error; err != nil {
		return err
	}

	return nil
}

func createSKUsForRelease20260226Gcp(db *gorm.DB) error {
	for _, sku := range release20260226GcpSkus {
		sku.ProviderId = uint64(release20260226GcpProvider.ID)
		instance, found := lo.Find(release20260226GcpInstanceTypes, func(i *schema.InstanceType) bool {
			return strings.Contains(sku.Key, i.InstanceType)
		})
		if !found {
			return fmt.Errorf("failed to find instance type for sku %s", sku.Key)
		}
		sku.InstanceTypeId = uint64(instance.ID)

		spu, found := lo.Find(release20260226GcpSpus, func(s *schema.ComponentTier) bool {
			return s.ComponentType == sku.ComponentType && s.CPUNum == instance.CPUNum && s.MemorySizeGi == instance.MemorySizeGi
		})
		if !found {
			return fmt.Errorf("failed to find spu for sku %s", sku.Key)
		}
		sku.ComponentTierId = uint64(spu.ID)

		if sku.ComponentType == "tikv" || sku.ComponentType == "tiflash" || sku.ComponentType == "pd" {
			sku.StorageTemplateId = lo.ToPtr(uint64(release20260226GcpPdssdStorage.ID))
		}
		if sku.DefaultThroughPut == nil {
			sku.DefaultThroughPut = lo.ToPtr(uint32(0))
		}
		sku.CreatedAt = release20260226GcpTimestamp
		sku.UpdatedAt = release20260226GcpTimestamp
	}

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

	if err := db.Where("`key` IN ?", lo.Map(release20260226GcpSkus, func(i *schema.ComponentTierUnit, _ int) string {
		return i.Key
	})).Find(&release20260226GcpSkus).Error; err != nil {
		return err
	}

	return nil
}

func bindTikvToPdForRelease20260226Gcp(db *gorm.DB) error {
	var pdSku schema.ComponentTierUnit
	if err := db.Where("`key` = ?", release20260226GcpPdSkuKey).First(&pdSku).Error; err != nil {
		return fmt.Errorf("failed to query pd sku %s: %w", release20260226GcpPdSkuKey, err)
	}

	var condition schema.TierUnitCondition
	err := db.Where("component_tier_unit_id = ? AND `key` = ?", pdSku.ID, globaldedicatedv1.Sku_Condition_ATTACH_TIERUNIT.String()).
		First(&condition).Error
	if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
		return fmt.Errorf("failed to query pd sku binding for sku id %d: %w", pdSku.ID, err)
	}

	existingValues := make([]string, 0)
	if err == nil && condition.Values != "" {
		if err := json.Unmarshal([]byte(condition.Values), &existingValues); err != nil {
			return fmt.Errorf("failed to unmarshal existing pd sku binding values: %w", err)
		}
	}

	newValuesBytes, err := json.Marshal(lo.Uniq(append(existingValues, release20260226GcpTikvSkuKey)))
	if err != nil {
		return fmt.Errorf("failed to marshal new pd sku binding values: %w", err)
	}

	newCondition := &schema.TierUnitCondition{
		ComponentTierUnitId: uint64(pdSku.ID),
		Key:                 globaldedicatedv1.Sku_Condition_ATTACH_TIERUNIT.String(),
		Operator:            globaldedicatedv1.Sku_Condition_IN.String(),
		Values:              string(newValuesBytes),
	}
	newCondition.CreatedAt = release20260226GcpTimestamp
	newCondition.UpdatedAt = release20260226GcpTimestamp

	if err := db.Clauses(clause.OnConflict{
		Columns:   []clause.Column{{Name: "key"}, {Name: "component_tier_unit_id"}},
		UpdateAll: true,
	}).Create(newCondition).Error; err != nil {
		return fmt.Errorf("failed to save pd sku binding for sku id %d: %w", pdSku.ID, err)
	}

	return nil
}
