package creditriskcontrol

import (
	"errors"
	"fmt"
	"strings"

	"github.com/pingcap/dbaas/billing2/billing_errors"
	"github.com/pingcap/dbaas/billing2/handler/centralhandler"
	"github.com/pingcap/dbaas/billing2/handler/infrahandler"
	"github.com/pingcap/dbaas/billing2/model/marketing"
	"github.com/pingcap/dbaas/billing2/model/payment/paymentsub"
	"github.com/pingcap/dbaas/billing2/model/payment/spend_limit"
	"github.com/pingcap/dbaas/billing2/utils/log"
	"github.com/tidbcloud/account/idl/pbgen/proto/account"
	"go.uber.org/zap"
)

func init() {
	RegisterCloseAction("close_tenant", CloseTenantAction)
	RegisterCloseAction("send_tenant_credit_close_email", SendTenantCreditCloseEmail)
	RegisterCloseAction("cleanup_tenant_dedicate_clusters", CleanupTenantDedicateClusters)
	RegisterCloseAction("cleanup_tenant_serverless_clusters", CleanupTenantDevTierClusters)
	RegisterCloseAction("cleanup_backups", CleanupTenantBackups)
	RegisterCloseAction("cleanup_tenant_support_plan", CleanupTenantSupportPlan)
	RegisterCloseAction("throttle_serverless_scalable_clusters", ThrottleServerlessCluster)
	RegisterCloseAction("throttle_essential_v1_clusters", ThrottleEssentialV1Clusters)
	RegisterCloseAction("recover_essential_v1_clusters", RecoverEssentialV1Clusters)
	RegisterCloseAction("send_suspend_email_for_credit_card_unpayable", SendSuspendEmailForCreditCardUnpayable)
	RegisterCloseAction("send_suspend_email_for_marketplace_unsubscribed", SendSuspendEmailForMarketplaceUnsubscribed)
	RegisterCloseAction("send_closed_email_for_credit_card_unpayable", SendClosedEmailForCreditCardUnpayable)
	RegisterCloseAction("send_closed_email_for_marketplace_unsubscribed", SendClosedEmailForMarketplaceUnsubscribed)

	RegisterCloseAction("cancel_closing_org", CancelClosingTenant)
}

func CloseTenantAction(tenantID uint, params *ActionParams) error {
	return centralhandler.GetCentralHandler().DeleteTenant(tenantID)
}

const EmailOnceKey_CreditRiskCloseStartNotify = "credit_risk_close_start_notify"
const TemplateID_CreditRiskCloseStartNotify = "d-1c65606947b249ca860e708b6360111c"
const TemplateID_OrgSuspendNotification = "d-3b68c82c9bd44475ae2d84594303a70e"
const TemplateID_OrgClosedNotification = "d-00f926ec5735446086eb4fec4c840c4e"
const TimeFmtDTM = "2006-01-02 15:04:05"

// CancelClosingTenant reverts the throttling applied while the tenant close
// was running: it recovers the starter spend-limit rows and then publishes
// recovered events for essential v1 clusters. Both steps are idempotent, so a
// retried cancel (the action failed half-way) is harmless.
func CancelClosingTenant(tenantID uint, params *ActionParams) error {
	if err := RecoverThrottledServerlessCluster(tenantID, params); err != nil {
		return err
	}
	return RecoverEssentialV1Clusters(tenantID, params)
}

func SendTenantCreditCloseEmail(tenantID uint, params *ActionParams) error {
	return centralhandler.GetCentralHandler().SendTenantAdminEmailOnce(tenantID, EmailOnceKey_CreditRiskCloseStartNotify, TemplateID_CreditRiskCloseStartNotify, map[string]string{
		"lastdate": params.CreditRiskClosedDate.Format(TimeFmtDTM),
	})
}

func sendOrgSuspendEmail(tenantID uint, reasonDetails string, params *ActionParams) error {
	lastDate := params.CreditRiskClosedDate.Format(TimeFmtDTM)
	templateID := TemplateID_OrgSuspendNotification
	key := fmt.Sprintf("%s-%d-%s", templateID, tenantID, lastDate)
	return centralhandler.GetCentralHandler().SendTenantEmailWithLabelOnce(
		tenantID, key, templateID,
		map[string]string{
			"lastdate":      params.CreditRiskClosedDate.Format(TimeFmtDTM),
			"ReasonDetails": reasonDetails,
		},
		account.MailLabels_MAIL_LABELS_ORG_OWNER,
		[]account.MailLabels{account.MailLabels_MAIL_LABELS_ORG_BILLING_ADMINS},
	)
}

