package billing

import (
	"errors"
	"fmt"

	"github.com/pingcap/dbaas/billing2/billing_errors"
	"github.com/pingcap/dbaas/billing2/config"
	"github.com/pingcap/dbaas/billing2/db"
	"github.com/pingcap/dbaas/billing2/db/entity"
	"github.com/pingcap/dbaas/billing2/forms"
	"github.com/pingcap/dbaas/billing2/utils/errorutils"
	"github.com/pingcap/dbaas/billing2/utils/mathutils"
	"github.com/shopspring/decimal"
	"github.com/spf13/viper"
	"gorm.io/gorm"
)

type PackageModel struct{}

func packageContentFromForm(packageInfo *forms.PackageInfo, units []forms.UnitInfo) (*entity.PackageContent, error) {
	if (!packageInfo.CreditsEnable) && (packageInfo.CreditsOnly) {
		return nil, billing_errors.NewInvalidInputError(errors.New("package credits disabled and credits only conflict"))
	}

	if packageInfo.Credits.Cmp(decimal.Zero) < 0 {
		return nil, billing_errors.NewInvalidInputError(errors.New("package credits should not less than zero"))
	}

	if packageInfo.Credits.Cmp(decimal.Zero) > 0 && (!packageInfo.CreditsEnable) {
		return nil, billing_errors.NewInvalidInputError(errors.New("package credits disable but credits not zero"))
	}

	if packageInfo.UnitPrices == nil {
		packageInfo.UnitPrices = map[string]forms.UnitPrice{}
	}

	if packageInfo.Labels == nil {
		packageInfo.Labels = map[string]string{}
	}

	if packageInfo.ExpressionLabels == nil {
		packageInfo.ExpressionLabels = []forms.ExpressionLabel{}
	}

	if packageInfo.ExtraPricingInfos == nil {
		packageInfo.ExtraPricingInfos = []forms.PricingInfo{}
	}

	for k := range packageInfo.ExtraPricingInfos {
		if packageInfo.ExtraPricingInfos[k].UnitPrices == nil {
			packageInfo.ExtraPricingInfos[k].UnitPrices = map[string]forms.UnitPrice{}
		}

		if packageInfo.ExtraPricingInfos[k].Labels == nil {
			packageInfo.ExtraPricingInfos[k].Labels = map[string]string{}
		}

		if packageInfo.ExtraPricingInfos[k].ExpressionLabels == nil {
			packageInfo.ExtraPricingInfos[k].ExpressionLabels = []forms.ExpressionLabel{}
		}
	}

	keys := map[string]bool{}
	for _, unit := range units {
		keys[unit.UnitKey] = true
	}

	if viper.GetBool(config.PackageUnitsValidate) {
		for k := range packageInfo.UnitPrices {
			if !keys[k] {
				return nil, billing_errors.NewInvalidInputError(fmt.Errorf("package units contains undeinfed unit %v", k))
			}
		}
		for i := range packageInfo.ExtraPricingInfos {
			for k := range packageInfo.ExtraPricingInfos[i].UnitPrices {
				if !keys[k] {
					return nil, billing_errors.NewInvalidInputError(fmt.Errorf("package units contains undeinfed unit %v", k))
				}
			}
		}
	}

	for _, v := range packageInfo.ExpressionLabels {
		if mathutils.OperatorFuncMap[v.Operator] == nil {
			return nil, billing_errors.NewInvalidInputError(fmt.Errorf("package expression operator invliad %v", v.Operator))
		}
	}

	for i := range packageInfo.ExtraPricingInfos {
		for _, v := range packageInfo.ExtraPricingInfos[i].ExpressionLabels {
			if mathutils.OperatorFuncMap[v.Operator] == nil {
				return nil, billing_errors.NewInvalidInputError(fmt.Errorf("package expression operator invliad %v", v.Operator))
			}
		}
	}

	content := &entity.PackageContent{
		PackageUnits:         entity.PackageUnits(packageInfo.UnitPrices),
		Credits:              packageInfo.Credits,
		Description:          packageInfo.Description,
		CreditsEnable:        packageInfo.CreditsEnable,
		CreditsOnly:          packageInfo.CreditsOnly,
		Discount:             packageInfo.Discount,
		Labels:               packageInfo.Labels,
		ExpressionLabels:     packageInfo.ExpressionLabels,
		TenantMaxBindedTimes: packageInfo.TenantMaxBindedTimes,
		ExtraPricingInfos:    packageInfo.ExtraPricingInfos,
	}
	return content, nil
}

func packageFromContent(content *entity.PackageContent) *forms.PackageInfo {
	info := &forms.PackageInfo{
		UnitPrices:           forms.UnitPrices(content.PackageUnits),
		Credits:              content.Credits,
		Description:          content.Description,
		CreditsOnly:          content.CreditsOnly,
		CreditsEnable:        content.CreditsEnable,
		Discount:             content.Discount,
		Labels:               content.Labels,
		ExpressionLabels:     content.ExpressionLabels,
		TenantMaxBindedTimes: content.TenantMaxBindedTimes,
		ExtraPricingInfos:    content.ExtraPricingInfos,
	}
	return info
}

