package plan

import (
	"errors"
	"fmt"

	"github.com/pingcap/dbaas/billing2/model/billingconfig"

	"github.com/pingcap/dbaas/billing2/billing_errors"
	"github.com/pingcap/dbaas/billing2/config"
	"github.com/pingcap/dbaas/billing2/db"
	"github.com/pingcap/dbaas/billing2/db/entity"
	"github.com/pingcap/dbaas/billing2/forms"
	"github.com/pingcap/dbaas/billing2/handler/centralhandler"
	"github.com/pingcap/dbaas/billing2/handler/eventbushandler"
	"github.com/pingcap/dbaas/billing2/handler/tidbmgmtsvchandler"
	"github.com/pingcap/dbaas/billing2/model/billing"
	"github.com/pingcap/dbaas/billing2/utils/labels"
	"github.com/pingcap/dbaas/billing2/utils/log"
	"github.com/pingcap/dbaas/billing2/utils/uncutils"
	"go.uber.org/zap"
)

const DevTierClusterCreatedEventType = "dev-tier-cluster-created"
const ClusterEventType = "ClusterEvent"
const ClusterEventType_ClusterEventTypeCreating = "ClusterCreating"
const ClusterEventType_ClusterEventTypeCreated = "ClusterCreated"
const ClusterEventType_ClusterEventTypeDeleted = "ClusterDeleted"

type ClusterEventData struct {
	TenantID         uint   `json:"tenant_id,string"`
	IsDevTier        bool   `json:"is_dev_tier"`
	ClusterEventType string `json:"event_type"`
	ClusterID        uint   `json:"cluster_id,string"`
	ProjectID        uint   `json:"project_id,string"`
	OperatorID       uint   `json:"operator_id,string"`
	UserEmail        string `json:"user_email"`
	Status           string `json:"status"`
	ServicePlan      string `json:"service_plan,omitempty"`
	ClusterType      string `json:"cluster_type,omitempty"`
}

var (
	ServicePlan_Starter   = "Starter"
	ServicePlan_Essential = "Essential"
	ServicePlan_Premium   = "Premium"
)

const (
	ClusterTypeTidbxInstance     = "tidbx_instance"
	ClusterTypeDedicatedCluster  = "dedicated_cluster"
	ClusterTypeTidbxResourcePool = "tidbx_resource_pool"
)

// will use dev tier package before enable in front end through dryrun costs to show the price
func (m *TenantPlanModel) PrepareDevTier(tenantID uint) error {
	planInfo, err := m.GetTenantDevTierPlan(tenantID)
	if err != nil {
		return err
	}

	if planInfo.BindedPackageID != 0 {
		log.Info("already binded dev tier package", zap.Uint("tenantID", tenantID))
		return nil
	}
	billingConfigID, err := m.getTenantBillingConfigID(tenantID)
	if err != nil {
		log.Error("PrepareDevTier tenant get billing config  error", zap.Uint("tenantID", tenantID), zap.Error(err))
		return err
	}
	tenantPlanEntity := &entity.TenantPlan{
		TenantID:              tenantID,
		Plan:                  entity.TenantPlan_DevTier,
		Status:                entity.TenantPlanStatus_NotStarted,
		BindedPackageID:       0,
		BindedBillingConfigID: billingConfigID,
	}

	if planInfo.PlanID == 0 {
		// not have created tenant plan entity yet
		err = tenantPlanEntity.Create(db.GetDB())
		if err != nil {
			log.Error("failed create tenant plan entity dev tier", zap.Uint("tenantID", tenantID), zap.Error(err))
			return err
		}
	}
	log.Info("successfully create tenant plan entity dev tier", zap.Uint("tenantID", tenantID))

	bindedPackageInfo, err := (&billing.BindedPackageModel{}).BindPackageInternal(tenantID, config.Get().GetUint(config.DevTierPackage))
	if err != nil {
		log.Error("failed bind dev tier package", zap.Uint("tenantID", tenantID), zap.Error(err))
		return err
	}
	log.Info("successfully bind dev tier package", zap.Uint("tenantID", tenantID))

	err = entity.NewTenantPlanQuerySet(db.GetDB()).TenantIDEq(tenantID).PlanEq(entity.TenantPlan_DevTier).GetUpdater().SetBindedPackageID(bindedPackageInfo.BindedPackageID).Update()
	if err != nil {
		log.Error("failed update tenant plan entity dev tier", zap.Uint("tenantID", tenantID), zap.Error(err))
		return err
	}
	log.Info("successfully update tenant plan entity dev tier", zap.Uint("tenantID", tenantID))
	return nil
}

