package api

import (
	"errors"
	"fmt"

	"github.com/gin-gonic/gin"
	"github.com/pingcap/dbaas/billing2/billing_errors"
	"github.com/pingcap/dbaas/billing2/config"
	"github.com/pingcap/dbaas/billing2/db/entity"
	"github.com/pingcap/dbaas/billing2/forms"
	"github.com/pingcap/dbaas/billing2/handler/auditloghandler"
	"github.com/pingcap/dbaas/billing2/handler/centralhandler"
	"github.com/pingcap/dbaas/billing2/handler/paymenthandler"
	"github.com/pingcap/dbaas/billing2/model/creditriskcontrol"
	"github.com/pingcap/dbaas/billing2/model/marketing/plan"
	"github.com/pingcap/dbaas/billing2/utils/ginutils"
	"github.com/pingcap/dbaas/billing2/utils/log"
	"github.com/pingcap/dbaas/billing2/utils/uncutils"
	"github.com/tidbcloud/account/idl/pbgen/proto/account"
	"github.com/tidbcloud/tiinf/sdk/auditlog"
	"go.uber.org/zap"
)

// only for stripe payment setup
// this code will not move to payment model.

type TenantPaymentStruct struct {
	TenantID        uint   `uri:"tenant_id" binding:"required"`
	PaymentMethodID string `uri:"payment_method_id" binding:"required"`
}

type BillingCfgStructStripeInvoice struct {
	BillingCfgStruct
	StripeInvoiceID string `uri:"invoice_id"`
}

func getTenantAndInvoiceFromURI(c *gin.Context) (*forms.BillingConfig, string, error) {
	uri := &BillingCfgStructStripeInvoice{}
	err := c.ShouldBindUri(uri)
	if err != nil {
		return nil, "", billing_errors.NewInvalidInputError(err)
	}
	billingConfig, err := getBillingCfgFromURI(c)
	if err != nil {
		return nil, "", billing_errors.NewInvalidInputError(err)
	}
	return billingConfig, uri.StripeInvoiceID, nil
}

func getTenantAndMethodFromURI(c *gin.Context) (uint, string, error) {
	uri := &TenantPaymentStruct{}
	err := c.ShouldBindUri(uri)
	if err != nil {
		return 0, "0", billing_errors.NewInvalidInputError(err)
	}
	return uri.TenantID, uri.PaymentMethodID, nil
}

func CreateStripeIfNotExists(tenantID uint, billingConfigID uint) error {
	_, err := paymentModel.GetPaymentMethodWithPaymentTypeCurrentBillingConfig(tenantID, entity.PaymentType_STRIPE)
	if err == nil {
		return nil
	}
	if !errors.Is(err, billing_errors.ObjectNotExistsError) {
		return err
	}
	_, err = paymentModel.GetActivePaymentMethod(tenantID, billingConfigID)
	if err == nil {
		return billing_errors.NewInvalidInputError(errors.New("can not auto create tenant stripe payment method when already have another payment method"))
	}
	if !errors.Is(err, billing_errors.ObjectNotExistsError) {
		return err
	}

	_, err = paymentModel.CreatePaymentMethodAndActive(tenantID, &forms.PaymentMethodCreateQuery{
		PaymentType:           entity.PaymentType_STRIPE,
		BindedBillingConfigID: &billingConfigID,
	})
	if err != nil {
		return err
	}
	return nil
}

func getStripeProvider(tenantID, billingConfigID uint) (paymenthandler.StripePaymentHandler, string, error) {
	paymentInfo, err := paymentModel.GetPaymentMethodWithPaymentType(tenantID, billingConfigID, entity.PaymentType_STRIPE)
	if err != nil {
		return nil, "", err
	}
	handler, err := paymenthandler.GetPaymentHandler().GetProvider(entity.PaymentType_STRIPE)
	if err != nil {
		return nil, "", err
	}
	return handler.(paymenthandler.StripePaymentHandler), paymentInfo.PaymentCode, nil
}

