package api

import (
	"errors"

	"github.com/gin-gonic/gin"
	"github.com/pingcap/dbaas/billing2/billing_errors"
	"github.com/pingcap/dbaas/billing2/db/entity"
	"github.com/pingcap/dbaas/billing2/forms"
	"github.com/pingcap/dbaas/billing2/handler/paymenthandler"
	"github.com/pingcap/dbaas/billing2/model/payment/paymentsub"
	"github.com/pingcap/dbaas/billing2/utils/ginutils"
	"github.com/pingcap/dbaas/billing2/utils/log"
	"github.com/pingcap/dbaas/billing2/utils/request"
	"go.uber.org/zap"
)

var paymentModel = paymentsub.PaymentModel{}

func paymentConsoleView(p forms.PaymentMethod) forms.PaymentMethod {
	return forms.PaymentMethod{
		TenantID:    p.TenantID,
		PaymentType: p.PaymentType,
		Payable:     p.Payable,
		Status:      p.Status,
	}
}

// @Summary Get Active PaymentMethod
// @Schemes
// @Accept json
// @Produce json
// @Param tenant_id path string true "tenant_id"
// @Param billing_config_id path int false "billing_config_id"
// @Success 200 {object} forms.PaymentMethod
// @Router /tenants/{tenant_id}/payments/active [get]
// @Router /tenants/{tenant_id}/billing_config_id/{billing_config_id}/payments/active [get]
func (s *API) GetActivePayment(c *gin.Context) (ginutils.HandlerFuncResult, error) {
	billingCfgStruct, err := getBillingCfgFromURI(c)
	if err != nil {
		return nil, err
	}
	info, err := paymentModel.GetActivePaymentMethod(billingCfgStruct.TenantID, billingCfgStruct.BillingConfigID)
	if err != nil {
		if errors.Is(err, billing_errors.ObjectNotExistsError) {
			return &forms.PaymentMethod{
				TenantID:    billingCfgStruct.TenantID,
				PaymentType: entity.PaymentType_STRIPE,
				Payable:     false,
				Status:      entity.PaymentMethodStatus_Active,
			}, nil
		}
		return nil, err
	}
	if err != nil {
		return nil, err
	}
	if !request.SourceFromOps(c.Request) {
		return paymentConsoleView(*info), nil
	}
	return info, err
}

// @Summary List Tenant Payment Methods
// @Schemes
// @Accept json
// @Produce json
// @Param tenant_id path string true "tenant_id"
// @Param billing_config_id path int false "billing_config_id"
// @Success 200 {object} []forms.PaymentMethod
// @Router /tenants/{tenant_id}/payments [get]
// @Router /tenants/{tenant_id}/billing_config_id/{billing_config_id}/payments [get]
func (s *API) ListPayments(c *gin.Context) (ginutils.HandlerFuncResult, error) {
	billingCfgStruct, err := getBillingCfgFromURI(c)
	if err != nil {
		return nil, err
	}
	infoList, err := paymentModel.ListPaymentMethods(billingCfgStruct.TenantID, billingCfgStruct.BillingConfigID)
	if err != nil {
		return nil, err
	}
	if !request.SourceFromOps(c.Request) {
		resp := make([]forms.PaymentMethod, len(infoList))
		for i, info := range infoList {
			resp[i] = paymentConsoleView(info)
		}
		return resp, nil
	}
	return infoList, nil
}

// @Summary List Tenant Payment Types
// @Schemes
// @Accept json
// @Produce json
// @Param tenant_id path string true "tenant_id"
// @Param billing_config_id path int false "billing_config_id"
// @Success 200 {object} []forms.PaymentMethodType
// @Router /tenants/{tenant_id}/payments/types [get]
// @Router /tenants/{tenant_id}/billing_config_id/{billing_config_id}/payments/types [get]
func (s *API) ListPaymentTypes(c *gin.Context) (ginutils.HandlerFuncResult, error) {
	billingCfgStruct, err := getBillingCfgFromURI(c)
	if err != nil {
		return nil, err
	}

	var result []forms.PaymentMethodType

	//msp-customer
	isMspPayment, err := paymentModel.IsMspPayment(billingCfgStruct.TenantID, billingCfgStruct.BillingConfigID)
	if isMspPayment {
		mspCustomerResult := append(result, forms.PaymentMethodType{
			TenantID:    billingCfgStruct.TenantID,
			PaymentType: entity.PaymentType_MSP,
			Payable:     true,
		})
		return mspCustomerResult, nil
	}
	payableMap := map[string]bool{entity.PaymentType_WireTransfer: true}
	payments, err := paymentModel.ListPaymentMethods(billingCfgStruct.TenantID, billingCfgStruct.BillingConfigID)
	if err != nil {
		return nil, err
	}
	types := paymenthandler.GetPaymentHandler().AllProviders()
	for _, p := range payments {
		payableMap[p.PaymentType] = p.Payable
	}
	if _, ok := payableMap[entity.PaymentType_STRIPE]; !ok {
		payableMap[entity.PaymentType_STRIPE] = false
	}
	for _, t := range types {
		if payable, ok := payableMap[t]; ok {
			result = append(result, forms.PaymentMethodType{
				TenantID:    billingCfgStruct.TenantID,
				PaymentType: t,
				Payable:     payable,
			})
		}
	}
	return result, nil
}

