package billing

import (
	"errors"
	"fmt"

	"github.com/pingcap/dbaas/billing2/billing_errors"
	"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/shopspring/decimal"
	"gorm.io/gorm"
)

type BindedPackageModel struct {
}

func getInfoByEntity(entity *entity.BindedPackage) (*forms.BindedPackageInfo, error) {
	packageInfo, err := (&PackageModel{}).GetPackage(entity.PackageID)
	if err != nil {
		return nil, err
	}
	info := &forms.BindedPackageInfo{
		BindedPackageID:  entity.ID,
		PackageID:        entity.PackageID,
		TenantID:         entity.TenantID,
		BindedDiscount:   entity.BindedDiscount,
		RemainingCredits: entity.RemainingCredits,
		Status:           entity.Status,
		PackageInfo:      packageInfo,
	}
	return info, nil

}

func (model *BindedPackageModel) BindPackage(tenantID uint, packageID uint) (*forms.BindedPackageInfo, error) {
	packageModel := &PackageModel{}
	packageInfo, err := packageModel.GetPackage(packageID)
	if err != nil {
		return nil, err
	}
	bindedPackage := &entity.BindedPackage{
		TenantID:         tenantID,
		PackageID:        packageID,
		RemainingCredits: packageInfo.Credits,
		Status:           entity.BindedPackageStatus_Active,
	}
	err = db.GetDB().Transaction(func(tx *gorm.DB) error {
		err = bindedPackage.Create(tx)
		if err != nil {
			return billing_errors.NewSystemError(err)
		}
		if packageInfo.TenantMaxBindedTimes != entity.PackageMaxBindedTimes_INF {
			count, err := entity.NewBindedPackageQuerySet(tx).TenantIDEq(tenantID).PackageIDEq(packageID).StatusEq(entity.BindedPackageStatus_Active).Count()
			if err != nil {
				return billing_errors.NewSystemError(err)
			}
			if uint(count) > packageInfo.TenantMaxBindedTimes {
				return billing_errors.NewInvalidInputError(errors.New("package binded more than tenant_max_binded_times"))
			}
		}
		return nil
	})
	if err != nil {
		return nil, err
	}
	info, err := getInfoByEntity(bindedPackage)
	if err != nil {
		return nil, err
	}
	return info, nil

}

func (model *BindedPackageModel) GetBindedPackage(bindedPacakgeID uint) (*forms.BindedPackageInfo, error) {
	bindedPackage := &entity.BindedPackage{}
	err := errorutils.WrapSQLError(entity.NewBindedPackageQuerySet(db.GetDB()).IDEq(bindedPacakgeID).One(bindedPackage), fmt.Sprintf("binded package %d", bindedPacakgeID))
	if err != nil {
		return nil, err
	}
	info, err := getInfoByEntity(bindedPackage)
	if err != nil {
		return nil, err
	}
	return info, nil
}

func (model *BindedPackageModel) UnbindPackage(bindedPacakgeID uint) error {
	bindedPackage := &entity.BindedPackage{Model: gorm.Model{ID: bindedPacakgeID}, Status: entity.BindedPackageStatus_InActive}
	err := errorutils.WrapSQLError(bindedPackage.Update(db.GetDB(), entity.BindedPackageDBSchema.Status), fmt.Sprintf("binded package %d", bindedPacakgeID))
	if err != nil {
		return err
	}
	return nil
}

func (model *BindedPackageModel) GetAllUserBindedPackage(tenantID uint) ([]forms.BindedPackageInfo, error) {
	entities := make([]entity.BindedPackage, 0)
	err := entity.NewBindedPackageQuerySet(db.GetDB()).TenantIDEq(tenantID).StatusEq(entity.BindedPackageStatus_Active).All(&entities)
	if err != nil {
		return nil, billing_errors.NewSystemError(err)
	}
	infos := make([]forms.BindedPackageInfo, len(entities))
	for i, bindedPackage := range entities {
		info, err := getInfoByEntity(&bindedPackage)
		if err != nil {
			return nil, err
		}
		infos[i] = *info
	}
	return infos, nil
}

func (model *BindedPackageModel) GetAllPackageBindedUser(packageID uint) ([]forms.BindedPackageInfo, error) {
	entities := make([]entity.BindedPackage, 0)
	err := entity.NewBindedPackageQuerySet(db.GetDB()).PackageIDEq(packageID).StatusEq(entity.BindedPackageStatus_Active).All(&entities)
	if err != nil {
		return nil, billing_errors.NewSystemError(err)
	}
	infos := make([]forms.BindedPackageInfo, len(entities))
	for i, bindedPackage := range entities {
		info, err := getInfoByEntity(&bindedPackage)
		if err != nil {
			return nil, err
		}
		infos[i] = *info
	}
	return infos, nil
}