// SetupIntent call setupIntent API can return the tokens
// @Summary SetupIntent
// @Tags StripePayment
// @Description SetupIntent
// @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.SetupIntent
// @Router /tenants/{tenant_id}/payments/stripe/setup_intent [post]
// @Router /tenants/{tenant_id}/billing_config_id/{billing_config_id}/payments/stripe/setup_intent [post]
func (s *API) SetupIntent(c *gin.Context) (ginutils.HandlerFuncResult, error) {
	billingCfgStruct, err := getBillingCfgFromURI(c)
	if err != nil {
		return nil, err
	}
	err = CreateStripeIfNotExists(billingCfgStruct.TenantID, billingCfgStruct.BillingConfigID)
	if err != nil {
		return nil, err
	}
	handler, code, err := getStripeProvider(billingCfgStruct.TenantID, billingCfgStruct.BillingConfigID)
	if err != nil {
		return nil, err
	}

	res, err := handler.SetupIntent(code)
	if err != nil {
		return nil, err
	}

	return res, nil
}

// ConfirmIntent callback after setupintent sync cards with stripe
// @Summary ConfirmIntent
// @Tags StripePayment
// @Description ConfirmIntent
// @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.OKType
// @Router /tenants/{tenant_id}/payments/stripe/setup_intent/confirm [put]
// @Router /tenants/{tenant_id}/billing_config_id/{billing_config_id}/payments/stripe/setup_intent/confirm [put]
func (s *API) ConfirmIntent(c *gin.Context) (ret ginutils.HandlerFuncResult, retErr error) {
	tenantID, err := getTenantIDFromURI(c)
	if err != nil {
		return nil, err
	}
	billingCfgStruct, err := getBillingCfgFromURI(c)
	if err != nil {
		return nil, err
	}

	handler, code, err := getStripeProvider(tenantID, billingCfgStruct.BillingConfigID)
	if err != nil {
		return nil, err
	}

	methods, err := handler.ListPaymentMethods(code)
	if err != nil {
		return nil, err
	}

	if len(methods) == 1 {
		_, err := handler.SetPaymentMethodDefault(code, methods[0].ID)
		if err != nil {
			return nil, err
		}

	}

	err = paymentModel.SyncPayableByBillingConfigID(tenantID, billingCfgStruct.BillingConfigID)
	if err != nil {
		return nil, err
	}

	// FE requires Tenant Plan change after add card, so we will try to enable it.
	err = (&plan.TenantPlanModel{}).EnableTenantOnDemandPlanByBillingConfigID(tenantID, billingCfgStruct.BillingConfigID)
	if err != nil {
		return nil, err
	}

	return forms.OK, nil
}

// ListPaymentMethods return the payment methods
// @Summary ListPaymentMethods
// @Tags StripePayment
// @Description ListPaymentMethods
// @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.StripePaymentMethod
// @Router  /tenants/{tenant_id}/payments/stripe/payment_methods [get]
// @Router  /tenants/{tenant_id}/billing_config_id/{billing_config_id}/payments/stripe/payment_methods [get]
func (s *API) ListPaymentMethods(c *gin.Context) (ginutils.HandlerFuncResult, error) {
	tenantID, err := getTenantIDFromURI(c)
	if err != nil {
		return nil, err
	}
	billingCfgStruct, err := getBillingCfgFromURI(c)
	if err != nil {
		return nil, err
	}

	handler, code, err := getStripeProvider(tenantID, billingCfgStruct.BillingConfigID)
	if err != nil {
		return []forms.StripePaymentMethod{}, nil
	}

	res, err := handler.ListPaymentMethods(code)
	if err != nil {
		return nil, err
	}
	return res, nil
}

