package migration

import (
	"encoding/json"
	"errors"
	"strings"
	"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 (
	raftIo2DefaultStorageGi uint32 = 40
	raftIo2DefaultIops      uint32 = 4000
)

// Add Performance SKUs for AWS ARM64 TiKV components
var _ = AddDBMigrateRecord(
	"202503271100",
	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").
				Where("`key` NOT LIKE ?", "%:Performance").
				Find(&originalUnits).Error; err != nil {
				return err
			}

			// STEP 2: Create Performance variants
			for _, unit := range originalUnits {
				// Create Performance suffix variant
				performanceUnit := schema.ComponentTierUnit{
					Model: gorm.Model{
						CreatedAt: timestamp,
						UpdatedAt: timestamp,
					},
					Key:                    unit.Key + ":Performance",
					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
					}).Create(&performanceUnit).Error; err != nil {
					return err
				}
			}

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

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

			// Update Performance variants
			for _, variant := range performanceVariants {
				// Set Performance raft storage values
				if err := tx.Model(&schema.ComponentTierUnit{}).
					Where("id = ?", variant.ID).
					Updates(map[string]any{
						"raft_storage_template_id": io2StorageTemplateId,
						"raft_default_storage_gi":  raftIo2DefaultStorageGi,
						"raft_default_iops":        raftIo2DefaultIops,
						"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
				performanceKey := unit.Key + ":Performance"

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

				values = lo.Uniq(append(values, performanceKey))
				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 {
					performanceKey := unit.Key + ":Performance"

					// Check if the performance key already exists in the mapping
					if lo.ContainsBy(tierTierUnit.TierUnitMapping.Items, func(item *schema.TierUnitMappingItem) bool {
						return item.SkuKey == performanceKey
					}) {
						continue
					}

					// Find the plus sku in this spu mapping
					plusSkuItem, ok := lo.Find(
						tierTierUnit.TierUnitMapping.Items,
						func(item *schema.TierUnitMappingItem) bool {
							return strings.Contains(item.SkuKey, "io2")
						},
					)
					// If not found plus sku, skip.
					if !ok {
						continue
					}

					// Create new item based on the plus sku item
					performanceItem := &schema.TierUnitMappingItem{
						TenantIDs:    plusSkuItem.TenantIDs,
						RegionNames:  plusSkuItem.RegionNames,
						ProviderName: plusSkuItem.ProviderName,
						SkuKey:       performanceKey,
					}

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

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

			return nil
		})
	},
)