type paymentQueryStruct struct {
	BillingCfgStruct
	PaymentType string `uri:"payment_type" binding:"required"`
}

// @Summary Setup PaymentMethod
// @Schemes
// @Accept json
// @Produce json
// @Param tenant_id path string true "tenant_id"
// @Param billing_config_id path int false "billing_config_id"
// @Param payment_type path string true "payment_type, e.g. wire_transfer"
// @Success 200 {object} forms.OKType
// @Router /tenants/{tenant_id}/payments/setup/{payment_type} [post]
// @Router /tenants/{tenant_id}/billing_config_id/{billing_config_id}/payments/setup/{payment_type} [post]
func (s *API) ActivePayment(c *gin.Context) (ginutils.HandlerFuncResult, error) {
	billingCfgStruct, err := getBillingCfgFromURI(c)
	if err != nil {
		return nil, err
	}

	paymentQuery := &paymentQueryStruct{}
	err = c.ShouldBindUri(paymentQuery)
	if err != nil {
		return 0, billing_errors.NewInvalidInputError(err)
	}
	if paymentQuery.PaymentType == entity.PaymentType_MSP {
		return 0, billing_errors.NewInvalidInputError(errors.New("Not MSP Customer  don't support msp PaymentMethod."))
	}
	isMspPayment, err := paymentModel.IsMspPayment(billingCfgStruct.TenantID, billingCfgStruct.BillingConfigID)
	if err != nil {
		return nil, err
	}
	if isMspPayment {
		return 0, billing_errors.NewInvalidInputError(errors.New("MSP Customer PaymentMethod don't support modify."))
	}

	paymentMethod, err := paymentModel.GetPaymentMethodWithPaymentTypeCurrentBillingConfig(paymentQuery.TenantID, paymentQuery.PaymentType)
	if err != nil {
		if errors.Is(err, billing_errors.ObjectNotExistsError) {
			switch paymentQuery.PaymentType {
			case entity.PaymentType_WireTransfer, entity.PaymentType_STRIPE:
				_, err := paymentModel.CreatePaymentMethodAndActive(paymentQuery.TenantID, &forms.PaymentMethodCreateQuery{
					TenantID:              paymentQuery.TenantID,
					PaymentType:           paymentQuery.PaymentType,
					BindedBillingConfigID: &billingCfgStruct.BillingConfigID})
				if err != nil {
					return nil, err
				}
				return forms.OK, nil
			}
		} else {
			return nil, err
		}
	}

	err = paymentModel.ActivePaymentMethod(paymentQuery.TenantID, billingCfgStruct.BillingConfigID, paymentMethod.PaymentMethodID)
	if err != nil {
		return nil, err
	}
	return forms.OK, nil
}

// @Summary Get All Payable TenantIDs
// @Schemes
// @Accept json
// @Produce json
// @Success 200 {object} []forms.TenantID
// @Router /opened_credits/tenant_ids [get]
func (s *API) GetAllPayableTenantIDS(c *gin.Context) (ginutils.HandlerFuncResult, error) {
	return paymentModel.GetAllPayableTenantIDs()
}

// @Summary Update Payment Code and SubCode
// @Schemes
// @Accept json
// @Produce json
// @Param tenant_id path string true "tenant_id"
// @Param billing_config_id path string true "billing_config_id"
// @Param payment_type path string true "payment_type, e.g. wire_transfer"
// @Param payment_code_update_query body forms.PaymentCodeUpdateQuery false "PaymentCodeUpdateQuery"
// @Success 200 {object} forms.PaymentMethod
// @Router /tenants/{tenant_id}/billing_config_id/{billing_config_id}/payments/{payment_type}/code [put]
func (s *API) UpdatePaymentCode(c *gin.Context) (ginutils.HandlerFuncResult, error) {
	paymentQuery := &paymentQueryStruct{}
	err := c.ShouldBindUri(paymentQuery)
	if err != nil {
		return 0, billing_errors.NewInvalidInputError(err)
	}
	var body forms.PaymentCodeUpdateQuery
	err = c.ShouldBindJSON(&body)
	if err != nil {
		return nil, err
	}
	if body.PaymentSubCode != nil && *body.PaymentSubCode == "" {
		body.PaymentSubCode = nil
	}
	paymentMethod, err := paymentModel.GetPaymentMethodWithPaymentType(paymentQuery.TenantID, paymentQuery.BillingConfigID, paymentQuery.PaymentType)
	if err != nil {
		return nil, err
	}
	if paymentMethod.Invoiceable {
		return nil, billing_errors.NewForbiddenError(errors.New("updating code for invoiceable payment is forbidden"))
	}
	return paymentModel.UpdatePaymentCode(paymentQuery.TenantID, paymentMethod.PaymentMethodID, body.PaymentCode, body.PaymentSubCode)
}