// UpdatePaymentMethod update customer
// @Summary UpdatePaymentMethod
// @Tags StripePayment
// @Description UpdatePaymentMethod
// @Accept  json
// @Produce json
// @Param tenant_id path string true "tenant_id"
// @Param billing_config_id path int false "billing_config_id"
// @Param payment_method_id path string true "payment_method_id"
// @Param method body forms.StripePaymentMethod true "method"
// @Success 200 {object} forms.StripePaymentMethod
// @Router /tenants/{tenant_id}/payments/stripe/payment_methods/{payment_method_id} [put]
// @Router /tenants/{tenant_id}/billing_config_id/{billing_config_id}/payments/stripe/payment_methods/{payment_method_id} [put]
func (s *API) UpdatePaymentMethod(c *gin.Context) (ret ginutils.HandlerFuncResult, retErr error) {
	tenantID, methodID, err := getTenantAndMethodFromURI(c)
	if err != nil {
		return nil, err
	}
	billingCfgStruct, err := getBillingCfgFromURI(c)
	if err != nil {
		return nil, err
	}
	handler, code, err := getStripeProvider(tenantID, billingCfgStruct.BillingConfigID)
	if err != nil {
		return nil, err
	}
	method := &forms.StripePaymentMethod{}

	err = c.ShouldBindJSON(method)
	if err != nil {
		return nil, err
	}
	defer func() {
		auditloghandler.Log(auditlog.EventUpdatePaymentCard, c, tenantID, auditloghandler.StripePaymentMethod2AuditLogDetails(method), retErr)
	}()
	res, err := handler.UpdatePaymentMethod(code, methodID, method)
	if err != nil {
		log.Info("failed update a payment method for customer", zap.Uint("tenant", tenantID), zap.String("method id", methodID), zap.Error(err))
		return nil, err
	}

	log.Info("update a payment method for customer", zap.Uint("tenant", tenantID), zap.String("method id", methodID))
	return res, nil

}

// DeletePaymentMethod update customer
// @Summary DeletePaymentMethod
// @Tags StripePayment
// @Description DeletePaymentMethod
// @Accept  json
// @Produce json
// @Param tenant_id path string true "tenant_id"
// @Param billing_config_id path int false "billing_config_id"
// @Param payment_method_id path string true "payment_method_id"
// @Success 200 {object} forms.OKType
// @Router /tenants/{tenant_id}/payments/stripe/payment_methods/{payment_method_id} [delete]
// @Router /tenants/{tenant_id}/billing_config_id/{billing_config_id}/payments/stripe/payment_methods/{payment_method_id} [delete]
func (s *API) DeletePaymentMethod(c *gin.Context) (ret ginutils.HandlerFuncResult, retErr error) {
	tenantID, methodID, err := getTenantAndMethodFromURI(c)
	if err != nil {
		return nil, err
	}
	billingCfgStruct, err := getBillingCfgFromURI(c)
	if err != nil {
		return nil, err
	}
	defer func() {
		details := &auditlog.Details{"payment_method_id": methodID}
		auditloghandler.Log(auditlog.EventDeletePaymentCard, c, tenantID, details, retErr)
	}()
	handler, code, err := getStripeProvider(tenantID, billingCfgStruct.BillingConfigID)
	if err != nil {
		return nil, err
	}
	err = handler.DeletePaymentMethod(code, methodID)
	if err != nil {
		log.Info("failed delete a payment method for customer", zap.Uint("tenant", tenantID), zap.String("method id", methodID), zap.Error(err))
		return nil, err
	}

	log.Info("delete a payment method for customer", zap.Uint("tenant", tenantID), zap.String("method id", methodID))
	return forms.OK, nil
}

