package api

import (
	"errors"
	"strings"

	"github.com/gin-gonic/gin"
	"github.com/pingcap/dbaas/billing2/billing_errors"
	"github.com/pingcap/dbaas/billing2/constant"
	"github.com/pingcap/dbaas/billing2/db/entity"
	"github.com/pingcap/dbaas/billing2/forms"
	"github.com/pingcap/dbaas/billing2/model/billingconfig"
	"github.com/pingcap/dbaas/billing2/model/marketing"
	"github.com/pingcap/dbaas/billing2/model/payment/paymentsub"
	"github.com/pingcap/dbaas/billing2/utils/ginutils"
	"github.com/pingcap/dbaas/billing2/utils/log"
	"github.com/shopspring/decimal"
	"go.uber.org/zap"
)

var BillingConfigModel = &billingconfig.BillingConfigModel{}

// @Summary List Billing Configs
// @Schemes
// @Accept json
// @Produce json
// @Param tenant_id path string true "tenant_id"
// @Success 200 {object} []forms.BillingConfig
// @Router /tenants/{tenant_id}/billing_configs [get]
func (s *API) ListBillingConfigs(c *gin.Context) (ginutils.HandlerFuncResult, error) {
	tenantID, err := getTenantIDFromURI(c)
	if err != nil {
		return nil, err
	}
	ret, err := BillingConfigModel.GetTenantIDAllBillingConfigID(tenantID)
	if err != nil {
		return nil, billing_errors.NewSystemError(err)
	}
	return ret, err
}

// @Summary Get Billing Configs By Managed Tenant
// @Schemes
// @Accept json
// @Produce json
// @Param tenant_id path string true "tenant_id"
// @Success 200 {object} forms.BillingConfig
// @Router /billing_configs/managed_tenants/{tenant_id} [get]
func (s *API) GetBillingConfigByManagedTenantID(c *gin.Context) (ginutils.HandlerFuncResult, error) {
	tenantID, err := getTenantIDFromURI(c)
	if err != nil {
		return nil, err
	}
	ret, err := BillingConfigModel.GetBillingConfigByManagedTenantID(tenantID)
	if err != nil {
		return nil, billing_errors.NewSystemError(err)
	}
	return ret, err
}

// @Summary Create CPPO Billing Config
// @Schemes
// @Accept json
// @Produce json
// @Param CPPOBillingConfig body forms.CPPOBillingConfig true "CPPOBillingConfig"
// @Success 200 {object} forms.OKType
// @Router /tenants/{tenant_id}/cppo_billing_config [POST]
func (s *API) CreateCPPOBillingConfig(c *gin.Context) (ginutils.HandlerFuncResult, error) {
	request := &forms.CPPOBillingConfig{}
	if err := c.ShouldBindJSON(request); err != nil {
		return nil, billing_errors.NewInvalidInputError(err)
	}
	log.Info("CreateCPPOBillingConfig ", zap.Any("request", request))

	dis := decimal.NewFromInt(1)
	var disDetected bool
	for _, d := range request.Discounts {
		if strings.EqualFold(d.Usage, constant.DiscountUsageCPPO) {
			if disDetected {
				return nil, billing_errors.NewInvalidInputError(errors.New("cannot assign cppo discount twice"))
			}
			dis = d.Value
			disDetected = true
		}
	}
	if request.SupportPlanInfo != nil {
		supportPlanModel := &marketing.SupportPlanModel{}
		request.SupportPlanInfo.Public = false
		supportPlan, err := supportPlanModel.CreateSupportPlan(&request.SupportPlanInfo.SupportPlan)
		if err != nil {
			return nil, err
		}
		request.SupportPlanInfo.SupportPlanID = supportPlan.SupportPlanID
	}
	customerBillingConfigID, paymentBillingConfigID, err := BillingConfigModel.CreateMarketplaceResellerBillingConfig(request.MarketplaceResellerBillingConfig)
	if err != nil {
		return nil, billing_errors.NewSystemError(err)
	}
	var paymentModel = paymentsub.PaymentModel{}
	_, err = paymentModel.CreatePaymentMethodAndActive(request.TenantID, &forms.PaymentMethodCreateQuery{
		TenantID:              request.TenantID,
		PaymentType:           entity.PaymentType_AWSCPPOTransfer,
		BindedBillingConfigID: &customerBillingConfigID,
	})
	if err != nil {
		log.Error("CreateCPPOBillingConfig Customer Billing config error", zap.Uint("tenantID", request.TenantID), zap.Error(err))
		return err, nil
	}
	invoiceable := false
	var subCode *string
	if request.PaymentSubCode != "" {
		subCode = &request.PaymentSubCode
	}
	_, err = paymentModel.CreatePaymentMethodAndActive(request.ResellerId, &forms.PaymentMethodCreateQuery{
		TenantID:              request.ResellerId,
		SetUpPaymentCode:      true,
		PaymentType:           entity.PaymentType_AWSCPPOReseller,
		PaymentCode:           request.PaymentCode,
		PaymentSubCode:        subCode,
		BindedBillingConfigID: &paymentBillingConfigID,
		Invoiceable:           &invoiceable,
		PaymentMeta:           forms.PaymentMeta{EnlargeMarketplaceMetering: true, EnlargerDiscount: dis},
	})
	if err != nil {
		log.Error("CreateCPPOBillingConfig Payment billing config error", zap.Uint("tenantID", request.TenantID), zap.Uint("resellerID", request.ResellerId), zap.Error(err))
		return err, nil
	}
	for _, d := range request.Discounts {
		d.BindedBillingConfigID = &paymentBillingConfigID
		d.StartedAt = *request.EffectiveDate
		info, err := discountModel.CreateDiscount(request.ResellerId, &d)
		if err != nil {
			log.Error("failed to create discount", zap.Error(err))
			return nil, err
		}
		log.Info("success to create discount for cppo reseller", zap.Any("info", info))
	}
	return forms.OK, nil
}

