package billing

import (
	"fmt"
	"strconv"
	"time"

	"github.com/pingcap/dbaas/central/config"
	"github.com/pingcap/dbaas/central/db"
	"github.com/pingcap/dbaas/central/db/entity"
	"github.com/pingcap/dbaas/central/handler/billing2"
	"github.com/pingcap/dbaas/central/model/tenant/handler"
	"github.com/pingcap/dbaas/central/utils"
	"github.com/pingcap/errors"
	"github.com/pingcap/log"
	"github.com/shopspring/decimal"
	"github.com/tidbcloud/pkgv2/salesforce"
	"go.uber.org/zap"
	"gorm.io/gorm"
	"gorm.io/gorm/clause"
)

func (c Context) bindOnDemandPackage(t *entity.Tenant) error {
	bindedPackageID, err := billing2.GetHandler().BindPackage(t.ID.Uint64(), config.Global().BillingConfig.BillingPackageIDs.OnDemand)
	if err != nil {
		c.Log().Error("failed bind ondemand package", zap.String("email", t.Email), zap.Error(err))
		return err
	}
	c.Log().Info("successfully bind ondemand package", zap.String("email", t.Email))
	t.Plan = entity.TenantPlanOnDemand
	t.OnDemandBindedPackageID = bindedPackageID

	err = db.Transaction(func(tx *gorm.DB) error {
		return t.Update(tx, entity.TenantDBSchema.Plan, entity.TenantDBSchema.OnDemandBindedPackageID)
	})
	if err != nil {
		c.Log().Error("failed update entity ondemand", zap.String("email", t.Email), zap.Error(err))
		return err
	}
	c.Log().Info("successfully update entity ondemand", zap.String("email", t.Email))
	return nil
}

func (c Context) UpdateSalesForceOnEnableOnDemand() error {
	salesforceConfig := config.Global().SalesforceConfig
	if !salesforceConfig.EnableSalesforce {
		return nil
	}
	err := db.Transaction(func(tx *gorm.DB) error {
		return tx.Clauses(clause.OnConflict{UpdateAll: true}).Create(&entity.TenantSalesforceSyncRecord{
			TenantID: c.GetTenantID(),
			Synced:   false,
		}).Error
	})
	if err != nil {
		return errors.Annotate(err, "failed update salesforce sync record")
	}
	err = c.updateSalesForceOnEnableOnDemand()
	if err != nil {
		return err
	}
	err = db.Transaction(func(tx *gorm.DB) error {
		return entity.NewTenantSalesforceSyncRecordQuerySet(tx).TenantIDEq(c.GetTenantID()).GetUpdater().SetSynced(true).Update()
	})
	if err != nil {
		return errors.Annotate(err, "failed update salesforce sync record")
	}
	return nil
}

func (c Context) updateSalesForceOnEnableOnDemand() error {
	t, err := c.GetTenantEntity()
	if err != nil {
		return err
	}
	account := salesforce.Account{
		Plan:             string(entity.TenantPlanOnDemand),
		PortalID:         strconv.FormatUint(t.ID.Uint64(), 10),
		BillingEmail:     t.BillingEmail,
		BindCreditCardAt: time.Now().UTC().Format(salesforce.TimeFormat),
	}
	err = c.salesforceClient.UpdateAccount(account)
	if err != nil {
		c.Log().Error("failed to call salesforce UpdateAccount API", zap.String("email", t.Email), zap.Error(err))
		return errors.Annotate(err, "failed to call salesforce UpdateAccount API")
	}
	c.Log().Info("successfully call salesforce UpdateAccount API", zap.String("email", t.Email), zap.Reflect("payload", account))
	return nil
}

func transNullableStringToEmpty(input *string) string {
	if input == nil {
		return ""
	}
	return *input
}

