package cvt

import (
	"encoding/json"
	"errors"
	"fmt"

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

	globalcommonv1 "github.com/tidbcloud/tidb-management-service/api/spec/global/common/v1"
	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/common"
	"github.com/tidbcloud/tidb-management-service/dedicated-global-service/infra/repoimpl/schema"
	"github.com/tidbcloud/tidb-management-service/utilities/enum"
)

func SkuEnToModel(en *entity.Sku, providerId uint64) *schema.ComponentTierUnit {
	m := &schema.ComponentTierUnit{
		Model: gorm.Model{
			ID: uint(en.Id),
		},
		Key:             en.Key,
		Priority:        en.Priority,
		ComponentType:   en.ComponentType.String(),
		ProviderId:      providerId,
		ComponentTierId: en.GetSpuId(),
		InstanceTypeId:  en.GetInstanceMuId(),
		DefaultNodeNum:  uint32(en.DefaultNodeCount),

		// Data storage related fields.
		DefaultIops:            uint32(en.GetDataStorage().GetIopsFixed().GetIops()),
		MaxIops:                uint32(en.GetDataStorage().GetIopsMax()),
		MinIops:                uint32(en.GetDataStorage().GetIopsMin()),
		IopsPerGi:              en.GetDataStorage().GetIopsDynamic().GetIopsPerSizeGi(),
		DefaultThroughPut:      lo.ToPtr(uint32(en.GetDataStorage().GetThroughputFixed().GetThroughputMibps())),
		MaxThroughPut:          uint32(en.GetDataStorage().GetThroughputMibpsMax()),
		MinThroughPut:          uint32(en.GetDataStorage().GetThroughputMibpsMin()),
		ThroughputMibpsPerGi:   en.GetDataStorage().GetThroughputDynamic().GetThroughputMibpsPerSizeGi(),
		ThroughputMibpsPerIops: en.GetDataStorage().GetThroughputDynamic().GetThroughputMibpsPerIops(),
		DefaultStorageGi:       uint32(en.GetDataStorage().GetSizeGiDefault()),
		MaxStorageGi:           uint32(en.GetDataStorage().GetSizeGiMax()),
		MinStorageGi:           uint32(en.GetDataStorage().GetSizeGiMin()),
		StorageTemplateId:      lo.EmptyableToPtr(en.GetDataStorage().GetStorageMuId()),

		// Raft storage related fields.
		RaftDefaultIops:            uint32(en.GetRaftlogStorage().GetIopsFixed().GetIops()),
		RaftMaxIops:                uint32(en.GetRaftlogStorage().GetIopsMax()),
		RaftMinIops:                uint32(en.GetRaftlogStorage().GetIopsMin()),
		RaftIopsPerGi:              en.GetRaftlogStorage().GetIopsDynamic().GetIopsPerSizeGi(),
		RaftDefaultThroughPut:      uint32(en.GetRaftlogStorage().GetThroughputFixed().GetThroughputMibps()),
		RaftMaxThroughPut:          uint32(en.GetRaftlogStorage().GetThroughputMibpsMax()),
		RaftMinThroughPut:          uint32(en.GetRaftlogStorage().GetThroughputMibpsMin()),
		RaftThroughputMibpsPerGi:   en.GetRaftlogStorage().GetThroughputDynamic().GetThroughputMibpsPerSizeGi(),
		RaftThroughputMibpsPerIops: en.GetRaftlogStorage().GetThroughputDynamic().GetThroughputMibpsPerIops(),
		RaftDefaultStorageGi:       uint32(en.GetRaftlogStorage().GetSizeGiDefault()),
		RaftMaxStorageGi:           uint32(en.GetRaftlogStorage().GetSizeGiMax()),
		RaftMinStorageGi:           uint32(en.GetRaftlogStorage().GetSizeGiMin()),
		RaftStorageTemplateId:      lo.EmptyableToPtr(en.GetRaftlogStorage().GetStorageMuId()),
	}

	return m
}

