package creditriskcontrol

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"
	"gorm.io/gorm/clause"
)

type CreditRiskLevelModel struct{}

func (*CreditRiskLevelModel) GetTenantCreditRiskLevelID(tenantID uint) (uint, error) {
	tenantCreditRiskLevelEntity := &entity.TenantCreditRiskLevel{}
	err := errorutils.WrapSQLError(entity.NewTenantCreditRiskLevelQuerySet(db.GetDB()).TenantIDEq(tenantID).One(tenantCreditRiskLevelEntity), fmt.Sprintf("CreditRisk Level Tenant: %d", tenantID))
	if err != nil {
		if errors.Is(err, billing_errors.ObjectNotExistsError) {
			return config.Get().GetUint(config.DefaultCreditRiskLevel), nil
		}
		return 0, err
	}
	return tenantCreditRiskLevelEntity.CreditRiskLevelID, nil
}

func (m *CreditRiskLevelModel) GetTenantCreditRiskLevel(tenantID uint) (*forms.CreditRiskLevel, error) {
	creditRiskLevelID, err := m.GetTenantCreditRiskLevelID(tenantID)
	if err != nil {
		return nil, err
	}
	return m.GetCreditRiskLevel(creditRiskLevelID)
}

func (*CreditRiskLevelModel) SetTenantCreditRiskLevel(tenantID uint, creditRiskLevel uint) error {
	tenantCreditRiskLevelEntity := &entity.TenantCreditRiskLevel{
		TenantID:          tenantID,
		CreditRiskLevelID: creditRiskLevel,
	}
	tx := db.GetDB().Clauses(clause.OnConflict{
		UpdateAll: true,
	})
	err := tenantCreditRiskLevelEntity.Create(tx)
	if err != nil {
		return err
	}
	return nil
}

func creditRiskLevelInfoFromEntity(creditRiskLevelEntity *entity.CreditRiskLevel) (*forms.CreditRiskLevel, error) {

	costQuota, err := (&CostQuotaModel{}).GetCreditRiskLevelQuota(creditRiskLevelEntity.ID)
	if err != nil {
		return nil, err
	}
	return &forms.CreditRiskLevel{
		CreditRiskLevelID: creditRiskLevelEntity.ID,
		Name:              creditRiskLevelEntity.Name,
		CostQuota:         *costQuota,
	}, nil
}

func (*CreditRiskLevelModel) GetCreditRiskLevel(creditRiskLevelID uint) (*forms.CreditRiskLevel, error) {
	creditRiskLevelEntity := &entity.CreditRiskLevel{}
	err := errorutils.WrapSQLError(entity.NewCreditRiskLevelQuerySet(db.GetDB()).IDEq(creditRiskLevelID).One(creditRiskLevelEntity), fmt.Sprintf("CreditRisk Level : %d", creditRiskLevelID))
	if err != nil {
		return nil, err
	}
	return creditRiskLevelInfoFromEntity(creditRiskLevelEntity)
}

func (*CreditRiskLevelModel) UpdateCreditRiskLevel(creditRiskLevelID uint, creditRiskLevel *forms.CreditRiskLevel) error {
	err := entity.NewCreditRiskLevelQuerySet(db.GetDB()).IDEq(creditRiskLevelID).GetUpdater().SetName(creditRiskLevel.Name).Update()
	if err != nil {
		return err
	}
	return nil
}

func (m *CreditRiskLevelModel) CreateCreditRiskLevel(creditRiskLevel *forms.CreditRiskLevel) (*forms.CreditRiskLevel, error) {
	creditRiskLevelEntity := &entity.CreditRiskLevel{
		Name: creditRiskLevel.Name,
	}
	err := creditRiskLevelEntity.Create(db.GetDB())
	if err != nil {
		return nil, err
	}
	return m.GetCreditRiskLevel(creditRiskLevelEntity.ID)
}

func (*CreditRiskLevelModel) DeleteCreditRiskLevel(creditRiskLevelID uint) error {
	if creditRiskLevelID == config.Get().GetUint(config.DefaultCreditRiskLevel) {
		return billing_errors.NewInvalidInputError(errors.New("can not delete default risk level"))
	}
	err := entity.NewTenantCreditRiskLevelQuerySet(db.GetDB()).CreditRiskLevelIDEq(creditRiskLevelID).Delete()
	if err != nil {
		return err
	}

	err = errorutils.WrapSQLError(entity.NewCreditRiskLevelQuerySet(db.GetDB()).IDEq(creditRiskLevelID).Delete(), fmt.Sprintf("CreditRisk Level : %d", creditRiskLevelID))
	if err != nil {
		return err
	}
	return nil
}

func (*CreditRiskLevelModel) ListAllCreditRiskLevels() ([]forms.CreditRiskLevel, error) {
	creditRiskLevelEntities := []entity.CreditRiskLevel{}
	err := entity.NewCreditRiskLevelQuerySet(db.GetDB()).All(&creditRiskLevelEntities)
	if err != nil {
		return nil, err
	}
	result := []forms.CreditRiskLevel{}
	for _, creditRiskLevelEntity := range creditRiskLevelEntities {
		res, err := creditRiskLevelInfoFromEntity(&creditRiskLevelEntity)
		if err != nil {
			return nil, err
		}
		result = append(result, *res)
	}
	return result, nil
}
