package spu

import (
	"context"

	"github.com/tidbcloud/pkgv2/db"

	globalcommonv1 "github.com/tidbcloud/tidb-management-service/api/spec/global/common/v1"
	"github.com/tidbcloud/tidb-management-service/dedicated-global-service/domain/entity"
	"github.com/tidbcloud/tidb-management-service/dedicated-global-service/domain/repository/spu"
	"github.com/tidbcloud/tidb-management-service/dedicated-global-service/infra/repoimpl/schema"
	"github.com/tidbcloud/tidb-management-service/dedicated-global-service/infra/repoimpl/spu/cvt"
	"github.com/tidbcloud/tidb-management-service/utilities/bizerr"
)

type instanceMuRepoImpl struct {
	provider db.Provider
}

func NewInstanceMuRepo(provider db.Provider) spu.InstanceMuRepo {
	return &instanceMuRepoImpl{
		provider: provider,
	}
}

func (it *instanceMuRepoImpl) Save(ctx context.Context, en *entity.InstanceMu) (uint, bizerr.BizError) {
	// Query provider first to get their IDs.
	var providerModel schema.Provider
	if err := it.provider.WriteDB(ctx).Take(&providerModel, "name = ?", en.CloudProvider.String()).Error; err != nil {
		return 0, bizerr.WrapGormError(err, "load provider failed")
	}

	instanceType, err := cvt.InstanceTypeEnToModel(en, uint64(providerModel.ID))
	if err != nil {
		return 0, bizerr.WrapError(err, "convert instance type failed")
	}

	wdb := it.provider.WriteDB(ctx)
	if instanceType.ID != 0 {
		wdb = wdb.Omit("created_at")
	}
	if err := wdb.Save(instanceType).Error; err != nil {
		return 0, bizerr.WrapGormError(err, "create instance type failed")
	}

	return instanceType.ID, nil
}

func (it *instanceMuRepoImpl) Query(
	ctx context.Context,
	filter spu.InstanceMuFilter,
) ([]*entity.InstanceMu, bizerr.BizError) {
	rdb := it.provider.ReadDB(ctx).Model(&schema.InstanceType{})

	if filter.Provider != globalcommonv1.CloudProvider_NAME_UNSPECIFIED {
		rdb = rdb.Where("`Provider`.`name` = ?", filter.Provider.String())
	}

	if len(filter.CpuArchs) > 0 {
		rdb = rdb.Where("`cpu_arch` IN (?)", filter.CpuArchs)
	}

	var its []*schema.InstanceType
	if err := rdb.InnerJoins("Provider").Find(&its).Error; err != nil {
		return nil, bizerr.WrapGormError(err, "load instance types")
	}

	out := make([]*entity.InstanceMu, 0, len(its))
	for _, it := range its {
		en, err := cvt.InstanceTypeModelToEn(it, &it.Provider)
		if err != nil {
			return nil, bizerr.WrapError(err, "convert instance type %d failed", it.ID)
		}
		out = append(out, en)
	}

	return out, nil
}

func (it *instanceMuRepoImpl) Get(ctx context.Context, instanceType string) (*entity.InstanceMu, bizerr.BizError) {
	var itModel schema.InstanceType
	if err := it.provider.ReadDB(ctx).Where("instance_type = ?", instanceType).First(&itModel).Error; err != nil {
		return nil, bizerr.WrapGormError(err, "load instance type failed")
	}

	en, err := cvt.InstanceTypeModelToEn(&itModel, &itModel.Provider)
	if err != nil {
		return nil, bizerr.WrapError(err, "convert instance type %d failed", itModel.ID)
	}

	return en, nil
}

func (it *instanceMuRepoImpl) GetByID(ctx context.Context, id uint64) (*entity.InstanceMu, bizerr.BizError) {
	var itModel schema.InstanceType
	rdb := it.provider.ReadDB(ctx).Model(&schema.InstanceType{})
	if err := rdb.Where("`instance_types`.`id` = ?", id).InnerJoins("Provider").First(&itModel).Error; err != nil {
		return nil, bizerr.WrapGormError(err, "load instance type failed")
	}
	en, err := cvt.InstanceTypeModelToEn(&itModel, &itModel.Provider)
	if err != nil {
		return nil, bizerr.WrapError(err, "convert instance type %d failed", itModel.ID)
	}
	return en, nil
}

func (it *instanceMuRepoImpl) Delete(ctx context.Context, id uint64) bizerr.BizError {
	if err := it.provider.WriteDB(ctx).Delete(&schema.InstanceType{}, id).Error; err != nil {
		return bizerr.WrapGormError(err, "delete instance type failed")
	}
	return nil
}