func sendOrgClosedEmail(tenantID uint, reasonDetails string, params *ActionParams) error {
	lastDate := params.CreditRiskClosedDate.Format(TimeFmtDTM)
	templateID := TemplateID_OrgClosedNotification
	key := fmt.Sprintf("%s-%d-%s", templateID, tenantID, lastDate)
	return centralhandler.GetCentralHandler().SendTenantEmailWithLabelOnce(
		tenantID, key, templateID,
		map[string]string{
			"lastdate":      params.CreditRiskClosedDate.Format(TimeFmtDTM),
			"ReasonDetails": reasonDetails,
		},
		account.MailLabels_MAIL_LABELS_ORG_OWNER,
		[]account.MailLabels{account.MailLabels_MAIL_LABELS_ORG_BILLING_ADMINS},
	)
}

func SendSuspendEmailForCreditCardUnpayable(tenantID uint, params *ActionParams) error {
	return sendOrgSuspendEmail(tenantID, "This is due to unpaid invoice(s) of your organization. You can pay the outstanding invoice(s) to resume your organization.", params)
}

func SendSuspendEmailForMarketplaceUnsubscribed(tenantID uint, params *ActionParams) error {
	org, err := centralhandler.GetCentralHandler().GetTenant(tenantID)
	if err != nil {
		return err
	}
	thirdParty := strings.ToUpper(paymentsub.AccountProvider2PaymentType(org.ThirdPartyAccountProvider))
	if thirdParty == "" {
		err = errors.New("invalidated third party")
		log.Error(err.Error(), zap.Uint("tenantID", tenantID), zap.String("accountProvider", org.ThirdPartyAccountProvider))
		return err
	}
	reason := fmt.Sprintf("This is due to you have unsubscribed TiDB Cloud from %s Marketplace. You can re-subscribe TiDB Cloud from %s Marketplace to resume your organization. ", thirdParty, thirdParty)
	return sendOrgSuspendEmail(tenantID, reason, params)
}

func SendClosedEmailForCreditCardUnpayable(tenantID uint, params *ActionParams) error {
	return sendOrgClosedEmail(tenantID, "This is due to unpaid invoice(s) of your organization.", params)
}

func SendClosedEmailForMarketplaceUnsubscribed(tenantID uint, params *ActionParams) error {
	org, err := centralhandler.GetCentralHandler().GetTenant(tenantID)
	if err != nil {
		return err
	}
	thirdParty := strings.ToUpper(paymentsub.AccountProvider2PaymentType(org.ThirdPartyAccountProvider))
	if thirdParty == "" {
		err = errors.New("invalidated third party")
		log.Error(err.Error(), zap.Uint("tenantID", tenantID), zap.String("accountProvider", org.ThirdPartyAccountProvider))
		return err
	}
	reason := fmt.Sprintf("This is due to you have unsubscribed TiDB Cloud from %s Marketplace", thirdParty)
	return sendOrgClosedEmail(tenantID, reason, params)
}

func ThrottleServerlessCluster(tenantID uint, params *ActionParams) error {
	log.Info("start to throttle serverless cluster")
	return (&spend_limit.SpendLimitModel{}).ClosePaidServerlessClustersSpendingLimits(tenantID)
}

func RecoverThrottledServerlessCluster(tenantID uint, params *ActionParams) error {
	log.Info("start to throttle serverless cluster")
	return (&spend_limit.SpendLimitModel{}).RecoverClosingSpendingLimits(tenantID)
}

func ThrottleEssentialV1Clusters(tenantID uint, params *ActionParams) error {
	log.Info("start to throttle essential v1 clusters", zap.Uint("tenantID", tenantID))
	return (&spend_limit.SpendLimitModel{}).CloseEssentialV1Clusters(tenantID)
}

func RecoverEssentialV1Clusters(tenantID uint, params *ActionParams) error {
	log.Info("start to recover essential v1 clusters", zap.Uint("tenantID", tenantID))
	return (&spend_limit.SpendLimitModel{}).RecoverEssentialV1Clusters(tenantID)
}

func CleanupTenantDedicateClusters(tenantID uint, params *ActionParams) error {
	log.Info("cleanup tenant dedicate clusters", zap.Uint("tenantID", tenantID))
	return infrahandler.Get().CleanupTenantDedicateClusters(tenantID)
}

func CleanupTenantDevTierClusters(tenantID uint, params *ActionParams) error {
	log.Info("cleanup tenant dev tier clusters", zap.Uint("tenantID", tenantID))
	return infrahandler.Get().CleanupTenantDevTierClusters(tenantID)
}

func CleanupTenantBackups(tenantID uint, params *ActionParams) error {
	log.Info("cleanup tenant backups", zap.Uint("tenantID", tenantID))
	return infrahandler.Get().CleanupTenantBackups(tenantID)
}

func CleanupTenantSupportPlan(tenantID uint, params *ActionParams) error {
	log.Info("cleanup tenant support plan", zap.Uint("tenantID", tenantID))
	supportPlanModel := marketing.SupportPlanModel{}
	supportPlan, err := supportPlanModel.GetTenantActiveBindedPlan(tenantID)
	if err != nil {
		if errors.Is(err, billing_errors.ObjectNotExistsError) {
			return nil
		}
		return err
	}
	_, err = supportPlanModel.CancelBindedSupportPlan(supportPlan.BindedSupportPlanID)
	return err
}