// SetPaymentMethodDefault
// @Summary SetPaymentMethodDefault
// @Tags StripePayment
// @Description SetPaymentMethodDefault
// @Accept  json
// @Produce json
// @Param tenant_id path string true "tenant_id"
// @Param billing_config_id path int false "billing_config_id"
// @Param payment_method_id path string true "payment_method_id"
// @Success 200 {object} forms.OKType
// @Router /tenants/{tenant_id}/payments/stripe/payment_methods/{payment_method_id}/default [put]
// @Router /tenants/{tenant_id}/billing_config_id/{billing_config_id}/payments/stripe/payment_methods/{payment_method_id}/default [put]
func (s *API) SetPaymentMethodDefault(c *gin.Context) (ret ginutils.HandlerFuncResult, retErr error) {
	tenantID, methodID, err := getTenantAndMethodFromURI(c)
	if err != nil {
		return nil, err
	}
	billingCfgStruct, err := getBillingCfgFromURI(c)
	if err != nil {
		return nil, err
	}
	defer func() {
		details := &auditlog.Details{"payment_method_id": methodID}
		auditloghandler.Log(auditlog.EventSetDefaultPaymentCard, c, tenantID, details, retErr)
	}()
	handler, code, err := getStripeProvider(tenantID, billingCfgStruct.BillingConfigID)
	if err != nil {
		return nil, err
	}

	_, err = handler.SetPaymentMethodDefault(code, methodID)
	if err != nil {
		log.Info("failed set default payment method for customer", zap.Uint("tenant", tenantID), zap.String("method id", methodID), zap.Error(err))
		return nil, err
	}

	log.Info("set default payment method for customer", zap.Uint("tenant", tenantID), zap.String("method id", methodID))
	return forms.OK, nil
}

// GetCustomerInfo
// @Summary GetCustomerInfo
// @Tags StripePayment
// @Description GetCustomerInfo
// @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.StripeCustomerInfo
// @Router /tenants/{tenant_id}/payments/stripe/customer [get]
// @Router /tenants/{tenant_id}/billing_config_id/{billing_config_id}/payments/stripe/customer [get]
func (s *API) GetCustomerInfo(c *gin.Context) (ginutils.HandlerFuncResult, error) {
	tenantID, err := getTenantIDFromURI(c)
	if err != nil {
		return nil, err
	}
	billingCfgStruct, err := getBillingCfgFromURI(c)
	if err != nil {
		return nil, err
	}
	handler, code, err := getStripeProvider(tenantID, billingCfgStruct.BillingConfigID)
	if err != nil {
		return forms.StripeCustomerInfo{}, nil
	}

	return handler.GetCustomerInfo(code)
}

// ListStripeInvoices
// @Summary ListStripeInvoices
// @Tags StripePayment
// @Description ListStripeInvoices
// @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.StripeInvoice
// @Router /tenants/{tenant_id}/payments/stripe/invoices [get]
// @Router /tenants/{tenant_id}/billing_config_id/{billing_config_id}/payments/stripe/invoices [get]
func (s *API) ListStripeInvoices(c *gin.Context) (ginutils.HandlerFuncResult, error) {
	tenantID, err := getTenantIDFromURI(c)
	if err != nil {
		return nil, err
	}
	billingCfgStruct, err := getBillingCfgFromURI(c)
	if err != nil {
		return nil, err
	}
	handler, code, err := getStripeProvider(tenantID, billingCfgStruct.BillingConfigID)
	if err != nil {
		return []forms.StripeInvoice{}, nil
	}

	return handler.ListStripeInvoicesWithCache(code)
}

// GetStripeInvoiceDetail
// @Id Stripe_GetStripeInvoiceDetail
// @Summary GetstripeInvoiceDetail
// @Tags StripePayment
// @Description GetstripeInvoiceDetail
// @Accept  json
// @Produce json
// @Param tenant_id path string true "tenant_id"
// @Param billing_config_id path int true "billing_config_id"
// @Param invoice_id path string true "invoice_id"
// @Success 200 {object} forms.StripeInvoiceDetail
// @Router /tenants/{tenant_id}/billing_config_id/{billing_config_id}/payments/stripe/invoices/{invoice_id} [get]
func (s *API) GetStripeInvoiceDetail(c *gin.Context) (ginutils.HandlerFuncResult, error) {
	billingCfgStruct, stripeVoiceID, err := getTenantAndInvoiceFromURI(c)
	if err != nil {
		return nil, err
	}
	handler, code, err := getStripeProvider(billingCfgStruct.TenantID, billingCfgStruct.BillingConfigID)
	if err != nil {
		return forms.StripeCustomerInfo{}, nil
	}
	result, err := handler.GetStripeInvoice(code, stripeVoiceID)
	if err != nil {
		return nil, billing_errors.NewSystemError(err)
	}
	return result, nil
}