func (m *TenantPlanModel) GetTenantDevTierPlan(tenantID uint) (*forms.PlanInfo, error) {
	return m.getTenantPlanInfo(tenantID, 0, entity.TenantPlan_DevTier)
}

func (m *TenantPlanModel) EnableTenantDevTierPlan(tenantID uint) error {
	planInfo, err := m.GetTenantDevTierPlan(tenantID)
	model := &billingconfig.BillingConfigModel{}
	billingConfigID, err := model.GetActiveBillingConfigID(tenantID)
	if err != nil {
		return billing_errors.NewInvalidInputError(err)
	}
	if err != nil {
		log.Info("err err ", zap.Error(err))

		return err
	}

	if planInfo.Status == entity.TenantPlanStatus_Active {
		log.Info("tenant dev tier plan already enabled", zap.Uint("tenantID", tenantID))
		return nil

	}
	if planInfo.Status == entity.TenantPlanStatus_Closed {
		log.Error("can not open tenant dev tier plan after closed", zap.Uint("tenantID", tenantID))
		return billing_errors.NewInvalidInputError(errors.New("can not open tenant dev tier plan after closed"))
	}

	if planInfo.BindedPackageID == 0 {
		err := m.PrepareDevTier(tenantID)
		if err != nil {
			log.Error("can not open tenant dev tier plan include prepare dev tier", zap.Uint("tenantID", tenantID), zap.Error(err))
			return err
		}
	}

	err = m.activeTenantPlan(tenantID, billingConfigID, entity.TenantPlan_DevTier)
	if err != nil {
		log.Error("failed to active tenant dev tier plan", zap.Uint("tenantID", tenantID), zap.Error(err))
		return err
	}

	return nil
}

func (m *TenantPlanModel) RegisterDevTierEventHandler() error {
	eventHandlerFunc := func(event *eventbushandler.Event) error {
		return m.EnableTenantDevTierPlan(event.EventData.(*forms.TenantID).TenantID)
	}
	err := eventbushandler.Get().RegisterEventHandleFunc(DevTierClusterCreatedEventType, "dev_tier_plan", forms.TenantID{}, eventHandlerFunc)
	if err != nil {
		return err
	}

	eventHandlerFunc = func(event *eventbushandler.Event) error {
		data := event.EventData.(*ClusterEventData)

		if data.ClusterType == ClusterTypeTidbxResourcePool {
			log.Info("ignore tidbx resource pool cluster event", zap.Uint("tenantID", data.TenantID))
			return nil
		}
		// send in-app notification
		m.sendInAppNotificationAboutClusterIfNeed(data)
		if data.ClusterEventType != ClusterEventType_ClusterEventTypeCreating {
			log.Info("not cluster creating event, just skipped this event", zap.Uint("tenantID", data.TenantID))
			return nil
		}
		if !data.IsDevTier {
			log.Info("not a dev tier cluster, just skipped this event", zap.Uint("tenantID", data.TenantID))
			return nil
		}
		log.Info("recieved dev tier cluster created, start enable tenant plan dev tier", zap.Uint("tenantID", data.TenantID))
		return m.EnableTenantDevTierPlan(data.TenantID)
	}
	err = eventbushandler.Get().RegisterEventHandleFunc(ClusterEventType, "dev_tier_plan", ClusterEventData{}, eventHandlerFunc)
	if err != nil {
		return err
	}

	eventHandlerFunc = func(event *eventbushandler.Event) error {
		return m.PrepareDevTier(event.EventData.(*forms.TenantID).TenantID)
	}
	err = eventbushandler.Get().RegisterEventHandleFunc(TenantRegisteredEventType, "dev_tier_plan", forms.TenantID{}, eventHandlerFunc)
	if err != nil {
		return err
	}
	return nil
}

