package jobs

import (
	"context"
	"encoding/json"
	"fmt"
	"strconv"

	"github.com/tidbcloud/account/internel/svc/org"

	"github.com/tidbcloud/account/commons/database"
	"go.opentelemetry.io/otel"

	"github.com/pingcap/errors"
	"github.com/tidbcloud/account/commons/config"
	"github.com/tidbcloud/account/internel/dao/entity"
	"github.com/tidbcloud/account/thirdparty/salesforce"
	"github.com/tidbcloud/tiinf/monitor/tilog"
	"go.uber.org/zap"
	"gorm.io/gorm"
	"gorm.io/gorm/clause"
)

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

// ConnectionType ... Auth0 connection type of registration
type ConnectionType string

// ConnectionType constants are for compatibility with Hotspot integration.
const (
	ConnectionTypeEmail     = "email"
	ConnectionTypeGoogle    = "google"
	ConnectionTypeGithub    = "github"
	ConnectionTypeOkta      = "okta"
	ConnectionTypeMicrosoft = "microsoft"
)

func transConnectionType(auth0ConnectionType config.Auth0ConnectionType) (string, error) {
	switch auth0ConnectionType {
	case config.Auth0DatabaseConnection:
		return ConnectionTypeEmail, nil
	case config.Auth0SocialConnectionGoogle:
		return ConnectionTypeGoogle, nil
	case config.Auth0SocialConnectionGithub:
		return ConnectionTypeGithub, nil
	case config.Auth0SocialConnectionMicrosoft:
		return ConnectionTypeMicrosoft, nil
	}

	if auth0ConnectionType.IsOKTAConnection() {
		return ConnectionTypeOkta, nil
	}
	tilog.PlainInfo("unknown auth0 connection type", zap.Any("auth0ConnectionType", auth0ConnectionType))
	return "", errors.Errorf("unknown auth0 connection type %s", auth0ConnectionType)
}