// CreditCardValidate
// @Id Stripe_CreditCardValidate
// @Summary credit card validation, query after s.SetUpIntent, before s.ConfirmIntent
// @Tags StripePayment
// @Description credit card validation. if pass, attach the paymentMethod to customer
// @Accept  json
// @Produce json
// @Param tenant_id path string true "tenant_id"
// @Param billing_config_id path int true "billing_config_id"
// @Param payment_method_id path string true "payment_method_id"
// @Success 200 {object} forms.CardValidateResult
// @Router /tenants/{tenant_id}/billing_config_id/{billing_config_id}/payments/stripe/payment_methods/{payment_method_id}/validate [PUT]
func (s *API) CreditCardValidate(c *gin.Context) (ret ginutils.HandlerFuncResult, retErr error) {
	// param parse
	tenantPaymentStruct := &TenantPaymentStruct{}
	bindingErr := c.ShouldBindUri(tenantPaymentStruct)

	if bindingErr != nil {
		return nil, billing_errors.NewInvalidInputError(bindingErr)
	}
	billingCfgStruct, err := getBillingCfgFromURI(c)
	if err != nil {
		return nil, billing_errors.NewInvalidInputError(err)
	}
	defer func() {
		details := &auditlog.Details{"payment_method_id": tenantPaymentStruct.PaymentMethodID}
		auditloghandler.Log(auditlog.EventAddPaymentCard, c, tenantPaymentStruct.TenantID, details, retErr)
	}()
	// validate credit card
	return (&creditriskcontrol.CreditCardVerifyModel{}).CreditCardValidate(tenantPaymentStruct.TenantID, billingCfgStruct.BillingConfigID, tenantPaymentStruct.PaymentMethodID)
}

// InvoicePaymentRequiredNotification
// @Id Stripe_CreditCardPaymentRequiredNotification
// @Summary manually send notification and email to notify customer need to pay the invoice
// @Description manually send notification and email to notify customer need to pay the invoice
// @Accept  json
// @Produce json
// @Param tenant_id path string true "tenant_id"
// @Param billing_config_id path int true "billing_config_id"
// @Param invoice_id path string true "invoice_id"
// @Success 200 {object} forms.OKType
// @Router /tenants/{tenant_id}/billing_config_id/{billing_config_id}/payments/stripe/invoices/{invoice_id}/payment_required_notification [POST]
func (s *API) InvoicePaymentRequiredNotification(c *gin.Context) (ret ginutils.HandlerFuncResult, retErr error) {
	billingCfgStruct, stripeVoiceID, err := getTenantAndInvoiceFromURI(c)
	if err != nil {
		return nil, err
	}
	handler, code, err := getStripeProvider(billingCfgStruct.TenantID, billingCfgStruct.BillingConfigID)
	if err != nil {
		return forms.StripeCustomerInfo{}, nil
	}
	result, err := handler.GetStripeInvoice(code, stripeVoiceID)
	if err != nil {
		return nil, billing_errors.NewSystemError(err)
	}
	payload := map[string]string{
		"invoiceMonth": result.StartDate.Format("2006-01"),
	}
	key := fmt.Sprintf("invoice-payment-required-notification-%s-%d", stripeVoiceID, billingCfgStruct.TenantID)
	uncutils.SendInAppNotification(billingCfgStruct.TenantID, uncutils.PaymentActionRequired, payload)
	err = centralhandler.GetCentralHandler().SendTenantEmailWithLabelOnce(
		billingCfgStruct.TenantID, key, config.Get().GetString(config.PaymentRequiredEmailTemplateID),
		payload,
		account.MailLabels_MAIL_LABELS_ORG_OWNER,
		[]account.MailLabels{account.MailLabels_MAIL_LABELS_ORG_BILLING_ADMINS},
	)
	if err != nil {
		return nil, billing_errors.NewSystemError(err)
	}
	return forms.OK, err
}

