package migration

import (
	"encoding/json"
	"errors"
	"time"

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

	"github.com/tidbcloud/tidb-management-service/dedicated-global-service/infra/repoimpl/schema"
)

var (
	raftGp3DefaultStorageGi  uint32 = 40
	raftGp3DefaultIops       uint32 = 3000
	raftGp3DefaultThroughPut uint32 = 125
)

// Add Standard SKUs for AWS ARM64 TiKV components
var _ = AddDBMigrateRecord(
	"202503201800",
	func(db *gorm.DB) error {
		// Define timestamp and user from input
		timestamp := time.Now()

		return db.Transaction(func(tx *gorm.DB) error {
			// STEP 1: Fetch original ARM64 TiKV components with gp3 storage
			var originalUnits []schema.ComponentTierUnit

			subQuery := tx.Model(&schema.ComponentTierUnit{}).
				Select("component_tier_units.id").
				Joins("JOIN instance_types ON component_tier_units.instance_type_id = instance_types.id AND instance_types.cpu_arch = 'arm64'")

			if err := tx.Model(&schema.ComponentTierUnit{}).
				Where("id IN (?)", subQuery).
				Where("component_type LIKE ?", "%tikv%").
				Where("`key` LIKE ?", "%gp3%").
				Where("`key` NOT LIKE ?", "%:Standard").
				Find(&originalUnits).Error; err != nil {
				return err
			}

			// STEP 2: Create Standard variants
			for _, unit := range originalUnits {
				// Create Standard suffix variant
				standardUnit := schema.ComponentTierUnit{
					Model: gorm.Model{
						CreatedAt: timestamp,
						UpdatedAt: timestamp,
					},
					Key:                    unit.Key + ":Standard",
					Priority:               unit.Priority,
					ComponentType:          unit.ComponentType,
					ProviderId:             unit.ProviderId,
					ComponentTierId:        unit.ComponentTierId,
					InstanceTypeId:         unit.InstanceTypeId,
					StorageTemplateId:      unit.StorageTemplateId,
					DefaultNodeNum:         unit.DefaultNodeNum,
					MinStorageGi:           unit.MinStorageGi,
					MaxStorageGi:           unit.MaxStorageGi,
					DefaultStorageGi:       unit.DefaultStorageGi,
					DefaultIops:            unit.DefaultIops,
					DefaultThroughPut:      unit.DefaultThroughPut,
					MaxIops:                unit.MaxIops,
					MinIops:                unit.MinIops,
					IopsPerGi:              unit.IopsPerGi,
					MaxThroughPut:          unit.MaxThroughPut,
					MinThroughPut:          unit.MinThroughPut,
					ThroughputMibpsPerGi:   unit.ThroughputMibpsPerGi,
					ThroughputMibpsPerIops: unit.ThroughputMibpsPerIops,
				}

				if err := tx.
					Clauses(clause.OnConflict{
						Columns:   []clause.Column{{Name: "key"}},
						UpdateAll: true, // Update all columns except primary key
					}).Save(&standardUnit).Error; err != nil {
					return err
				}
			}

			// STEP 3: Update raft fields for Standard and variants
			// Get newly created Standard variants
			var standardVariants []schema.ComponentTierUnit
			if err := tx.Model(&schema.ComponentTierUnit{}).
				Where("`key` LIKE ?", "%:Standard").
				Find(&standardVariants).Error; err != nil {
				return err
			}

			// Get the gp3 storage template ID
			var gp3StorageTemplateId uint64
			if err := tx.Model(&schema.StorageTemplate{}).
				Where("volume_type = ?", "gp3").
				Select("id").
				First(&gp3StorageTemplateId).Error; err != nil {
				return err
			}

			// Update Standard variants
			for _, variant := range standardVariants {
				// Set Standard raft storage values
				if err := tx.Model(&schema.ComponentTierUnit{}).
					Where("id = ?", variant.ID).
					Updates(map[string]any{
						"raft_storage_template_id": gp3StorageTemplateId,
						"raft_default_storage_gi":  raftGp3DefaultStorageGi,
						"raft_default_iops":        raftGp3DefaultIops,
						"raft_default_through_put": raftGp3DefaultThroughPut,
						"updated_at":               timestamp,
					}).Error; err != nil {
					return err
				}
			}

			// STEP 4: Update tier_unit_conditions to include new variants
			for _, unit := range originalUnits {
				// Find conditions referencing this unit
				var condition schema.TierUnitCondition
				if err := tx.Model(&schema.TierUnitCondition{}).
					Where("`values` LIKE ?", "%"+unit.Key+"%").
					First(&condition).Error; err != nil {
					if errors.Is(err, gorm.ErrRecordNotFound) {
						// No conditions found, continue to next unit
						continue
					}
					return err
				}

				// Update each condition
				standardKey := unit.Key + ":Standard"

				var values []string
				if err := json.Unmarshal([]byte(condition.Values), &values); err != nil {
					return err
				}

				values = lo.Uniq(append(values, standardKey))
				valuesBytes, err := json.Marshal(values)
				if err != nil {
					return err
				}

				// Update the condition
				if err := tx.Model(&schema.TierUnitCondition{}).
					Where("id = ?", condition.ID).
					Updates(map[string]any{
						"values":     string(valuesBytes),
						"updated_at": timestamp,
					}).Error; err != nil {
					return err
				}
			}

			// STEP 5: Update tier_tier_units to include new variants in mapping
			tierIDMap := lo.GroupBy(originalUnits, func(unit schema.ComponentTierUnit) uint64 {
				return unit.ComponentTierId
			})

			// For each component_tier_id, update the tier_tier_units
			for tierID, units := range tierIDMap {
				// Find the tier_tier_unit record
				var tierTierUnit schema.TierTierUnit
				result := tx.Model(&schema.TierTierUnit{}).Where("component_tier_id = ?", tierID).First(&tierTierUnit)

				// Skip if not found
				if result.Error != nil {
					if errors.Is(result.Error, gorm.ErrRecordNotFound) {
						continue
					}
					return result.Error
				}

				// For each unit, add new items to the mapping
				for _, unit := range units {
					standardKey := unit.Key + ":Standard"

					// Find existing items with the original key and add new variants
					for _, item := range tierTierUnit.TierUnitMapping.Items {
						if item.SkuKey == unit.Key {
							// Create standard entry
							standardItem := &schema.TierUnitMappingItem{
								TenantIDs:    item.TenantIDs,
								RegionNames:  item.RegionNames,
								ProviderName: item.ProviderName,
								SkuKey:       standardKey,
							}

							// Add new items
							tierTierUnit.TierUnitMapping.Items = append(
								tierTierUnit.TierUnitMapping.Items,
								standardItem,
							)
							break
						}
					}
				}

				// Update the record
				tierTierUnit.UpdatedAt = timestamp
				if err := tx.Save(&tierTierUnit).Error; err != nil {
					return err
				}
			}

			return nil
		})
	},
)