func syncSalesforce(ctx context.Context, tenantID entity.ID) error {
	salesforceConfig := config.Global().SalesforceConfig
	if !salesforceConfig.EnableSalesforce {
		return nil
	}

	var (
		registrationLog    entity.RegistrationLog
		user               entity.User
		tenant             entity.Tenant
		leadFirstName      string
		leadLastName       string
		leadConnectionType string
		err                error
	)
	err = database.Transaction(func(tx *gorm.DB) error {
		tenant, err = org.GetTenantByID(tx, tenantID)
		if err != nil {
			tilog.Error(ctx, "GetTenantByID err", zap.Any("org", tenantID), zap.Error(err))
			return err
		}
		email := tenant.Email
		//  msp org‘s  email maybe is not the cloud user
		if tenant.OrgTag == string(entity.OrgTagMsp) {
			leadFirstName = "MSP"
			leadLastName = "MSP"
			return nil
		}

		err = entity.NewUserQuerySet(tx).EmailEq(email).One(&user)
		if err != nil {
			if err == gorm.ErrRecordNotFound {
				tilog.Info(ctx, "user not exist ignore syncSalesforce", zap.Any("email", email), zap.Any("org", tenant))
				return nil
			}
			tilog.Info(ctx, "failed to get user by email:"+email)
			return errors.Annotatef(err, "failed to get user by email %s", email)
		}

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

		leadFirstName = user.FirstName
		leadLastName = user.LastName
		leadConnectionType, err = transConnectionType(config.Auth0ConnectionType(registrationLog.Source))
		if err != nil {
			return err
		}

		return nil
	})
	if err != nil {
		return err
	}

	createdAt := tenant.CreatedAt.UTC().Format(salesforce.TimeFormat)
	email := tenant.Email

	lead := salesforce.Lead{
		Email:                 email,
		Name:                  tenant.Name,
		Country:               tenant.Country,
		TimeZone:              config.DefaultUtcOffset,
		PortalID:              strconv.FormatUint(tenant.ID.Uint64(), 10),
		FirstName:             leadFirstName,
		LastName:              leadLastName,
		CreatedAt:             createdAt,
		AcceptService:         1,
		AgreementTime:         createdAt,
		IPAddress:             registrationLog.IPAddress,
		PhoneNumber:           user.PhoneNumber,
		Job:                   user.Job,
		Source:                transNullableStringToEmpty(tenant.ThirdPartyAccountProvider),
		ConnectionType:        leadConnectionType,
		WebsiteReferrerURL:    registrationLog.WebsiteReferrerURL,
		UtmCampaign:           registrationLog.UtmCampaign,
		UtmContent:            registrationLog.UtmContent,
		UtmMedium:             registrationLog.UtmMedium,
		UtmSource:             registrationLog.UtmSource,
		UtmTerm:               registrationLog.UtmTerm,
		MostRecentReferralUrl: registrationLog.MostRecentReferralUrl,
	}

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

// SyncSalesforce
//
//	@Description:SyncSalesforce， push data to Salesforce once an hour
//	@param ctx
//	@param payload
//	@return error
func SyncSalesforce(ctx context.Context, payload []byte) error {
	tilog.Info(ctx, "SyncSalesforce job ", zap.Any("payload", payload))
	ctx, span := otel.Tracer(config.TraceComponentNameWorker).Start(context.Background(), "SyncSalesforceJob")
	defer span.End()
	var req config.SyncSalesforceSchema
	if json.Unmarshal(payload, &req) != nil {
		return errors.New("invalid payload")
	}
	tilog.Info(ctx, "SyncSalesforce job start org_id="+fmt.Sprintf("%d", req.TenantID))

	tenantID := entity.ID(req.TenantID)

	salesforceConfig := config.Global().SalesforceConfig
	if !salesforceConfig.EnableSalesforce {
		tilog.Info(ctx, "SyncSalesforce is disabled", zap.String("feature", "signup"), zap.Uint64("tenantID", tenantID.Uint64()))
		return nil
	}
	err := database.Transaction(func(tx *gorm.DB) error {
		return tx.Clauses(clause.OnConflict{DoNothing: true}).Create(&entity.TenantSalesforceSyncRecord{
			TenantID: tenantID,
			Synced:   false,
		}).Error
	})
	if err != nil {
		return errors.Annotate(err, "failed update salesforce sync record")
	}
	tilog.Info(ctx, "SyncSalesforce job : save sync record to table tenant_salesforce_sync_records, org_id="+fmt.Sprintf("%d", req.TenantID))
	err = database.Transaction(func(tx *gorm.DB) error {
		var record entity.TenantSalesforceSyncRecord

		err := tx.Clauses(clause.Locking{Strength: "UPDATE"}).Model(&entity.TenantSalesforceSyncRecord{}).Where("tenant_id=?", tenantID).First(&record).Error
		if err != nil {
			return err
		}

		if record.Synced {
			return nil
		}

		err = syncSalesforce(ctx, tenantID)
		if err != nil {
			tilog.Info(ctx, "failed to sync salesforce", zap.String("feature", "signup"), zap.Uint64("tenantID", tenantID.Uint64()), zap.String("err", err.Error()))
			// dev,staging not retry
			if config.IsProdEnv() {
				// remote retry
				return err
			}
		}
		return tx.Model(&record).Update("synced", true).Error

	})
	if err != nil {
		return errors.Annotate(err, "failed update salesforce sync record")
	}
	return nil
}

func SaveSalesforceJobData(ctx context.Context, orgId uint64) {
	salesforceData := config.SyncSalesforceSchema{TenantID: orgId}
	data, err := json.Marshal(salesforceData)
	if err != nil {
		tilog.Error(ctx, "failed to marshal salesforce data", zap.Any("org", orgId), zap.Error(err))
		return
	}
	// NOTE: we call this function directly temporarily, because self-hosted PoC apply does not ready yet.
	if err = SyncSalesforce(ctx, data); err != nil {
		tilog.Error(ctx, "failed to sync salesforce", zap.Any("orgId", orgId), zap.Error(err))
		return
	}
	tilog.Info(ctx, "save sync salesforce data succcess", zap.Any("orgId", orgId))

}