func InitializeStripePaymentRouter(router ginutils.MyRoute) {
	s := &API{}
	router.HandleWithError("POST", "/tenants/:tenant_id/payments/stripe/setup_intent", s.SetupIntent)
	router.HandleWithError("PUT", "/tenants/:tenant_id/payments/stripe/setup_intent/confirm", s.ConfirmIntent)
	router.HandleWithError("GET", "/tenants/:tenant_id/payments/stripe/customer", s.GetCustomerInfo)
	router.HandleWithError("GET", "/tenants/:tenant_id/payments/stripe/payment_methods", s.ListPaymentMethods)
	router.HandleWithError("PUT", "/tenants/:tenant_id/payments/stripe/payment_methods/:payment_method_id", s.UpdatePaymentMethod)
	router.HandleWithError("DELETE", "/tenants/:tenant_id/payments/stripe/payment_methods/:payment_method_id", s.DeletePaymentMethod)
	router.HandleWithError("PUT", "/tenants/:tenant_id/payments/stripe/payment_methods/:payment_method_id/default", s.SetPaymentMethodDefault)
	router.HandleWithError("GET", "/tenants/:tenant_id/payments/stripe/invoices", s.ListStripeInvoices)
	router.HandleWithError("PUT", "/tenants/:tenant_id/payments/stripe/payment_methods/:payment_method_id/validate", s.CreditCardValidate)
	// for billing config add api
	router.HandleWithError("POST", "/tenants/:tenant_id/billing_config_id/:billing_config_id/payments/stripe/setup_intent", s.SetupIntent)
	router.HandleWithError("GET", "/tenants/:tenant_id/billing_config_id/:billing_config_id/payments/stripe/invoices", s.ListStripeInvoices)
	router.HandleWithError("GET", "/tenants/:tenant_id/billing_config_id/:billing_config_id/payments/stripe/invoices/:invoice_id", s.GetStripeInvoiceDetail)
	router.HandleWithError("PUT", "/tenants/:tenant_id/billing_config_id/:billing_config_id/payments/stripe/payment_methods/:payment_method_id/validate", s.CreditCardValidate)

	//no modify
	router.HandleWithError("PUT", "/tenants/:tenant_id/billing_config_id/:billing_config_id/payments/stripe/setup_intent/confirm", s.ConfirmIntent)
	router.HandleWithError("GET", "/tenants/:tenant_id/billing_config_id/:billing_config_id/payments/stripe/customer", s.GetCustomerInfo)
	router.HandleWithError("GET", "/tenants/:tenant_id/billing_config_id/:billing_config_id/payments/stripe/payment_methods", s.ListPaymentMethods)
	router.HandleWithError("PUT", "/tenants/:tenant_id/billing_config_id/:billing_config_id/payments/stripe/payment_methods/:payment_method_id", s.UpdatePaymentMethod)
	router.HandleWithError("DELETE", "/tenants/:tenant_id/billing_config_id/:billing_config_id/payments/stripe/payment_methods/:payment_method_id", s.DeletePaymentMethod)
	router.HandleWithError("PUT", "/tenants/:tenant_id/billing_config_id/:billing_config_id/payments/stripe/payment_methods/:payment_method_id/default", s.SetPaymentMethodDefault)

	router.HandleWithError("POST", "/tenants/:tenant_id/billing_config_id/:billing_config_id/payments/stripe/invoices/:invoice_id/payment_required_notification", s.InvoicePaymentRequiredNotification)

}
