package convert

import (
	"bytes"
	"context"
	"encoding/json"
	"errors"
	"fmt"
	"time"

	"github.com/samber/lo"
	coresvcmodel "github.com/tidbcloud/core-svc/model"
	"github.com/tidbcloud/tiinf/monitor/tilog"
	"go.uber.org/zap"

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

func ValidateSpu(
	ctx context.Context,
	tiersInCoresvc []coresvcmodel.Tier,
	tiersInGlobal []schema.ComponentTier,
	tier2TierUnitMapping []schema.TierTierUnit,
) error {
	tiersInCoresvc = lo.Filter(tiersInCoresvc, func(item coresvcmodel.Tier, index int) bool {
		return len(item.TierUnitMapping.Items) > 0
	})

	tiersInCoresvc = lo.Filter(tiersInCoresvc, func(item coresvcmodel.Tier, index int) bool {
		return len(item.TierUnitMapping.Items) > 0 && item.DeployType != coresvcmodel.DeployTypeShared &&
			!isSpecialSpu(&item)
	})

	tierId2TierUnits := lo.SliceToMap(
		tier2TierUnitMapping,
		func(item schema.TierTierUnit) (uint64, schema.TierTierUnit) {
			return item.ComponentTierId, item
		},
	)

	printDiff(tiersInCoresvc, tiersInGlobal)
	if len(tiersInCoresvc) != len(tiersInGlobal) {
		return errors.New("the quantity of tiers is not equal")
	}

	for index := range tiersInCoresvc {
		if uint64(tiersInGlobal[index].ID) != tiersInCoresvc[index].ID.Uint64() {
			return errors.New("tier id is not equal")
		}

		dataInCoreSvc, err := json.Marshal(tiersInCoresvc[index].TierUnitMapping)
		if err != nil {
			return err
		}
		dataInGlobal, err := json.Marshal(tierId2TierUnits[tiersInCoresvc[index].ID.Uint64()].TierUnitMapping)
		if err != nil {
			return err
		}

		if !bytes.Equal(dataInGlobal, dataInCoreSvc) {
			return fmt.Errorf("tier unitmapping is not equal, tier id = %d", tiersInCoresvc[index].ID.Uint64())
		}

		if tiersInGlobal[index].Key != tiersInCoresvc[index].TierKey {
			return errors.New("tier key is not equal")
		}
		if tiersInGlobal[index].ComponentType != string(tiersInCoresvc[index].ComponentType) {
			return errors.New("tier component is not equal")
		}
		if tiersInGlobal[index].DisplayName != tiersInCoresvc[index].DisplayName {
			return errors.New("tier display name is not equal")
		}
		if int(tiersInGlobal[index].CPUNum) != tiersInCoresvc[index].CPUNum {
			return errors.New("tier cpu number is not equal")
		}
		if int(tiersInGlobal[index].MemorySizeGi) != tiersInCoresvc[index].MemorySizeGi {
			return errors.New("tier MemorySizeGi is not equal")
		}
		if tiersInGlobal[index].Default != tiersInCoresvc[index].Default {
			return errors.New("tier default is not equal")
		}
	}

	return nil
}

func printDiff(tiersInCoresvc []coresvcmodel.Tier, tiersInGlobal []schema.ComponentTier) {
	tiersInGlobalMap := lo.SliceToMap(tiersInGlobal, func(item schema.ComponentTier) (string, schema.ComponentTier) {
		return item.Key, item
	})

	for i := range tiersInCoresvc {
		if _, ok := tiersInGlobalMap[tiersInCoresvc[i].TierKey]; !ok {
			tilog.PlainError("tier in coresvc but not in global", zap.String("tier key", tiersInCoresvc[i].TierKey))
		}
	}
}

func ValidateSku(
	ctx context.Context,
	tiersInCoresvc []coresvcmodel.Tier,
	tierUnitsInCoresvc []coresvcmodel.TierUnit,
	tierUnitsInGlobal []schema.ComponentTierUnit,
	conditionsInGlobal []schema.TierUnitCondition,
	tierUnitMappingInGlobal []schema.TierTierUnit,
) error {
	tierUnitKey2TierUnitInCoresvc := lo.SliceToMap(
		tierUnitsInCoresvc,
		func(item coresvcmodel.TierUnit) (string, coresvcmodel.TierUnit) {
			return item.TierUnitKey, item
		},
	)

	tiersInCoresvc = lo.Filter(tiersInCoresvc, func(item coresvcmodel.Tier, index int) bool {
		return len(item.TierUnitMapping.Items) > 0 && item.DeployType != coresvcmodel.DeployTypeShared &&
			!isSpecialSpu(&item)
	})

	tierUnitInUsedInCoreSvc := make(map[string][]*coresvcmodel.TierUnitMappingItem)
	tierUnit2TierId := make(map[string]uint64)

	filteredTiersInCoresvc := lo.Filter(tiersInCoresvc, func(item coresvcmodel.Tier, index int) bool {
		return len(item.TierUnitMapping.Items) > 0
	})

	for index := range filteredTiersInCoresvc {
		for i := range filteredTiersInCoresvc[index].TierUnitMapping.Items {
			item := filteredTiersInCoresvc[index].TierUnitMapping.Items[i]

			if isLocalStorage(item.TierUnitKey) {
				continue
			}

			arr, ok := tierUnitInUsedInCoreSvc[item.TierUnitKey]
			if !ok {
				arr = make([]*coresvcmodel.TierUnitMappingItem, 0)
			}
			arr = append(arr, item)
			tierUnitInUsedInCoreSvc[item.TierUnitKey] = arr

			tierId, ok := tierUnit2TierId[item.TierUnitKey]
			if !ok {
				tierUnit2TierId[item.TierUnitKey] = filteredTiersInCoresvc[index].ID.Uint64()
			} else if tierId != filteredTiersInCoresvc[index].ID.Uint64() {
				return fmt.Errorf("tierunit %s belong to different tier %d %d", item.TierUnitKey, filteredTiersInCoresvc[index].ID.Uint64(), tierId)
			}
		}
	}

	tierUnitsUsedInGlobal := make(map[string]bool)
	lo.ForEach(tierUnitMappingInGlobal, func(ttu schema.TierTierUnit, index int) {
		lo.ForEach(ttu.TierUnitMapping.Items, func(item *schema.TierUnitMappingItem, index int) {
			if isLocalStorage(item.SkuKey) {
				return
			}

			tierUnitsUsedInGlobal[item.SkuKey] = true
		})
	})

	printDiff2(tierUnitInUsedInCoreSvc, tierUnitsUsedInGlobal)
	if len(tierUnitInUsedInCoreSvc) != len(tierUnitsUsedInGlobal) {
		return errors.New("the quantity of tierunit is not equal")
	}

	for index := range tierUnitsInGlobal {
		if uint64(
			tierUnitsInGlobal[index].ID,
		) != tierUnitKey2TierUnitInCoresvc[tierUnitsInGlobal[index].Key].ID.Uint64() {
			tilog.PlainError(
				"default number is not equal",
				zap.Any("global", tierUnitsInGlobal[index]),
				zap.Any("coresvc", tierUnitKey2TierUnitInCoresvc[tierUnitsInGlobal[index].Key]),
			)
			return errors.New("tier uint id is not equal")
		}

		if tierUnitsInGlobal[index].ComponentTierId != tierUnit2TierId[tierUnitsInGlobal[index].Key] {
			if _, ok := tierUnitsUsedInGlobal[tierUnitsInGlobal[index].Key]; ok {
				tilog.PlainError(
					"default number is not equal",
					zap.Any("global", tierUnitsInGlobal[index]),
					zap.Any("coresvc", tierUnitKey2TierUnitInCoresvc[tierUnitsInGlobal[index].Key]),
				)
				return fmt.Errorf(
					"tier unit %s belong to different tier %d %d",
					tierUnitsInGlobal[index].Key,
					tierUnitsInGlobal[index].ComponentTierId,
					tierUnit2TierId[tierUnitsInGlobal[index].Key],
				)
			}
		}
		if tierUnitsInGlobal[index].ComponentType != string(
			tierUnitKey2TierUnitInCoresvc[tierUnitsInGlobal[index].Key].ComponentType,
		) {
			tilog.PlainError(
				"default number is not equal",
				zap.Any("global", tierUnitsInGlobal[index]),
				zap.Any("coresvc", tierUnitKey2TierUnitInCoresvc[tierUnitsInGlobal[index].Key]),
			)
			return errors.New("tier unit: component is not equal")
		}
		if tierUnitsInGlobal[index].ProviderId != tierUnitKey2TierUnitInCoresvc[tierUnitsInGlobal[index].Key].ProviderID.Uint64() {
			tilog.PlainError(
				"default number is not equal",
				zap.Any("global", tierUnitsInGlobal[index]),
				zap.Any("coresvc", tierUnitKey2TierUnitInCoresvc[tierUnitsInGlobal[index].Key]),
			)
			return errors.New("tier unit: provider is not equal")
		}
		if tierUnitsInGlobal[index].InstanceTypeId != tierUnitKey2TierUnitInCoresvc[tierUnitsInGlobal[index].Key].InfraComputeID.Uint64() {
			tilog.PlainError(
				"default number is not equal",
				zap.Any("global", tierUnitsInGlobal[index]),
				zap.Any("coresvc", tierUnitKey2TierUnitInCoresvc[tierUnitsInGlobal[index].Key]),
			)
			return errors.New("tier unit: instance id is not equal")
		}
		if *tierUnitsInGlobal[index].StorageTemplateId != tierUnitKey2TierUnitInCoresvc[tierUnitsInGlobal[index].Key].InfraStorageID.Uint64() {
			tilog.PlainError(
				"default number is not equal",
				zap.Any("global", tierUnitsInGlobal[index]),
				zap.Any("coresvc", tierUnitKey2TierUnitInCoresvc[tierUnitsInGlobal[index].Key]),
			)
			return errors.New("tier unit: storage id is not equal")
		}
		if int(
			tierUnitsInGlobal[index].DefaultNodeNum,
		) < tierUnitKey2TierUnitInCoresvc[tierUnitsInGlobal[index].Key].DefaultNodeNum {
			tilog.PlainError(
				"default number is not equal",
				zap.Any("global", tierUnitsInGlobal[index]),
				zap.Any("coresvc", tierUnitKey2TierUnitInCoresvc[tierUnitsInGlobal[index].Key]),
			)
			return errors.New("tier unit: default number is not equal")
		}
	}

	var conditions []schema.TierUnitCondition
	for i := range conditionsInGlobal {
		if conditionsInGlobal[i].Key == globaldedicatedv1.Sku_Condition_ATTACH_TIERUNIT.String() {
			conditions = append(conditions, conditionsInGlobal[i])
		}
	}

	tierUnitId2TierUnit := lo.SliceToMap(
		tierUnitsInGlobal,
		func(item schema.ComponentTierUnit) (uint, schema.ComponentTierUnit) {
			return item.ID, item
		},
	)

	tierUnitKey2Condition := lo.SliceToMap(
		conditions,
		func(item schema.TierUnitCondition) (string, schema.TierUnitCondition) {
			return tierUnitId2TierUnit[uint(item.ComponentTierUnitId)].Key, item
		},
	)

	if len(tierUnitKey2Condition) != len(conditions) {
		return errors.New("tierUnitKey2Condition is not equal conditions")
	}

	for key := range tierUnitInUsedInCoreSvc {
		tierUnit := tierUnitKey2TierUnitInCoresvc[key]
		if tierUnit.PD == nil || lo.IsEmpty(*tierUnit.PD) {
			continue
		}

		condition, ok := tierUnitKey2Condition[tierUnit.PD.TierUnitKey]
		if !ok {
			return fmt.Errorf(
				"pd tier unit %s does not attach tikv tierunit %s in global",
				tierUnit.PD.TierUnitKey,
				key,
			)
		}

		var tikvTierUnits []string
		if err := json.Unmarshal([]byte(condition.Values), &tikvTierUnits); err != nil {
			panic(err)
		}
		if !lo.Contains(tikvTierUnits, tierUnit.TierUnitKey) {
			return fmt.Errorf("tikv tier unit %s is not in pd relationship %d", tierUnit.TierUnitKey, condition.ID)
		}
	}

	return nil
}

func printDiff2(tierUnitInUsed map[string][]*coresvcmodel.TierUnitMappingItem, tierUnitsUsedInGlobal map[string]bool) {
	for key := range tierUnitInUsed {
		if _, ok := tierUnitsUsedInGlobal[key]; !ok {
			tilog.PlainError("tier unit is not in global", zap.String("tier unit", key))
		}
	}

	for key := range tierUnitsUsedInGlobal {
		if _, ok := tierUnitInUsed[key]; !ok {
			tilog.PlainError("tier unit is not in core svc", zap.String("tier unit", key))
		}
	}
}

func ValidateTierUnitUsedCheck(
	ctx context.Context,
	clustersInCoresvc []coresvcmodel.Cluster,
	tiersInGlobal []schema.ComponentTier,
	tierUnitsInGlobal []schema.ComponentTierUnit,
) error {
	tierUnitKey2TierUnits := lo.SliceToMap(
		tierUnitsInGlobal,
		func(item schema.ComponentTierUnit) (string, schema.ComponentTierUnit) {
			return item.Key, item
		},
	)

	tierKey2Tiers := lo.SliceToMap(tiersInGlobal, func(item schema.ComponentTier) (string, schema.ComponentTier) {
		return item.Key, item
	})

	for i := range clustersInCoresvc {
		for _, item := range clustersInCoresvc[i].ComponentsV2 {
			if isLocalStorage(item.TierUnitKey) {
				continue
			}

			_, tierUnitOk := tierUnitKey2TierUnits[item.TierUnitKey]
			_, tierOk := tierKey2Tiers[item.TierKey]
			if tierUnitOk && tierOk {
				continue
			}

			if !tierOk {
				if clustersInCoresvc[i].DeletedTime != nil &&
					clustersInCoresvc[i].DeletedTime.Before(time.Now().Add(-24*30*10*time.Hour)) {
					tilog.PlainWarn(
						"tier is not found in global, but it is used by cluster",
						zap.String("tier", item.TierKey),
						zap.Uint64("cluster", item.ClusterID.Uint64()),
					)
				} else if !tierOk {
					return fmt.Errorf("tier %s is not found in global, but it is used by cluster %d", item.TierKey, item.ClusterID.Uint64())
				}
			}

			if !tierUnitOk {
				if clustersInCoresvc[i].DeletedTime != nil &&
					clustersInCoresvc[i].DeletedTime.Before(time.Now().Add(-24*30*10*time.Hour)) {
					tilog.PlainWarn(
						"tierunit is not found in global, but it is used by cluster",
						zap.String("tierunit", item.TierUnitKey),
						zap.Uint64("cluster", item.ClusterID.Uint64()),
					)
				} else if !tierUnitOk {
					return fmt.Errorf("tier unit %s is not found in global, but it is used by cluster %d", item.TierUnitKey, item.ClusterID.Uint64())
				}
			}
		}
	}

	return nil
}
