package cluster

import (
	"context"
	"strings"

	"github.com/tidbcloud/pkgv2/db"
	"gorm.io/gorm/clause"

	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/domain/repository/cluster"
	"github.com/tidbcloud/tidb-management-service/dedicated-global-service/infra/repoimpl/schema"
	"github.com/tidbcloud/tidb-management-service/utilities/bizerr"
	"github.com/tidbcloud/tidb-management-service/utilities/enum"
)

type componentRepoImpl struct {
	provider db.Provider
}

func NewComponentRepo(provider db.Provider) cluster.ComponentRepo {
	return &componentRepoImpl{
		provider: provider,
	}
}

func (i *componentRepoImpl) GetUsedComponentCount(
	ctx context.Context,
	tenantId uint64,
) (map[globaldedicatedv1.ComponentType]uint32, bizerr.BizError) {
	rdb := i.provider.ReadDB(ctx)
	var results []struct {
		ComponentType string
		Count         uint32
	}

	if err := rdb.
		Model(&schema.ClusterComponent{}).
		Select("`cluster_components`.`component_type`, SUM(`count`) as count").
		Joins("JOIN `clusters` on `cluster_components`.`cluster_id`=`clusters`.`id`").
		Where("`clusters`.`tenant_id` = ?", tenantId).
		Group("`cluster_components`.`component_type`").
		Scan(&results).Error; err != nil {
		return nil, bizerr.WrapGormError(err, "failed to get component count")
	}

	countMap := make(map[globaldedicatedv1.ComponentType]uint32)
	for _, result := range results {
		componentType, ok := globaldedicatedv1.ComponentType_value[strings.ToUpper(result.ComponentType)]
		if !ok {
			return nil, bizerr.New(bizerr.ErrCodeUnknown, "unknown component type %s", result.ComponentType)
		}
		countMap[globaldedicatedv1.ComponentType(componentType)] = result.Count
	}

	return countMap, nil
}

func (i *componentRepoImpl) Save(
	ctx context.Context,
	component *entity.DesiredClusterComponent,
) (uint64, bizerr.BizError) {
	wdb := i.provider.WriteDB(ctx)

	// save component
	m, err := cvtComponentEntityToModel(component.ClusterComponent)
	if err != nil {
		return 0, bizerr.WrapError(err, "failed to convert component")
	}

	if err := wdb.Save(m).Error; err != nil {
		return 0, bizerr.WrapGormError(err, "failed to save component")
	}

	if _, ok := entity.ComponentsHaveStorage[component.GetComponentType()]; !ok {
		return uint64(m.ID), nil
	}

	// save storages
	storageModels := make([]*schema.ClusterComponentStorage, 0, len(component.GetStorages()))
	for diskType, storage := range component.GetStorages() {
		if storage.GetVolumeType() == globaldedicatedv1.StorageMu_VOLUME_TYPE_UNSPECIFIED {
			continue
		}
		storage.ComponentId = uint64(m.ID)
		dt, err := enum.FromString[globaldedicatedv1.Sku_DiskType](diskType)
		if err != nil {
			return 0, bizerr.WrapError(err, "invalid disk type %s", diskType)
		}
		storage.DiskType = globaldedicatedv1.Sku_DiskType(dt)
		m, err := cvtComponentStorageEntityToModel(storage)
		if err != nil {
			return 0, bizerr.WrapError(err, "failed to convert component storage")
		}
		storageModels = append(storageModels, m)
	}

	if err := wdb.Save(storageModels).Error; err != nil {
		return 0, bizerr.WrapGormError(err, "failed to save component storages")
	}

	return uint64(m.ID), nil
}

func (i *componentRepoImpl) SaveAll(
	ctx context.Context,
	components []*entity.DesiredClusterComponent,
	clusterId, tidbGroupId uint64,
) bizerr.BizError {
	for _, component := range components {
		component.ClusterId = clusterId
		component.TidbGroupId = tidbGroupId
		if _, err := i.Save(ctx, component); err != nil {
			return err
		}
	}

	return nil
}

func (i *componentRepoImpl) Load(
	ctx context.Context,
	id uint,
) (*globaldedicatedv1.Cluster_ClusterComponent, bizerr.BizError) {
	rdb := i.provider.ReadDB(ctx)

	var component schema.ClusterComponent
	if err := rdb.
		Preload("ComponentTierUnit.Provider").
		Preload("ComponentTierUnit.InstanceType").
		Preload("ComponentTierUnit.StorageTemplate").
		Preload("ComponentTierUnit.ComponentTier").
		Preload("ComponentTierUnit.TierUnitConditions").
		Preload(clause.Associations).
		Take(&component, id).Error; err != nil {
		return nil, bizerr.WrapGormError(err, "failed to load component")
	}

	en, err := cvtComponentModelToEntity(&component)
	if err != nil {
		return nil, bizerr.WrapError(err, "failed to convert component")
	}

	return en, nil
}

func (i *componentRepoImpl) DeleteByClusterId(ctx context.Context, clusterID uint64) bizerr.BizError {
	wdbComp := i.provider.WriteDB(ctx)
	wdbStorage := i.provider.WriteDB(ctx)

	// delete storages
	if err := wdbStorage.Where("`cluster_component_id` IN (SELECT `id` FROM `cluster_components` WHERE `cluster_id` = ?)", clusterID).
		Delete(&schema.ClusterComponentStorage{}).
		Error; err != nil {
		return bizerr.WrapGormError(err, "failed to delete component storages")
	}

	// delete components
	if err := wdbComp.Where("`cluster_id` = ?", clusterID).Delete(&schema.ClusterComponent{}).Error; err != nil {
		return bizerr.WrapGormError(err, "failed to delete components")
	}
	return nil
}

func (i *componentRepoImpl) DeleteByTidbGroupId(ctx context.Context, clusterId, tidbGroupId uint64) bizerr.BizError {
	wdbComp := i.provider.WriteDB(ctx)
	wdbStorage := i.provider.WriteDB(ctx)

	// delete storages
	if err := wdbStorage.Where("`cluster_component_id` IN (SELECT `id` FROM `cluster_components` WHERE `cluster_id` = ? AND `work_pool_id` = ?)", clusterId, tidbGroupId).
		Delete(&schema.ClusterComponentStorage{}).
		Error; err != nil {
		return bizerr.WrapGormError(err, "failed to delete component storages of work pool %d", tidbGroupId)
	}

	// delete components
	if err := wdbComp.Where("`cluster_id` = ? AND `work_pool_id` = ?", clusterId, tidbGroupId).Delete(&schema.ClusterComponent{}).Error; err != nil {
		return bizerr.WrapGormError(err, "failed to delete components of work pool %d", tidbGroupId)
	}
	return nil
}