func packageFromEntity(entity *entity.Package) *forms.PackageInfo {
	info := packageFromContent(&entity.PackageContent)
	info.PackageID = entity.ID
	info.Version = entity.LatestVersion
	return info
}

func (model *PackageModel) GetAllPackages() ([]forms.PackageInfo, error) {
	var packages []entity.Package
	err := entity.NewPackageQuerySet(db.GetDB()).All(&packages)
	if err != nil {
		return nil, billing_errors.NewSystemError(err)
	}
	result := make([]forms.PackageInfo, len(packages))
	for i, packageEntity := range packages {
		result[i] = *packageFromEntity(&packageEntity)
	}
	return result, nil
}

func (model *PackageModel) CreatePackage(packageInfo *forms.PackageInfo) (*forms.PackageInfo, error) {
	units, err := (&UnitModel{}).GetAllUnits()
	if err != nil {
		return nil, err
	}
	content, err := packageContentFromForm(packageInfo, units)
	if err != nil {
		return nil, err
	}
	packageEntity := &entity.Package{
		LatestVersion:  1,
		PackageContent: *content,
	}
	if err != nil {
		return nil, err
	}
	err = db.GetDB().Transaction(func(tx *gorm.DB) error {
		err := packageEntity.Create(tx)
		if err != nil {
			return err
		}
		packageVersion := &entity.PackageVersion{
			PackageID:      packageEntity.ID,
			Version:        1,
			PackageContent: packageEntity.PackageContent,
		}
		err = packageVersion.Create(tx)
		if err != nil {
			return err
		}
		return nil
	})

	if err != nil {
		return nil, billing_errors.NewSystemError(err)
	}

	return packageFromEntity(packageEntity), nil
}

func (model *PackageModel) GetPackage(packageID uint) (*forms.PackageInfo, error) {
	packageEntity := &entity.Package{}
	err := errorutils.WrapSQLError(entity.NewPackageQuerySet(db.GetDB()).IDEq(packageID).One(packageEntity), fmt.Sprintf("package %v", packageID))
	if err != nil {
		return nil, err
	}
	return packageFromEntity(packageEntity), nil
}

func (model *PackageModel) DeletePackage(packageID uint) error {
	users, err := (&BindedPackageModel{}).GetAllPackageBindedUser(packageID)
	if err != nil {
		return err
	}

	if len(users) != 0 {
		return billing_errors.NewDeletePackageAlreadyBinded(nil)
	}

	err = errorutils.WrapSQLError(entity.NewPackageQuerySet(db.GetDB()).IDEq(packageID).Delete(), fmt.Sprintf("package %v", packageID))
	if err != nil {
		return err
	}
	return nil
}

func (model *PackageModel) GetPackageWithVersion(packageID uint, version uint) (*forms.PackageInfo, error) {
	packageVersion := &entity.PackageVersion{}
	err := errorutils.WrapSQLError(entity.NewPackageVersionQuerySet(db.GetDB()).PackageIDEq(packageID).VersionEq(version).One(packageVersion), fmt.Sprintf("package version %v %v", packageID, packageVersion))
	if err != nil {
		return nil, err
	}
	info := packageFromContent(&packageVersion.PackageContent)

	info.PackageID = packageVersion.PackageID
	info.Version = packageVersion.Version
	return info, nil
}

func (model *PackageModel) UpdatePackage(packageID uint, packageInfo *forms.PackageInfo) (*forms.PackageInfo, error) {
	units, err := (&UnitModel{}).GetAllUnits()
	if err != nil {
		return nil, err
	}
	content, err := packageContentFromForm(packageInfo, units)
	if err != nil {
		return nil, err
	}
	packageEntity := &entity.Package{}
	err = db.GetDB().Transaction(func(tx *gorm.DB) error {
		err := errorutils.WrapSQLError(entity.NewPackageQuerySet(tx).IDEq(packageID).One(packageEntity), fmt.Sprintf("package %v", packageID))
		if err != nil {
			return err
		}

		packageVersion := &entity.PackageVersion{
			PackageID:      packageEntity.ID,
			Version:        packageEntity.LatestVersion + 1,
			PackageContent: *content,
		}

		err = packageVersion.Create(tx)

		if err != nil {
			return err
		}

		packageEntity.LatestVersion = packageVersion.Version
		packageEntity.PackageContent = *content
		err = tx.Model(packageEntity).Select("*").Omit("created_at, deleted_at").Updates(packageEntity).Error
		if err != nil {
			return err
		}
		return nil
	})
	if err != nil {
		if errors.As(err, &billing_errors.BaseError{}) {
			return nil, err
		}
		return nil, billing_errors.NewSystemError(err)
	}
	return packageFromEntity(packageEntity), nil
}
