package migrate

import (
	"context"
	"fmt"

	"github.com/tidbcloud/core-svc/core_ctx/domain/vo"
	"github.com/tidbcloud/core-svc/core_ctx/infra/repo_imp/cvt"
	"github.com/tidbcloud/core-svc/model"
	"github.com/tidbcloud/pkgv2/log"
	"github.com/tidbcloud/pkgv2/logid"
	"gorm.io/gorm"
)

var (
	migrateTierKeyBatchSize = 1000
)

//var _ = AddCoreMigrateRecord(
//	"202211071142",
//	func(db *gorm.DB) error {
//		ctx := context.Background()
//		ctx = logid.WithLogid(ctx, logid.GenLogid())
//
//		params := &MigrateTierKeyParam{
//			Dryrun:                   true,
//			MigrateClusterComponents: true,
//			MigrateClusterSnapshots:  true,
//		}
//		return MigrateAllClusterComponentsAndClusterSnapshots(ctx, db, params)
//	},
//)

type MigrateTierKeyParam struct {
	Dryrun                   bool
	MigrateClusterComponents bool
	MigrateClusterSnapshots  bool
}

func MigrateAllClusterComponentsAndClusterSnapshots(ctx context.Context, db *gorm.DB, params *MigrateTierKeyParam) error {
	ctx = logid.WithLogid(ctx, logid.GenLogid())
	logger := log.WithContextS(ctx)
	logger.Infof("params=%+v", params)

	key2TierUnit, err := loadKey2TierUnit(db)
	if err != nil {
		return err
	}
	key2Tier, err := loadKey2Tier(db)
	if err != nil {
		return err
	}
	id2TierUnitKey, err := loadInternalProfileID2TierUnitKey(ctx, db, key2TierUnit)
	if err != nil {
		return err
	}
	id2TierKey, err := loadLabelProfileID2TierKey(ctx, db, key2Tier)
	if err != nil {
		return err
	}

	if params.MigrateClusterComponents {
		if err := migrateTierKey4ClusterComponents(ctx, db, id2TierUnitKey, id2TierKey, params.Dryrun); err != nil {
			return err
		}
		logger.Infof("migrateTierKey4ClusterComponents done.")
	}
	if params.MigrateClusterSnapshots {
		if err := migrateTierKey4ClusterSnapshots(ctx, db, id2TierUnitKey, id2TierKey, params.Dryrun); err != nil {
			return err
		}
		logger.Infof("migrateTierKey4ClusterSnapshots done.")
	}
	return nil
}

type hashKey4TierUnit string

func buildHashKey4TierUnit(providerName model.Cloud, componentType model.ComponentName, computeType, storageType string, storageIops, storageThroughput uint32) hashKey4TierUnit {
	return hashKey4TierUnit(fmt.Sprintf("%s:%s:%s:%s:%d:%d", providerName, componentType, computeType, storageType, storageIops, storageThroughput))
}

func loadInternalProfileID2TierUnitKey(ctx context.Context, db *gorm.DB, key2TierUnit map[string]*model.TierUnit) (map[model.ID]string, error) {
	logger := log.WithContextS(ctx)

	hk2TierUnitKey := make(map[hashKey4TierUnit]string, len(key2TierUnit))
	for _, each := range key2TierUnit {
		hk := buildHashKey4TierUnit(each.InfraProvider.Provider.Name, each.ComponentType, each.InfraCompute.InstanceType, each.InfraStorage.InstanceType, each.InfraStorage.IOPS, each.InfraStorage.ThroughputMi)
		if _, ok := hk2TierUnitKey[hk]; ok {
			return nil, fmt.Errorf("duplicate hk, hk=%s, id=%d", hk, each.ID)
		}

		hk2TierUnitKey[hk] = each.TierUnitKey
	}

	missingHk2InternalProfileId := map[hashKey4TierUnit]uint64{}
	var internalProfiles []*model.InternalProfile
	if err := db.Model(&model.InternalProfile{}).
		Preload("InfraCompute").Preload("InfraStorage").
		Preload("InfraCompute.InfraProvider").Preload("InfraCompute.InfraProvider.Provider").
		Find(&internalProfiles).Error; err != nil {
		return nil, fmt.Errorf("find InternalProfile: %w", err)
	}
	id2TierUnitKey := make(map[model.ID]string, len(internalProfiles))
	for _, each := range internalProfiles {
		hk := buildHashKey4TierUnit(each.InfraCompute.InfraProvider.Provider.Name, each.ComponentType, each.InfraCompute.InstanceType, each.InfraStorage.InstanceType, each.InfraStorage.IOPS, each.InfraStorage.ThroughputMi)
		tierUnitKey, ok := hk2TierUnitKey[hk]
		if !ok {
			missingHk2InternalProfileId[hk] = each.ID.Uint64()
			continue
		}
		logger.Infof("internalProfileId=%d, tierUnitKey=%s", each.ID, tierUnitKey)

		id2TierUnitKey[each.ID] = tierUnitKey
	}
	if len(missingHk2InternalProfileId) != 0 {
		logger.Errorf("some hk are missing, len=%d, detail=%+v", len(missingHk2InternalProfileId), missingHk2InternalProfileId)
		return nil, fmt.Errorf("some hk are missing, len=%d", len(missingHk2InternalProfileId))
	}

	return id2TierUnitKey, nil
}

