package paymentsub

import (
	"errors"
	"fmt"

	"github.com/pingcap/dbaas/billing2/billing_errors"
	"github.com/pingcap/dbaas/billing2/db"
	"github.com/pingcap/dbaas/billing2/db/entity"
	"github.com/pingcap/dbaas/billing2/forms"
	"github.com/pingcap/dbaas/billing2/handler/centralhandler"
	"github.com/pingcap/dbaas/billing2/handler/paymenthandler"
	"github.com/pingcap/dbaas/billing2/utils/errorutils"
	"github.com/pingcap/log"
	"go.uber.org/zap"
)

type PaymentModel struct {
}

func getPaymentInfoFromEntity(payment *entity.PaymentMethod) *forms.PaymentMethod {
	ret := forms.PaymentMethod{
		PaymentMethodID: payment.ID,
		TenantID:        payment.TenantID,
		PaymentType:     payment.PaymentType,
		PaymentCode:     payment.PaymentCode,
		Payable:         payment.Payable,
	}
	return &ret
}

func (*PaymentModel) GetPaymentMethod(tenantID uint) (*forms.PaymentMethod, error) {
	paymentEntity := &entity.PaymentMethod{}
	err := errorutils.WrapSQLError(entity.NewPaymentMethodQuerySet(db.GetDB()).TenantIDEq(tenantID).One(paymentEntity), fmt.Sprintf("tenant %d payment method", tenantID))
	if err != nil {
		return nil, err
	}
	return getPaymentInfoFromEntity(paymentEntity), nil
}

func (s *PaymentModel) IsHourlyPayment(tenantID uint) (bool, error) {
	method, err := s.GetPaymentMethod(tenantID)
	if err != nil {
		if errors.Is(err, billing_errors.ObjectNotExistsError) {
			return false, nil
		}
		return false, err
	}
	return method.PaymentType == entity.PaymentType_AWS || method.PaymentType == entity.PaymentType_GCP, nil
}

func (*PaymentModel) CreatePaymentMethod(tenantID uint, paymentInfo *forms.PaymentMethod) (*forms.PaymentMethod, error) {
	paymentHandler, err := paymenthandler.GetPaymentHandler().GetProvider(paymentInfo.PaymentType)
	if err != nil {
		return nil, err
	}

	tenant, err := centralhandler.GetCentralHandler().GetTenant(tenantID)
	if err != nil {
		return nil, err
	}

	userCode, err := paymentHandler.GetUserCode(*tenant)
	if err != nil {
		return nil, err
	}

	payable, err := paymentHandler.Payable(userCode)

	if err != nil {
		return nil, err
	}

	entity := &entity.PaymentMethod{
		TenantID:    tenantID,
		PaymentType: paymentInfo.PaymentType,
		PaymentCode: userCode,
		Payable:     payable,
	}
	err = entity.Create(db.GetDB())
	if err != nil {
		return nil, err
	}
	return getPaymentInfoFromEntity(entity), nil
}

func (s *PaymentModel) SyncPayable(tenantID uint) error {
	paymentInfo, err := s.GetPaymentMethod(tenantID)
	if err != nil {
		if errors.Is(err, billing_errors.ObjectNotExistsError) {
			return nil
		}
		return err
	}
	// WARN now only sync not payable to payable. a user can not from payable to not payable
	if paymentInfo.Payable {
		return nil
	}

	userCode := paymentInfo.PaymentCode
	paymentHandler, err := paymenthandler.GetPaymentHandler().GetProvider(paymentInfo.PaymentType)
	if err != nil {
		return err
	}

	payable, err := paymentHandler.Payable(userCode)

	if err != nil {
		return err
	}
	err = entity.NewPaymentMethodQuerySet(db.GetDB()).IDEq(paymentInfo.PaymentMethodID).GetUpdater().SetPayable(payable).Update()
	if err != nil {
		return billing_errors.NewSystemError(err)
	}
	log.Info("synced tenant payable", zap.Uint("tenant", tenantID), zap.Bool("payable", payable))
	return nil
}

func (s *PaymentModel) Payable(tenantID uint) (bool, error) {
	paymentInfo, err := s.GetPaymentMethod(tenantID)
	if err != nil {
		if errors.Is(err, billing_errors.ObjectNotExistsError) {
			return false, nil
		}
		return false, err
	}
	return paymentInfo.Payable, nil
}

func (s *PaymentModel) Pay(tenantID uint, invoice *forms.Invoice) error {
	paymentInfo, err := s.GetPaymentMethod(tenantID)
	if err != nil {
		return err
	}
	paymentHandler, err := paymenthandler.GetPaymentHandler().GetProvider(paymentInfo.PaymentType)
	if err != nil {
		return err
	}
	err = paymentHandler.Pay(paymentInfo.PaymentCode, invoice)
	if err != nil {
		return err
	}
	return nil
}