func (model *BindedPackageModel) ChargeCredits(bindedPackageID uint, creditsChargeInfo *forms.CreditsChargeInfo) (*forms.BindedPackageInfo, error) {

	recordEntity := &entity.CreditsChargeRecord{
		BindedPackageID: bindedPackageID,
		ChargeCount:     creditsChargeInfo.ChargeCount,
		ChargeType:      entity.CreditsChargeType_MANUAL,
		ChargeReason:    creditsChargeInfo.ChargeReason,
		ChargerTenantID: creditsChargeInfo.ChargerTenantID,
		ChargerUserID:   creditsChargeInfo.ChargerUserID,
	}
	bindedPackageInfo, err := model.GetBindedPackage(bindedPackageID)
	if err != nil {
		return nil, err
	}

	if bindedPackageInfo.Status != entity.BindedPackageStatus_Active {
		return nil, billing_errors.NewInvalidInputError(errors.New("charge credits invalid package status"))
	}

	if !bindedPackageInfo.PackageInfo.CreditsEnable {
		return nil, billing_errors.NewInvalidInputError(errors.New("charge credits package credits disable"))
	}

	err = db.GetDB().Transaction(func(tx *gorm.DB) error {
		err := recordEntity.Create(tx)
		if err != nil {
			return billing_errors.NewSystemError(err)
		}
		res := tx.Model(&entity.BindedPackage{}).Where("id = ?", recordEntity.BindedPackageID).Update("remaining_credits", gorm.Expr("remaining_credits + ?", recordEntity.ChargeCount))
		if res.Error != nil {
			return billing_errors.NewSystemError(err)
		}
		if res.RowsAffected != 1 {
			return billing_errors.NewInvalidInputError(errors.New("invalid Binded Package ID"))
		}
		return nil
	})
	if err != nil {
		return nil, err
	}
	return model.GetBindedPackage(bindedPackageID)
}

func (model *BindedPackageModel) GetChargeCreditsHistory(bindedPacakgeID uint) (*forms.CreditsChargeHistory, error) {
	entities := []entity.CreditsChargeRecord{}
	err := entity.NewCreditsChargeRecordQuerySet(db.GetDB()).BindedPackageIDEq(bindedPacakgeID).All(&entities)
	if err != nil {
		return nil, billing_errors.NewSystemError(err)
	}

	total := decimal.Zero

	result := &forms.CreditsChargeHistory{
		BindedPackageID: bindedPacakgeID,
	}
	result.History = make([]forms.CreditsChargeInfo, len(entities))
	fmt.Printf("total record %v", len(entities))

	for i, entity := range entities {
		total = total.Add(entity.ChargeCount)
		fmt.Printf("total added %v", entity.ChargeCount)
		result.History[i] = forms.CreditsChargeInfo{
			BindedPackageID: bindedPacakgeID,
			ChargeCount:     entity.ChargeCount,
			ChargeReason:    entity.ChargeReason,
			ChargerTenantID: entity.ChargerTenantID,
			ChargerUserID:   entity.ChargerUserID,
			ChargeTime:      entity.CreatedAt,
		}
	}
	result.TotalAmounts = total

	return result, nil
}

func (model *BindedPackageModel) ChangeBindedDiscount(bindedPacakgeID uint, changeInfo *forms.ChangeBindedDiscountInput) (*forms.BindedPackageInfo, error) {

	recordEntity := &entity.BindedDiscountChangeRecord{
		BindedPackageID: bindedPacakgeID,
		BindedDiscount:  changeInfo.BindedDiscount,
		ChangeReason:    changeInfo.ChangeReason,
		ChangerTenantID: changeInfo.ChangerTenantID,
		ChangerUserID:   changeInfo.ChangerUserID,
	}
	bindedPackageInfo, err := model.GetBindedPackage(bindedPacakgeID)
	if err != nil {
		return nil, err
	}

	if bindedPackageInfo.Status != entity.BindedPackageStatus_Active {
		return nil, billing_errors.NewInvalidInputError(errors.New("change discount invalid package status"))
	}

	err = db.GetDB().Transaction(func(tx *gorm.DB) error {
		err := recordEntity.Create(tx)
		if err != nil {
			return billing_errors.NewSystemError(err)
		}
		err = (&entity.BindedPackage{
			Model: gorm.Model{
				ID: recordEntity.BindedPackageID,
			},
			BindedDiscount:         recordEntity.BindedDiscount,
			BindedDiscountRecordID: &recordEntity.ID,
		}).Update(tx, entity.BindedPackageDBSchema.BindedDiscount, entity.BindedPackageDBSchema.BindedDiscountRecordID)
		if err != nil {
			return billing_errors.NewSystemError(err)
		}
		return nil
	})
	if err != nil {
		return nil, err
	}
	return model.GetBindedPackage(bindedPacakgeID)
}