func loadLabelProfileID2TierKey(ctx context.Context, db *gorm.DB, key2Tier map[string]*model.Tier) (map[model.ID]string, error) {
	logger := log.WithContextS(ctx)

	var labelProfiles []*model.LabelProfile
	if err := db.Model(&model.LabelProfile{}).Find(&labelProfiles).Error; err != nil {
		return nil, fmt.Errorf("find LabelProfile: %w", err)
	}

	missingTierKey2LabelProfileId := map[vo.TierKey]uint64{}
	id2TierKey := make(map[model.ID]string, len(labelProfiles))
	for _, each := range labelProfiles {
		deployType := cvt.DeployType2pb(each.DeployType)
		componentType := cvt.ComponentType2pb(each.ComponentType)
		tierKey := vo.BuildTierKey(deployType, componentType, each.Name)
		logger.Infof("labelProfileId=%d, tierKey=%s", each.ID, tierKey)

		if _, ok := key2Tier[string(tierKey)]; !ok {
			missingTierKey2LabelProfileId[tierKey] = each.ID.Uint64()
			continue
		}
		id2TierKey[each.ID] = string(tierKey)
	}
	if len(missingTierKey2LabelProfileId) != 0 {
		logger.Errorf("some tiers are missing, len=%d, detail=%+v", len(missingTierKey2LabelProfileId), missingTierKey2LabelProfileId)
		return nil, fmt.Errorf("some hk are missing, len=%d", len(missingTierKey2LabelProfileId))
	}

	return id2TierKey, nil
}

func loadKey2TierUnit(db *gorm.DB) (map[string]*model.TierUnit, error) {
	var tierUnits []*model.TierUnit
	if err := db.Model(&model.TierUnit{}).
		Preload("InfraCompute").Preload("InfraStorage").
		Preload("InfraProvider").Preload("InfraProvider.Provider").
		Find(&tierUnits).Error; err != nil {
		return nil, fmt.Errorf("find tierUnits: %w", err)
	}
	key2TierUnit := make(map[string]*model.TierUnit, len(tierUnits))
	for _, each := range tierUnits {
		key2TierUnit[each.TierUnitKey] = each
	}

	return key2TierUnit, nil
}

func loadKey2Tier(db *gorm.DB) (map[string]*model.Tier, error) {
	var tiers []*model.Tier
	if err := db.Model(&model.Tier{}).Find(&tiers).Error; err != nil {
		return nil, fmt.Errorf("find tiers: %w", err)
	}
	key2Tier := make(map[string]*model.Tier, len(tiers))
	for _, each := range tiers {
		key2Tier[each.TierKey] = each
	}

	return key2Tier, nil
}