func (m *TenantPlanModel) sendInAppNotificationAboutClusterIfNeed(data *ClusterEventData) {
	if data.ClusterEventType != ClusterEventType_ClusterEventTypeCreated && data.ClusterEventType != ClusterEventType_ClusterEventTypeDeleted {
		log.Info("no need to send in-app notification for cluster create/delete, it's not create or delete event", zap.Any("data", data))
		return
	}
	notificationEvent := ""
	payload := map[string]string{
		"clusterId": fmt.Sprintf("%d", data.ClusterID),
		"projectId": fmt.Sprintf("%d", data.ProjectID),
		"creator":   data.UserEmail,
	}
	if data.ClusterEventType == ClusterEventType_ClusterEventTypeCreated {
		switch data.ServicePlan {
		case ServicePlan_Premium: // special case
			notificationEvent = uncutils.PremiumClusterCreated
		case ServicePlan_Essential:
			notificationEvent = uncutils.EssentialClusterCreated
		case ServicePlan_Starter:
			notificationEvent = uncutils.StarterClusterCreated
		default:
			if data.IsDevTier {
				notificationEvent = uncutils.ServerlessClusterCreated
			} else {
				notificationEvent = uncutils.DedicatedClusterCreated
			}
		}
	}
	if data.ClusterEventType == ClusterEventType_ClusterEventTypeDeleted {
		switch data.ServicePlan {
		case ServicePlan_Premium:
			notificationEvent = uncutils.PremiumClusterDeleted
		case ServicePlan_Essential:
			notificationEvent = uncutils.EssentialClusterDeleted
		case ServicePlan_Starter:
			notificationEvent = uncutils.StarterClusterDeleted
		default:
			if data.IsDevTier {
				notificationEvent = uncutils.ServerlessClusterDeleted
			} else {
				notificationEvent = uncutils.DedicatedClusterDeleted
			}
		}
	}
	if data.ProjectID != 0 {
		// get project name if exist
		projectName, err := labels.GetProjectName(data.ProjectID)
		if err != nil {
			log.Error("fail to send in-app notification for cluster create/delete, get project name error", zap.Any("data", data), zap.Error(err))
			return
		}
		payload["projectName"] = projectName
	} else {
		// get org name instead
		tenantInfo, err := centralhandler.GetCentralHandler().GetTenant(data.TenantID)
		if err != nil {
			log.Error("fail to send in-app notification for cluster create/delete, get org name error", zap.Any("data", data), zap.Error(err))
			return
		}
		payload["orgName"] = tenantInfo.Name
	}
	mgmtClient, err := tidbmgmtsvchandler.Get()
	if err != nil {
		log.Error("fail to send in-app notification for cluster create/delete, get mgmt client error", zap.Any("data", data), zap.Error(err))
		return
	}
	cluster, err := mgmtClient.GetClusterByID(uint64(data.TenantID), uint64(data.ClusterID))
	if err != nil {
		log.Error("fail to send in-app notification for cluster create/delete, get cluster info error", zap.Any("data", data), zap.Error(err))
		return
	}
	payload["clusterName"] = cluster.Name
	log.Info("start to send in-app notification for cluster create/delete", zap.String("notificationEvent", notificationEvent), zap.Any("payload", payload))
	uncutils.SendInAppNotification(data.TenantID, notificationEvent, payload)
}