func (c Context) SyncSalesforce() error {
	salesforceConfig := config.Global().SalesforceConfig
	if !salesforceConfig.EnableSalesforce {
		return nil
	}
	err := db.Transaction(func(tx *gorm.DB) error {
		return tx.Clauses(clause.OnConflict{UpdateAll: true}).Create(&entity.TenantSalesforceSyncRecord{
			TenantID: c.GetTenantID(),
			Synced:   false,
		}).Error
	})
	if err != nil {
		return errors.Annotate(err, "failed update salesforce sync record")
	}
	err = c.syncSalesforce()
	if err != nil {
		return err
	}
	err = db.Transaction(func(tx *gorm.DB) error {
		return entity.NewTenantSalesforceSyncRecordQuerySet(tx).TenantIDEq(c.GetTenantID()).GetUpdater().SetSynced(true).Update()
	})
	if err != nil {
		return errors.Annotate(err, "failed update salesforce sync record")
	}
	return nil
}

func (c Context) syncSalesforce() error {
	salesforceConfig := config.Global().SalesforceConfig
	if !salesforceConfig.EnableSalesforce {
		return nil
	}
	tenant, err := c.GetTenantEntity()
	if err != nil {
		return err
	}
	email := tenant.Email
	if err != nil {
		return errors.Annotatef(err, "failed to get user by email %s", email)
	}

	var registrationLog entity.RegistrationLog
	var user entity.User
	err = db.Transaction(func(tx *gorm.DB) error {
		err = entity.NewUserQuerySet(tx).EmailEq(email).One(&user)
		if err != nil {
			return errors.Annotatef(err, "failed to get user by email %s", email)
		}

		err := entity.NewRegistrationLogQuerySet(tx).EmailEq(email).One(&registrationLog)
		if err != nil {
			c.Log().Error("failed to get registration log by email", zap.String("email", email), zap.String("feature", "signup"), zap.Error(err))
			return err
		}
		return nil
	})
	if err != nil {
		return err
	}

	createdAt := tenant.CreatedAt.UTC().Format(salesforce.TimeFormat)
	lead := salesforce.Lead{
		Email:         email,
		Name:          tenant.Name,
		Country:       tenant.Country,
		Plan:          string(tenant.Plan),
		TimeZone:      config.DefaultUtcOffset,
		PortalID:      strconv.FormatUint(tenant.ID.Uint64(), 10),
		FirstName:     user.FirstName,
		LastName:      user.LastName,
		CreatedAt:     createdAt,
		AcceptService: 1,
		AgreementTime: createdAt,
		IPAddress:     registrationLog.IPAddress,
		PhoneNumber:   user.PhoneNumber,
		Job:           user.Job,
		Source:        transNullableStringToEmpty(tenant.ThirdPartyAccountProvider),
	}

	err = c.salesforceClient.SyncLeads(lead)
	if err != nil {
		c.Log().Error("failed to call salesforce SyncLeads API", zap.String("email", email), zap.Reflect("payload", lead), zap.String("feature", "signup"), zap.Error(err))
		return err
	}
	c.Log().Info("successfully call salesforce SyncLeads API", zap.String("email", email), zap.Reflect("payload", lead), zap.String("feature", "signup"))
	return nil
}

func (c Context) EnableOrgOnDemandPlan() error {
	t, err := c.GetTenantEntity()
	if err != nil {
		return err
	}
	if t.Plan == entity.TenantPlanOnDemand {
		return nil
	}

	err = c.bindOnDemandPackage(t)
	if err != nil {
		return err
	}

	_ = c.UpdateSalesForceOnEnableOnDemand()
	return nil
}

func (c Context) EnableOrgOnDemandPlanWithoutSalesforceSync() error {
	t, err := c.GetTenantEntity()
	if err != nil {
		return err
	}
	if t.Plan == entity.TenantPlanOnDemand {
		return nil
	}

	err = c.bindOnDemandPackage(t)
	if err != nil {
		return err
	}

	return nil
}

func (c Context) DisableOrgOnDemandPlan() error {
	// for now the user ondemand could not be disable
	panic("not implemented")
}

func (c Context) IsOnDemandPlanEnabled() (bool, error) {
	t, err := c.GetTenantEntity()
	if err != nil {
		return false, err
	}
	return t.Plan == entity.TenantPlanOnDemand, nil
}

var NoDiscount = 100