func migrateTierKey4ClusterComponents(ctx context.Context, db *gorm.DB, id2TierUnitKey map[model.ID]string, id2TierKey map[model.ID]string, dryrun bool) error {
	logger := log.WithContextS(ctx)

	id := uint64(0)
	for {
		var clusterComponents []*model.ClusterComponent
		if err := db.Model(&model.ClusterComponent{}).Where("id > ?", id).Order("id ASC").Limit(migrateTierKeyBatchSize).
			Find(&clusterComponents).Error; err != nil {
			return fmt.Errorf("find cluster components: %w", err)
		}
		logger.Infof("batchSize=%d, current=%d", migrateTierKeyBatchSize, len(clusterComponents))

		for _, component := range clusterComponents {
			tierUnitKey, ok := id2TierUnitKey[component.InternalProfileID]
			if !ok {
				return fmt.Errorf("can't find tierUnitKey, componentId=%d,internalProfileId=%d", component.ID, component.InternalProfileID)
			}
			tierKey, ok := id2TierKey[component.LabelProfileID]
			if !ok {
				return fmt.Errorf("can't find tierKey, componentId=%d,labelProfileId=%d", component.ID, component.LabelProfileID)
			}
			logger.Infof("update tierKey/tierUnitKey,componentId=%d,tierKey=%s,tierUnitKey=%s", component.ID, tierKey, tierUnitKey)

			if component.TierKey != "" {
				logger.Infof("converted,continue. componentId=%d,curTierKey=%s,curTierUnitKey=%s", component.ID, component.TierKey, component.TierUnitKey)
				continue
			}
			component.TierKey = tierKey
			component.TierUnitKey = tierUnitKey
			if !dryrun {
				if err := component.Update(db, model.ClusterComponentDBSchema.TierKey, model.ClusterComponentDBSchema.TierUnitKey); err != nil {
					return fmt.Errorf("update tierKey/tierUnitKey: %w", err)
				}
			} else {
				logger.Infof("dryrun mode, skip update")
			}
		}

		if len(clusterComponents) < migrateTierKeyBatchSize {
			logger.Infof("all components migrated, break")
			break
		}
		id = clusterComponents[len(clusterComponents)-1].ID.Uint64()
	}

	return nil
}

func migrateTierKey4ClusterSnapshots(ctx context.Context, db *gorm.DB, id2TierUnitKey map[model.ID]string, id2TierKey map[model.ID]string, dryrun bool) error {
	logger := log.WithContextS(ctx)

	id := uint64(0)
	for {
		var clusterSnapshots []*model.ClusterSnapshot
		if err := db.Model(&model.ClusterSnapshot{}).Where("id > ?", id).Order("id ASC").Limit(migrateTierKeyBatchSize).
			Find(&clusterSnapshots).Error; err != nil {
			return fmt.Errorf("find cluster snapshots: %w", err)
		}

		for _, snapshot := range clusterSnapshots {
			if len(snapshot.ComponentStates.Components) == 0 {
				logger.Infof("empty components,continue. snapshotId=%d", snapshot.ID)
				continue
			}
			if snapshot.ComponentStates.Components[0].TierKey != "" {
				logger.Infof("converted,continue. snapshotId=%d", snapshot.ID)
				continue
			}

			newComponents := make([]model.ComponentState, len(snapshot.ComponentStates.Components))
			for idx, component := range snapshot.ComponentStates.Components {
				tierUnitKey, ok := id2TierUnitKey[component.InternalProfileID]
				if !ok {
					return fmt.Errorf("can't find tierUnitKey, snapshotId=%d,componentType=%s,internalProfileId=%d", snapshot.ID, component.ComponentType, component.InternalProfileID)
				}
				tierKey, ok := id2TierKey[component.LabelProfileID]
				if !ok {
					return fmt.Errorf("can't find tierKey, snapshotId=%d,componentType=%s,labelProfileId=%d", snapshot.ID, component.ComponentType, component.LabelProfileID)
				}
				logger.Infof("update tierKey/tierUnitKey, snapshotId=%d,componentType=%s,tierKey=%s,tierUnitKey=%s", snapshot.ID, component.ComponentType, tierKey, tierUnitKey)

				component.TierKey = tierKey
				component.TierUnitKey = tierUnitKey
				newComponents[idx] = component
			}
			// since the component is a valuer not a pointer, we need to write it back.
			snapshot.ComponentStates.Components = newComponents

			if !dryrun {
				if err := snapshot.Update(db, model.ClusterSnapshotDBSchema.ComponentStates); err != nil {
					return fmt.Errorf("update cluster snapshot: %w", err)
				}
			} else {
				logger.Infof("dryrun mode, skip update")
			}
		}

		if len(clusterSnapshots) < migrateTierKeyBatchSize {
			logger.Infof("all snapshots migrated, break")
			break
		}
		id = clusterSnapshots[len(clusterSnapshots)-1].ID.Uint64()
	}

	return nil
}