func SkuModelToEn(m *schema.ComponentTierUnit) (*entity.Sku, error) {
	if m == nil {
		return nil, errors.New("nil tier unit model")
	}

	out := make([]*globaldedicatedv1.Sku_Condition, 0)
	for i := range m.TierUnitConditions {
		v, err := SkuConditionModel2En(&m.TierUnitConditions[i])
		if err != nil {
			return nil, err
		}
		out = append(out, v)
	}

	ct, err := enum.FromString[globaldedicatedv1.ComponentType](m.ComponentType)
	if err != nil {
		return nil, fmt.Errorf("invalid component type: %s", m.ComponentType)
	}

	providerName, err := providerModel2En(m.Provider.Name)
	if err != nil {
		return nil, err
	}

	arch, err := enum.FromString[globaldedicatedv1.InstanceMu_CpuArch](m.InstanceType.CpuArch)
	if err != nil {
		return nil, fmt.Errorf("invalid cpu arch: %s", m.InstanceType.CpuArch)
	}

	instanceCpuGeneration, err := common.GetInstanceCpuGeneration(m)
	if err != nil {
		return nil, fmt.Errorf("failed to get instance cpu generation: %w", err)
	}

	if m.ComponentTier.Name == "" {
		m.ComponentTier.Name = common.GetSpuName(m.ComponentTier.Key)
	}

	res := &entity.Sku{
		Id:               uint64(m.ID),
		SpuId:            m.ComponentTierId,
		SpuKey:           m.ComponentTier.Key,
		SpuName:          m.ComponentTier.Name,
		SpuDisplayName:   m.ComponentTier.DisplayName,
		SpuVersion:       m.ComponentTier.Version,
		Key:              m.Key,
		CloudProvider:    providerName,
		ComponentType:    globaldedicatedv1.ComponentType(ct),
		InstanceMuId:     m.InstanceTypeId,
		CpuArch:          globaldedicatedv1.InstanceMu_CpuArch(arch),
		CpuGeneration:    instanceCpuGeneration,
		InstanceType:     m.InstanceType.InstanceType,
		CpuCore:          int32(m.InstanceType.CPUNum),
		MemoryGi:         int32(m.InstanceType.MemorySizeGi),
		DefaultNodeCount: int32(m.DefaultNodeNum),
		Conditions:       out,
		Priority:         m.Priority,
		DataStorage:      nil, // fill later
		RaftlogStorage:   nil, // fill later
	}

	// fill DataStorage
	if m.StorageTemplateId != nil {
		vt, err := enum.FromString[globaldedicatedv1.StorageMu_VolumeType](m.StorageTemplate.VolumeType)
		if err != nil {
			return nil, fmt.Errorf("invalid volume type: %s", m.StorageTemplate.VolumeType)
		}
		res.DataStorage = &globaldedicatedv1.Sku_Storage{
			DiskType:    globaldedicatedv1.Sku_DATA,
			StorageMuId: lo.FromPtr(m.StorageTemplateId),
			VolumeType:  globaldedicatedv1.StorageMu_VolumeType(vt),
			Iops: &globaldedicatedv1.Sku_Storage_IopsFixed_{
				IopsFixed: &globaldedicatedv1.Sku_Storage_IopsFixed{
					Iops: int32(m.DefaultIops),
				},
			},
			IopsMax: int32(m.MaxIops),
			IopsMin: int32(m.MinIops),
			Throughput: &globaldedicatedv1.Sku_Storage_ThroughputFixed_{
				ThroughputFixed: &globaldedicatedv1.Sku_Storage_ThroughputFixed{
					ThroughputMibps: int32(lo.FromPtr(m.DefaultThroughPut)),
				},
			},
			ThroughputMibpsMax: int32(m.MaxThroughPut),
			ThroughputMibpsMin: int32(m.MinThroughPut),
			SizeGiDefault:      int32(m.DefaultStorageGi),
			SizeGiMin:          int32(m.MinStorageGi),
			SizeGiMax:          int32(m.MaxStorageGi),
		}
		// If there is not-null dynamic storage fields, overwrite the fixed fields.
		if m.IopsPerGi != 0 {
			res.DataStorage.Iops = &globaldedicatedv1.Sku_Storage_IopsDynamic_{
				IopsDynamic: &globaldedicatedv1.Sku_Storage_IopsDynamic{
					IopsPerSizeGi: m.IopsPerGi,
				},
			}
		}
		if m.ThroughputMibpsPerGi != 0 && m.ThroughputMibpsPerIops != 0 {
			res.DataStorage.Throughput = &globaldedicatedv1.Sku_Storage_ThroughputDynamic_{
				ThroughputDynamic: &globaldedicatedv1.Sku_Storage_ThroughputDynamic{
					ThroughputMibpsPerSizeGi: m.ThroughputMibpsPerGi,
					ThroughputMibpsPerIops:   m.ThroughputMibpsPerIops,
				},
			}
		}
	}

	// fill RaftlogStorage
	if m.RaftStorageTemplateId != nil {
		vt, err := enum.FromString[globaldedicatedv1.StorageMu_VolumeType](m.RaftStorageTemplate.VolumeType)
		if err != nil {
			return nil, fmt.Errorf("invalid volume type: %s", m.RaftStorageTemplate.VolumeType)
		}
		res.RaftlogStorage = &globaldedicatedv1.Sku_Storage{
			DiskType:    globaldedicatedv1.Sku_RAFT_LOG,
			StorageMuId: lo.FromPtr(m.RaftStorageTemplateId),
			VolumeType:  globaldedicatedv1.StorageMu_VolumeType(vt),
			Iops: &globaldedicatedv1.Sku_Storage_IopsFixed_{
				IopsFixed: &globaldedicatedv1.Sku_Storage_IopsFixed{
					Iops: int32(m.RaftDefaultIops),
				},
			},
			IopsMax: int32(m.RaftMaxIops),
			IopsMin: int32(m.RaftMinIops),
			Throughput: &globaldedicatedv1.Sku_Storage_ThroughputFixed_{
				ThroughputFixed: &globaldedicatedv1.Sku_Storage_ThroughputFixed{
					ThroughputMibps: int32(m.RaftDefaultThroughPut),
				},
			},
			ThroughputMibpsMax: int32(m.RaftMaxThroughPut),
			ThroughputMibpsMin: int32(m.RaftMinThroughPut),
			SizeGiDefault:      int32(m.RaftDefaultStorageGi),
			SizeGiMin:          int32(m.RaftMinStorageGi),
			SizeGiMax:          int32(m.RaftMaxStorageGi),
		}
		if m.RaftIopsPerGi != 0 {
			res.RaftlogStorage.Iops = &globaldedicatedv1.Sku_Storage_IopsDynamic_{
				IopsDynamic: &globaldedicatedv1.Sku_Storage_IopsDynamic{
					IopsPerSizeGi: m.RaftIopsPerGi,
				},
			}
		}
		if m.RaftThroughputMibpsPerGi != 0 && m.RaftThroughputMibpsPerIops != 0 {
			res.RaftlogStorage.Throughput = &globaldedicatedv1.Sku_Storage_ThroughputDynamic_{
				ThroughputDynamic: &globaldedicatedv1.Sku_Storage_ThroughputDynamic{
					ThroughputMibpsPerSizeGi: m.RaftThroughputMibpsPerGi,
					ThroughputMibpsPerIops:   m.RaftThroughputMibpsPerIops,
				},
			}
		}
	}

	return res, nil
}

