package billing

import (
	"errors"
	"fmt"
	"github.com/pingcap/dbaas/cost-metering-service/billing_errors"
	"github.com/pingcap/dbaas/cost-metering-service/db"
	"github.com/pingcap/dbaas/cost-metering-service/db/entity"
	"github.com/pingcap/dbaas/cost-metering-service/errorutils"
	"github.com/pingcap/dbaas/cost-metering-service/forms"
	"github.com/pingcap/dbaas/cost-metering-service/log"
	"go.uber.org/zap"
	"gorm.io/gorm"
)

type UnitModel struct {
}

func (model *UnitModel) CreateAndUpdateUnit(unitInfo *forms.UnitInfo) (*forms.UnitInfo, error) {
	isUpdate := false
	unitFromDb := &entity.Unit{}

	err := entity.NewUnitQuerySet(db.GetDB()).UnitKeyEq(unitInfo.UnitKey).One(unitFromDb)
	if err != nil {
		if errors.Is(err, billing_errors.ObjectNotExistsError) {
			isUpdate = false
		} else {
			return nil, err
		}
	} else {
		isUpdate = true
	}

	unit := &entity.Unit{
		CostCenterOrgID: unitInfo.CostCenterOrgID,
		UnitKey:         unitInfo.UnitKey,
		Description:     unitInfo.Description,
		Category:        unitInfo.Category,
		Provider:        unitInfo.Provider,
		Region:          unitInfo.Region,
		SkuType:         unitInfo.SkuType,
		MeteringUnits:   unitInfo.MeteringUnits,
		Version:         1,
	}
	if !isUpdate {

		err = unit.Create(db.GetDB())
		if err != nil {
			return nil, err
		}
	} else {
		unit.Version = unitFromDb.Version + 1
		err = db.GetDB().Transaction(func(tx *gorm.DB) error {
			err = db.GetDB().Model(unitFromDb).Where("unit_key", unitFromDb.UnitKey).Updates(unit).Error
			if err != nil {
				log.Error("CreateAndUpdateUnit error", zap.Any("unitInfo", unitInfo), zap.Error(err))
				return err
			}

			//cachehandler.Get().Delete("GetUnit", unitInfo.UnitKey)
			return nil

		})
		if err != nil {
			return nil, err
		}

	}

	return unitFromEntity(unit), nil
}
func unitFromEntity(unit *entity.Unit) *forms.UnitInfo {
	return &forms.UnitInfo{
		CostCenterOrgID: unit.CostCenterOrgID,
		UnitKey:         unit.UnitKey,
		Description:     unit.Description,
		Category:        unit.Category,
		Provider:        unit.Provider,
		Region:          unit.Region,
		SkuType:         unit.SkuType,
		MeteringUnits:   unit.MeteringUnits,
		Version:         unit.Version,
	}
}
func (model *UnitModel) GetAllUnits() ([]forms.UnitInfo, error) {
	var units []entity.Unit
	err := entity.NewUnitQuerySet(db.GetDB()).All(&units)
	if err != nil {
		return nil, err
	}
	var result []forms.UnitInfo = make([]forms.UnitInfo, len(units))
	for i, unit := range units {
		result[i] = *unitFromEntity(&unit)
	}
	return result, nil
}
func (model *UnitModel) GetOrgUnitsByType(costCenterOrgID uint, skyType string) ([]forms.UnitInfo, error) {
	var units []entity.Unit
	err := entity.NewUnitQuerySet(db.GetDB()).CostCenterOrgIDEq(costCenterOrgID).SkuTypeEq(skyType).All(&units)
	if err != nil {
		return nil, err
	}
	var result = make([]forms.UnitInfo, len(units))
	for i, unit := range units {
		result[i] = *unitFromEntity(&unit)
	}
	return result, nil
}

func (model *UnitModel) GetUnit(unitKey string) (*forms.UnitInfo, error) {
	result := &forms.UnitInfo{}

	unit := &entity.Unit{}
	err := errorutils.WrapSQLError(entity.NewUnitQuerySet(db.GetDB()).UnitKeyEq(unitKey).One(unit), fmt.Sprintf("unit %v", unitKey))
	if err != nil {
		return nil, err
	}
	result = unitFromEntity(unit)
	return result, nil
}
func (model *UnitModel) CreateUnit(unitInfo *forms.UnitInfo) (*forms.UnitInfo, error) {
	return model.CreateAndUpdateUnit(unitInfo)
}
func (model *UnitModel) UpdateUnit(unitInfo *forms.UnitInfo) (*forms.UnitInfo, error) {
	unit := &entity.Unit{
		CostCenterOrgID: unitInfo.CostCenterOrgID,
		UnitKey:         unitInfo.UnitKey,
		Description:     unitInfo.Description,
		Provider:        unitInfo.Provider,
		Category:        unitInfo.Category,
		Region:          unitInfo.Region,
	}
	_, err := model.GetUnit(unit.UnitKey)
	if err != nil {
		return nil, err
	}
	err = unit.Update(db.GetDB(), entity.UnitDBSchema.Description, entity.UnitDBSchema.Category, entity.UnitDBSchema.Provider, entity.UnitDBSchema.Region)
	if err != nil {
		return nil, err
	}
	return unitFromEntity(unit), nil
}
func (model *UnitModel) DeleteUnit(unitKey string) error {
	err := errorutils.WrapSQLError(entity.NewUnitQuerySet(db.GetDB()).UnitKeyEq(unitKey).Delete(), fmt.Sprintf("unit %v", unitKey))
	if err != nil {
		return err
	}
	return nil
}