func (c Context) GetOnDemandEffectiveDiscount() (int64, error) {
	enable, err := c.IsOnDemandPlanEnabled()
	if err != nil {
		return 0, err
	}

	if !enable {
		return 100, nil
	}

	result, err := c.GetGlobalOnDemandDiscount()
	if err != nil {
		return 0, err
	}

	discount, err := c.GetTenantOnDemandDiscount()
	if err != nil {
		return 0, err
	}
	if discount != nil {
		if *discount < result {
			result = *discount
		}
	}
	return result, nil
}

func (m Model) GetGlobalOnDemandDiscount() (int64, error) {
	onDemandPackageID := config.Global().BillingConfig.BillingPackageIDs.OnDemand
	res, err := billing2.GetHandler().GetPackageDiscount(onDemandPackageID)
	if err != nil {
		return 0, err
	}
	return utils.DecimalToInt(*res), nil
}

func (m Model) UpdateGlobalOnDemandDiscount(globalDiscount int64) error {
	globalDiscountDecimal := utils.IntToDecimal(globalDiscount)
	onDemandPackageID := config.Global().BillingConfig.BillingPackageIDs.OnDemand
	err := billing2.GetHandler().UpdatePackageDiscount(onDemandPackageID, globalDiscountDecimal)
	if err != nil {
		return err
	}
	log.Info("success update global discount", zap.Int64("discount", globalDiscount))
	return nil
}

func (c Context) GetTenantOnDemandDiscount() (*int64, error) {
	t, err := c.GetTenantEntity()
	if err != nil {
		return nil, err
	}
	if t.Plan != entity.TenantPlanOnDemand {
		return nil, errors.NotValidf("can not get tenant discount, tenant on_demand is not enable, tenant is %s", t.Name)
	}

	res, err := billing2.GetHandler().GetBindedPackageDiscount(t.ID.Uint64(), t.OnDemandBindedPackageID)
	if err != nil {
		return nil, err
	}

	if res == nil {
		return nil, nil
	}

	result := utils.DecimalToInt(*res)
	return &result, nil
}

func (c Context) UpdateTenantOnDemandDiscount(discount *int64) error {
	t, err := c.GetTenantEntity()
	if err != nil {
		return err
	}
	if t.Plan != entity.TenantPlanOnDemand {
		return errors.NotValidf("can not update tenant discount, tenant on_demand is not enable, tenant is %s", t.Name)
	}
	var discountDecimal *decimal.Decimal = nil
	if discount != nil {
		tmpDiscount := utils.IntToDecimal(*discount)
		discountDecimal = &tmpDiscount
	}

	err = billing2.GetHandler().UpdateBindedPackageDiscount(t.ID.Uint64(), t.OnDemandBindedPackageID, discountDecimal)
	if err != nil {
		c.Log().Info("failed update tenant discount", zap.String("email", t.Email), zap.Int64p("discount", discount), zap.Error(err))
		return err
	}
	c.Log().Info("success update tenant discount", zap.String("email", t.Email), zap.Int64p("discount", discount))
	return nil
}

func (c Context) ConfirmOnDemandByStripeSetupIntent() error {
	err := c.EnableOrgOnDemandPlan()
	if err != nil {
		return err
	}
	err = billing2.GetHandler().ConfirmStripeSetupIntent(c.GetTenantID().Uint64())
	if err != nil {
		return err
	}
	return nil
}

func (c Context) CreatePayment() error {
	t, err := c.GetTenantEntity()
	if err != nil {
		return err
	}
	var paymentType string
	if t.ThirdPartyAccountCode == nil {
		// for stripe will create payment by stripe setup intent in billing service.
		return nil
	}
	switch *t.ThirdPartyAccountProvider {
	case handler.AWSAccountProvider:
		paymentType = billing2.PaymentType_AWS
	case handler.GCPAccountProvider:
		paymentType = billing2.PaymentType_GCP
	default:
		return errors.Trace(fmt.Errorf("unknown third party account provider %s", *t.ThirdPartyAccountProvider))
	}
	err = billing2.GetHandler().CreatePayment(t.ID.Uint64(), paymentType)
	if err != nil {
		c.Log().Info("failed create payment", zap.String("email", t.Email), zap.Error(err))
		return err
	}
	c.Log().Info("success create payment", zap.String("email", t.Email))
	return nil
}