// @Summary Update Payment Invoiceable
// @Schemes
// @Accept json
// @Produce json
// @Param tenant_id path string true "tenant_id"
// @Param billing_config_id path string true "billing_config_id"
// @Param payment_type path string true "payment_type, e.g. wire_transfer"
// @Success 200 {object} forms.PaymentMethod
// @Router /tenants/{tenant_id}/billing_config_id/{billing_config_id}/payments/{payment_type}/invoiceable [put]
func (s *API) UpdatePaymentInvoiceable(c *gin.Context) (ginutils.HandlerFuncResult, error) {
	paymentQuery := &paymentQueryStruct{}
	err := c.ShouldBindUri(paymentQuery)
	if err != nil {
		return 0, billing_errors.NewInvalidInputError(err)
	}
	paymentMethod, err := paymentModel.GetPaymentMethodWithPaymentType(paymentQuery.TenantID, paymentQuery.BillingConfigID, paymentQuery.PaymentType)
	if err != nil {
		return nil, err
	}
	if !paymentMethod.Invoiceable {
		return paymentModel.UpdatePaymentInvoiceable(paymentMethod.PaymentMethodID)
	}
	return paymentMethod, nil
}

func (s *API) paymentType(type_ string) forms.PaymentType {
	switch type_ {
	case entity.PaymentType_AWS:
		return forms.PaymentType_AWS
	case entity.PaymentType_GCP:
		return forms.PaymentType_GCP
	case entity.PaymentType_STRIPE:
		return forms.PaymentType_CreditCard
	case entity.PaymentType_WireTransfer:
		return forms.PaymentType_WireTransfer
	case entity.PaymentType_AWSCPPOTransfer:
		return forms.PaymentType_AWSCPPO
	}
	log.Error("unknown payment type", zap.String("type", type_))
	return forms.PaymentType_None
}

// @Id Billing_GetPartnerPayment
// @Tags Partner, PartnerBilling
// @Summary Get active payment method
// @Schemes
// @Accept json
// @Produce json
// @Param accessOrgId query string true "org_id"
// @Success 200 {object} forms.PaymentForOpenAPI
// @Router /payments [get]
func (s *API) GetPartnerPayment(c *gin.Context) (ginutils.HandlerFuncResult, error) {
	billingCfgStruct, err := getBillingCfgFromAuth(c)
	if err != nil {
		return nil, err
	}
	info, err := paymentModel.GetActivePaymentMethod(billingCfgStruct.TenantID, billingCfgStruct.BillingConfigID)
	if err != nil {
		if errors.Is(err, billing_errors.ObjectNotExistsError) {
			return forms.PaymentForOpenAPI{
				Type:    forms.PaymentType_None,
				Payable: false,
			}, nil
		}
		return nil, err
	}
	return forms.PaymentForOpenAPI{
		Type:    s.paymentType(info.PaymentType),
		Payable: info.Payable,
	}, nil
}

func InitializePaymentRouter(router ginutils.MyRoute) {
	s := &API{}
	router.HandleWithError("POST", "/tenants/:tenant_id/payments/setup/:payment_type", s.ActivePayment)
	router.HandleWithError("GET", "/tenants/:tenant_id/payments", s.ListPayments)
	router.HandleWithError("GET", "/tenants/:tenant_id/payments/types", s.ListPaymentTypes)
	router.HandleWithError("GET", "/tenants/:tenant_id/payments/active", s.GetActivePayment)
	router.HandleWithError("GET", "/payments_payable/tenant_ids", s.GetAllPayableTenantIDS)
	// for billing config add api
	router.HandleWithError("GET", "/tenants/:tenant_id/billing_config_id/:billing_config_id/payments", s.ListPayments)
	router.HandleWithError("GET", "/tenants/:tenant_id/billing_config_id/:billing_config_id/payments/active", s.GetActivePayment)
	router.HandleWithError("GET", "/tenants/:tenant_id/billing_config_id/:billing_config_id/payments/types", s.ListPaymentTypes)
	router.HandleWithError("POST", "/tenants/:tenant_id/billing_config_id/:billing_config_id/payments/setup/:payment_type", s.ActivePayment)
	// param use PRIMARY key or .. , no modify

	router.HandleWithError("PUT", "/tenants/:tenant_id/billing_config_id/:billing_config_id/payments/:payment_type/invoiceable", s.UpdatePaymentInvoiceable)
	router.HandleWithError("PUT", "/tenants/:tenant_id/billing_config_id/:billing_config_id/payments/:payment_type/code", s.UpdatePaymentCode)

	InitializeStripePaymentRouter(router)
}

func InitializePaymentRouterForOpenAPI(router ginutils.MyRoute) {
	s := &API{}
	router.HandleWithError("GET", "/payments", s.GetPartnerPayment)
}
