package billing

import (
	"context"
	"strconv"
	"time"

	"github.com/pingcap/dbaas/central/config"
	"github.com/pingcap/dbaas/central/db"
	"github.com/pingcap/dbaas/central/db/dbutil"
	"github.com/pingcap/dbaas/central/db/entity"
	"github.com/pingcap/dbaas/central/handler/billing2"
	"github.com/pingcap/errcode"
	"github.com/pingcap/errors"
	"github.com/tidbcloud/pkgv2/gormutil"
	"github.com/tidbcloud/pkgv2/salesforce"
	"go.uber.org/zap"
	"gorm.io/gorm"
)

func (c Context) bindDevTierPackage(t *entity.Tenant) error {
	bindedPackageID, err := billing2.GetHandler().BindPackage(t.ID.Uint64(), config.Global().BillingConfig.BillingPackageIDs.DevTier)
	if err != nil {
		c.Log().Error("failed bind dev tier package", zap.String("email", t.Email), zap.Error(err))
		return err
	}
	c.Log().Info("successfully bind dev tier package", zap.String("email", t.Email))
	t.DevTierBindedPackageID = bindedPackageID

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

func (c Context) GetDevTierEntity() (*entity.Trial, error) {
	tx := c.GetDB()
	return dbutil.LoadTrialByTenantID(context.Background(), tx, c.GetTenantID())
}

// will use dev tier package before enable in front end through dryrun costs to show the price
func (c Context) PrepareDevTier() error {
	t, err := c.GetTenantEntity()
	if err != nil {
		return err
	}
	err = c.bindDevTierPackage(t)

	if err != nil {
		c.Log().Error("failed to bind dev tier package", zap.Error(err))
		return err
	}

	return nil
}

func (c Context) EnableOrgDevTier() error {
	tx := c.GetDB()

	enabled, err := c.IsOrgDevTierEnabled()
	if err != nil {
		return err
	}

	if enabled {
		c.Log().Info("Dev Tier already enabled")
		return nil
	}

	trialCount, err := entity.NewTrialQuerySet(tx).TenantIDEq(c.tenantAndUser.TenantID).TrialTypeEq(entity.TrialTypeDevTier).Count()
	if err != nil {
		c.Log().Error("failed to query trial record", zap.Error(err))
		return errcode.NewInternalErr(errors.New("failed to enable dev tier"))
	}
	if trialCount != 0 {
		c.Log().Error("already enabled dev tier")
		return nil
	}
	now := time.Now()
	estimatedEndedAt := now.Add(config.Duration4DevTierTrialTotal)

	trialRecord := &entity.Trial{
		CreatedByID:      c.tenantAndUser.ID,
		TenantID:         c.tenantAndUser.TenantID,
		StartedAt:        now,
		EstimatedEndedAt: &estimatedEndedAt,
		TrialType:        entity.TrialTypeDevTier,
	}
	err = trialRecord.Create(tx)
	if err != nil {
		if gormutil.IsDuplicatedError(err) {
			c.Log().Info("trial record duplicated")
			return nil
		}
		c.Log().Error("failed to add trial record", zap.Error(err))
		return errcode.NewInternalErr(errors.New("failed to add trial record"))
	}

	t, err := c.GetTenantEntity()
	if err != nil {
		return err
	}

	if t.Plan == entity.TenantPlanNewUser {
		// change to free trial to avoid send new user notify email.
		t.Plan = entity.TenantPlanFreeTrial
		err = t.Update(tx, entity.TenantDBSchema.Plan)
		if err != nil {
			c.Log().Error("failed to update tenant plan to free trial", zap.Error(err))
			return err
		}

		salesforceConfig := config.Global().SalesforceConfig
		if salesforceConfig.EnableSalesforce {
			account := salesforce.Account{
				Plan:     string(entity.TenantPlanFreeTrial),
				PortalID: strconv.FormatUint(t.ID.Uint64(), 10),
			}

			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))
			} else {
				c.Log().Info("successfully call salesforce UpdateAccount API", zap.String("email", t.Email), zap.Reflect("payload", account))
			}
		}
	}
	return nil
}

func (c Context) IsOrgDevTierEnabled() (bool, error) {
	trial, err := c.GetDevTierEntity()
	if err != nil {
		// got trial failed
		if err != gorm.ErrRecordNotFound {
			return false, errors.Trace(err)
		}
		// trial is not found, not enabled
		return false, nil
	}
	if trial.EndedAt != nil {
		return false, nil
	}
	return true, nil
}

func (c Context) CanOrgDevTierEnable() (bool, error) {
	trial, err := c.GetDevTierEntity()
	if err != nil {
		// got trial failed
		if err != gorm.ErrRecordNotFound {
			return false, errors.Trace(err)
		}
		// trial is not found, can be enable
		return true, nil
	}
	if trial.EndedAt != nil {
		return false, nil
	}
	return true, nil
}

func (c Context) GetDevTierStage(now time.Time) (entity.DevTierTrialStage, error) {
	trial, err := c.GetDevTierEntity()
	if err != nil {
		return "", err
	}
	useTime := now.Sub(trial.StartedAt)
	remain := config.Duration4DevTierTrialTotal - useTime
	if remain <= 0 {
		return entity.DevTierTrialStageEnd, nil
	} else if remain <= config.Duration4DevTierCloseToEndStage2 {
		return entity.DevTierTrialStageCloseToEndStage2, nil
	} else if remain <= config.Duration4DevTierCloseToEndStage1 {
		return entity.DevTierTrialStageCloseToEndStage1, nil
	}
	return entity.DevTierTrialStageNormal, nil
}
