package api

import (
	"errors"
	"fmt"

	"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/log"
	"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"`
}

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) error {
	_, err := paymentModel.GetPaymentMethod(tenantID)
	if err != nil {
		if errors.Is(err, billing_errors.ObjectNotExistsError) {
			_, err = (&paymentsub.PaymentModel{}).CreatePaymentMethod(tenantID, &forms.PaymentMethod{PaymentType: entity.PaymentType_STRIPE})
			if err != nil {
				return err
			}
			return nil
		}
		return err
	}
	return nil
}

func getStripeProvider(tenantID uint) (paymenthandler.StripePaymentHandler, string, error) {
	paymentInfo, err := paymentModel.GetPaymentMethod(tenantID)
	if err != nil {
		return nil, "?", err
	}
	if paymentInfo.PaymentType != entity.PaymentType_STRIPE {
		return nil, "", billing_errors.NewInvalidInputError(fmt.Errorf("invalid payment type %s", paymentInfo.PaymentType))
	}
	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"
// @Success 200 {object} forms.SetupIntent
// @Router /tenants/{tenant_id}/payments/stripe/setup_intent [post]
func (s *API) SetupIntent(c *gin.Context) (ginutils.HandlerFuncResult, error) {
	tenantID, err := getTenantIDFromURI(c)
	if err != nil {
		return nil, err
	}
	err = CreateStripeIfNotExists(tenantID)
	if err != nil {
		return nil, err
	}
	handler, code, err := getStripeProvider(tenantID)
	if err != nil {
		return nil, err
	}

	res, err := handler.SetupIntent(code)
	if err != nil {
		ginutils.ReturnError(c, 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"
// @Success 200 {object} forms.OKType
// @Router /tenants/{tenant_id}/payments/stripe/setup_intent/confirm [put]
func (s *API) ConfirmIntent(c *gin.Context) (ginutils.HandlerFuncResult, error) {
	tenantID, err := getTenantIDFromURI(c)
	if err != nil {
		return nil, err
	}

	handler, code, err := getStripeProvider(tenantID)
	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.SyncPayable(tenantID)
	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"
// @Success 200 {object} []forms.StripePaymentMethod
// @Router  /tenants/{tenant_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
	}

	handler, code, err := getStripeProvider(tenantID)
	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 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]
func (s *API) UpdatePaymentMethod(c *gin.Context) (ginutils.HandlerFuncResult, error) {
	tenantID, methodID, err := getTenantAndMethodFromURI(c)
	if err != nil {
		return nil, err
	}

	handler, code, err := getStripeProvider(tenantID)
	if err != nil {
		return nil, err
	}
	method := &forms.StripePaymentMethod{}

	err = c.ShouldBindJSON(method)
	if err != nil {
		return nil, err
	}

	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 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]
func (s *API) DeletePaymentMethod(c *gin.Context) (ginutils.HandlerFuncResult, error) {
	tenantID, methodID, err := getTenantAndMethodFromURI(c)
	if err != nil {
		return nil, err
	}

	handler, code, err := getStripeProvider(tenantID)
	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 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]
func (s *API) SetPaymentMethodDefault(c *gin.Context) (ginutils.HandlerFuncResult, error) {
	tenantID, methodID, err := getTenantAndMethodFromURI(c)
	if err != nil {
		return nil, err
	}

	handler, code, err := getStripeProvider(tenantID)
	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"
// @Success 200 {object} forms.StripeCustomerInfo
// @Router /tenants/{tenant_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
	}

	handler, code, err := getStripeProvider(tenantID)
	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"
// @Success 200 {object} []forms.StripeInvoice
// @Router /tenants/{tenant_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
	}

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

	return handler.ListStripeInvoices(code)
}

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)
}
