package admin

import (
	"fmt"
	"strconv"
	"time"

	"github.com/pingcap/dbaas/pkg/salesforce"
	"github.com/pingcap/errors"
	"github.com/pingcap/log"
	"github.com/tidbcloud/central-sdk/config"
	"github.com/tidbcloud/central-sdk/db"
	"github.com/tidbcloud/central-sdk/db/dbutil"
	"github.com/tidbcloud/central-sdk/db/entity"
	"github.com/tidbcloud/central-sdk/metrics"
	centralModel "github.com/tidbcloud/central-sdk/model"
	"go.uber.org/zap"
	"gorm.io/gorm"
)

func (m UserContext) ListTenants(name string, id uint64, limit, offset int, noLimit bool) ([]*Tenant, int64, error) {
	var (
		tenants   []entity.Tenant
		discounts []entity.GlobalDiscount
		total     int64
		err       error
	)

	err = db.Transaction(func(tx *gorm.DB) error {
		qs := entity.NewTenantQuerySet(tx)
		if name != "" {
			qs = qs.NameLike("%" + name + "%")
		}
		if id > 0 {
			qs = qs.IDEq(entity.ID(id))
		}

		if !noLimit { //no pagination
			if total, err = qs.Count(); err != nil {
				return errors.Annotate(err, "count tenant error")
			}
			qs = qs.Limit(limit).Offset(offset)
		}

		if err := qs.All(&tenants); err != nil {
			return errors.Annotate(err, "fetch tenants failed")
		}
		if noLimit {
			//no pagination
			total = int64(len(tenants))
		}
		if err := entity.NewGlobalDiscountQuerySet(tx).All(&discounts); err != nil {
			return errors.Annotate(err, "fetch global discount failed")
		}
		return nil
	})
	if err != nil {
		return nil, 0, err
	}

	var results []*Tenant
	now := time.Now()
	for _, item := range tenants {
		var tenantDiscount []entity.Discount
		err := db.Transaction(func(tx *gorm.DB) error {
			return entity.NewDiscountQuerySet(tx).TenantIDEq(item.ID).All(&tenantDiscount)
		})
		if err != nil {
			return nil, 0, err
		}
		hasDiscount, discount, description, startTime, EndTime := centralModel.MergeDiscount(now, discounts, tenantDiscount)
		results = append(results, &Tenant{
			ID:                  uint64(item.ID),
			Name:                item.Name,
			Email:               item.Email,
			Country:             item.Country,
			Timezone:            item.TimeZone,
			Status:              string(item.Status),
			Plan:                string(item.Plan),
			BindCreditCardAt:    item.BindCreditCardAt,
			HasDiscount:         hasDiscount,
			Discount:            discount,
			DiscountDescription: description,
			DiscountStartTime:   startTime,
			DiscountEndTime:     EndTime,
		})
	}
	return results, total, nil
}

func (m UserContext) DeleteTenant(tenantID uint64) error {
	return db.Transaction(func(tx *gorm.DB) error {
		return dbutil.UpdateTenantStatus(tx, entity.ID(tenantID), entity.TenantStatusDeleted)
	})
}

func (m UserContext) SalesforceSyncLeads(orgID entity.ID) error {
	salesforceConfig := config.Global().SalesforceConfig
	if salesforceConfig.EnableSalesforce {
		var (
			tenant          entity.Tenant
			user            entity.User
			registrationLog entity.RegistrationLog
			email           string
		)

		err := db.Transaction(func(tx *gorm.DB) error {
			err := entity.NewTenantQuerySet(tx).IDEq(orgID).One(&tenant)
			if err != nil {
				return errors.Annotatef(err, "failed to get tenant by ID %d", orgID)
			}

			email = tenant.Email
			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 {
				return errors.Annotatef(err, "failed to get registration log by email %s", email)
			}
			return nil
		})
		if err != nil {
			return err
		}

		createdAt := tenant.CreatedAt.UTC().Format(salesforce.TimeFortmat)
		lead := salesforce.Lead{
			Email:         email,
			Name:          tenant.Name,
			Country:       tenant.Country,
			Plan:          string(tenant.Plan),
			TimeZone:      tenant.TimeZone,
			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,
		}
		labels := map[string]string{
			metrics.SvcNameLblName:   metrics.SvcNameOfSalesforce,
			metrics.SvcMethodLblName: "SalesforceSyncLeads",
			metrics.SuccessLblName:   "1",
			metrics.TenantIDLblName:  fmt.Sprint(tenant.ID.Uint64()),
		}
		err = m.salesforceClient.SyncLeads(lead)
		if err != nil {
			labels[metrics.SuccessLblName] = "0"
			metrics.IntegrationThroughput.With(labels).Inc()
			log.Error("failed to call salesforce SyncLeads API", zap.String("email", email), zap.Error(err), zap.Reflect("payload", lead))
			return err
		}
		log.Info("successfully call salesforce SyncLeads API", zap.String("email", email), zap.Reflect("payload", lead))
		metrics.IntegrationThroughput.With(labels).Inc()
	}
	return nil
}