func providerModel2En(provider string) (globalcommonv1.CloudProvider_Name, error) {
	providerCvt, err := enum.FromString[globalcommonv1.CloudProvider_Name](provider)
	if err != nil {
		return 0, err
	}
	return globalcommonv1.CloudProvider_Name(providerCvt), nil
}

func SkuConditionEnToModel(en *globaldedicatedv1.Sku_Condition, skuId uint64) (*schema.TierUnitCondition, error) {
	if en == nil {
		return nil, errors.New("nil tier unit condition")
	}

	if en.Key == globaldedicatedv1.Sku_Condition_KEY_UNSPECIFIED {
		return nil, fmt.Errorf("unsupport key: %s", en.Key)
	}
	if en.Operator == globaldedicatedv1.Sku_Condition_OPERATOR_UNSPECIFIED {
		return nil, fmt.Errorf("unsupport operator: %s", en.Operator)
	}
	if len(en.Values) == 0 {
		return nil, errors.New("empty values")
	}

	data, err := json.Marshal(en.Values)
	if err != nil {
		return nil, err
	}

	return &schema.TierUnitCondition{
		Key:                 en.Key.String(),
		Operator:            en.Operator.String(),
		Values:              string(data),
		ComponentTierUnitId: skuId,
	}, nil
}

func SkuConditionModel2En(m *schema.TierUnitCondition) (*globaldedicatedv1.Sku_Condition, error) {
	if m == nil {
		return nil, errors.New("nil tier unit condition model")
	}

	key, err := enum.FromString[globaldedicatedv1.Sku_Condition_Key](m.Key)
	if err != nil {
		return nil, err
	}

	operator, err := enum.FromString[globaldedicatedv1.Sku_Condition_Operator](m.Operator)
	if err != nil {
		return nil, err
	}

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

	return &globaldedicatedv1.Sku_Condition{
		Key:      globaldedicatedv1.Sku_Condition_Key(key),
		Operator: globaldedicatedv1.Sku_Condition_Operator(operator),
		Values:   values,
	}, nil
}