// @Summary Create CPPO Billing Config
// @Schemes
// @Accept json
// @Produce json
// @Param MCPOBillingConfig body forms.MCPOBillingConfig true "MCPOBillingConfig"
// @Success 200 {object} forms.OKType
// @Router /tenants/{tenant_id}/mcpo_billing_config [POST]
func (s *API) CreateMCPOBillingConfig(c *gin.Context) (ginutils.HandlerFuncResult, error) {
	request := &forms.MCPOBillingConfig{}
	if err := c.ShouldBindJSON(request); err != nil {
		return nil, billing_errors.NewInvalidInputError(err)
	}
	log.Info("Create MCPO BillingConfig ", zap.Any("request", request))

	dis := decimal.NewFromInt(1)
	var disDetected bool
	for _, d := range request.Discounts {
		if strings.EqualFold(d.Usage, constant.DiscountUsageMCPO) {
			if disDetected {
				return nil, billing_errors.NewInvalidInputError(errors.New("cannot assign cppo discount twice"))
			}
			dis = d.Value
			disDetected = true
		}
	}
	if request.SupportPlanInfo != nil {
		supportPlanModel := &marketing.SupportPlanModel{}
		request.SupportPlanInfo.Public = false
		supportPlan, err := supportPlanModel.CreateSupportPlan(&request.SupportPlanInfo.SupportPlan)
		if err != nil {
			return nil, err
		}
		request.SupportPlanInfo.SupportPlanID = supportPlan.SupportPlanID
	}
	customerBillingConfigID, paymentBillingConfigID, err := BillingConfigModel.CreateMarketplaceResellerBillingConfig(request.MarketplaceResellerBillingConfig)
	if err != nil {
		return nil, billing_errors.NewSystemError(err)
	}
	var paymentModel = paymentsub.PaymentModel{}
	_, err = paymentModel.CreatePaymentMethodAndActive(request.TenantID, &forms.PaymentMethodCreateQuery{
		TenantID:              request.TenantID,
		PaymentType:           entity.PaymentType_GCPMCPOTransfer,
		BindedBillingConfigID: &customerBillingConfigID,
	})
	if err != nil {
		log.Error("Create MCPO BillingConfig Customer Billing config error", zap.Uint("tenantID", request.TenantID), zap.Error(err))
		return err, nil
	}
	invoiceable := false
	_, err = paymentModel.CreatePaymentMethodAndActive(request.ResellerId, &forms.PaymentMethodCreateQuery{
		TenantID:              request.ResellerId,
		SetUpPaymentCode:      true,
		PaymentType:           entity.PaymentType_GCPMCPOReseller,
		PaymentCode:           request.PaymentCode,
		BindedBillingConfigID: &paymentBillingConfigID,
		Invoiceable:           &invoiceable,
		PaymentMeta:           forms.PaymentMeta{EnlargeMarketplaceMetering: true, EnlargerDiscount: dis},
	})
	if err != nil {
		log.Error("CreateCPPOBillingConfig Payment billing config error", zap.Uint("tenantID", request.TenantID), zap.Uint("resellerID", request.ResellerId), zap.Error(err))
		return err, nil
	}
	for _, d := range request.Discounts {
		d.BindedBillingConfigID = &paymentBillingConfigID
		d.StartedAt = *request.EffectiveDate
		info, err := discountModel.CreateDiscount(request.ResellerId, &d)
		if err != nil {
			log.Error("failed to create discount", zap.Error(err))
			return nil, err
		}
		log.Info("success to create discount for cppo reseller", zap.Any("info", info))
	}
	return forms.OK, nil
}

func InitializeBillingConfigRouter(router ginutils.MyRoute) {
	s := &API{}
	router.HandleWithError("GET", "/tenants/:tenant_id/billing_configs", s.ListBillingConfigs)
	router.HandleWithError("GET", "billing_configs/managed_tenants/:tenant_id", s.GetBillingConfigByManagedTenantID)
	router.HandleWithError("POST", "/tenants/:tenant_id/cppo_billing_config", s.CreateCPPOBillingConfig)
	router.HandleWithError("POST", "/tenants/:tenant_id/mcpo_billing_config", s.CreateMCPOBillingConfig)

}
