package event_handler

import (
	"errors"
	"fmt"
	"github.com/tidbcloud/account/idl/pbgen/proto/account"

	"github.com/pingcap/dbaas/billing2/billing_errors"
	"github.com/pingcap/dbaas/billing2/constant"
	"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/model/billingconfig"
	"github.com/pingcap/dbaas/billing2/model/payment/paymentsub"
	"github.com/pingcap/dbaas/billing2/utils/log"
	"github.com/pingcap/dbaas/billing2/utils/timeutils"
	"go.uber.org/zap"
)

var model billingconfig.BillingConfigModel

type BillingConfigEventHandler struct {
}

func (m *BillingConfigEventHandler) CreateCustomerPaymentByBillingConfig(tenantID, billingConfigID uint) error {
	billingConfig, err := model.GetBillingConfig(billingConfigID)
	if err != nil {
		return err
	}
	var paymentType string
	switch billingConfig.Type {
	case entity.BillingConfigType_MSPCustomer:
		paymentType = entity.PaymentType_MSP
	case entity.BillingConfigType_ResaleCustomer:
		paymentType = entity.PaymentType_Resale
	default:
		return errors.New(fmt.Sprintf("unsupported billing config type %s", billingConfig.Type))
	}
	paymentInfo, err := (&paymentsub.PaymentModel{}).CreatePaymentMethodAndActive(tenantID, &forms.PaymentMethodCreateQuery{
		PaymentType:           paymentType,
		BindedBillingConfigID: &billingConfigID,
	})
	if err != nil {
		log.Error("failed to create payment method on customer activated", zap.String("billingConfigType", billingConfig.Type), zap.Uint("tenantID", tenantID), zap.Uint("billingConfigID", billingConfigID), zap.Error(err))
		return err
	}
	log.Info("success to create payment method on customer activated", zap.String("billingConfigType", billingConfig.Type), zap.Uint("tenantID", tenantID), zap.Uint("billingConfigID", billingConfigID), zap.Uint("paymentID", paymentInfo.PaymentMethodID))
	return nil
}

func (m *BillingConfigEventHandler) CreateOrUpdateBillingConfig(orgEventData *forms.OrgEventData) error {
	log.Info("CreateOrUpdateBillingConfig group  ", zap.Any("orgEventData", orgEventData))
	tenantInfo, err := centralhandler.GetCentralHandler().GetTenant(uint(orgEventData.OrgId))
	if err != nil {
		return err
	}
	log.Info("CreateOrUpdateBillingConfig group tenantInfo ", zap.Any("tenantInfo", tenantInfo))

	switch orgEventData.OrgEventType {
	case constant.TenantEventTypeCreate:
		billingConfig, err := model.EventCreateBillingConfig(orgEventData, tenantInfo)
		if err != nil {
			if errors.Is(err, billing_errors.ObjectAlreadyExists) {
				log.Warn("billing config have been already created", zap.Uint("tenantID", uint(orgEventData.OrgId)), zap.Error(err))
				return nil
			}
			log.Error("CreateOrUpdateBillingConfig EventCreateBillingConfig error", zap.Any("tenantInfo", tenantInfo), zap.Error(err))
			return err
		}
		switch billingConfig.Type {
		case entity.BillingConfigType_MSPCustomer, entity.BillingConfigType_ResaleCustomer:
			err = m.CreateCustomerPaymentByBillingConfig(billingConfig.TenantID, billingConfig.BillingConfigID)
			if err != nil {
				return err
			}
		}
		return nil

	case constant.TenantEventTypeUpdate:
		return nil

	case constant.TenantEventTypeActivate:
		_, err := model.GetActiveBillingConfigID(uint(orgEventData.OrgId))
		if err != nil {
			if errors.Is(err, billing_errors.ObjectNotExistsError) {
				_, err = model.EventCreateBillingConfig(orgEventData, tenantInfo)
				return err
			}
			return err
		}
		return nil

	case constant.TenantEventTypeScheduleActivate:
		// for account test
		billingConfigs, err := model.GetTenantIDScheduledBillingConfig(uint(orgEventData.OrgId))
		if err != nil {
			log.Error("CreateOrUpdateBillingConfig TenantEventTypeScheduleActivate error", zap.Uint64("tenantID", orgEventData.OrgId), zap.Error(err))
			return nil
		} else if len(billingConfigs) > 1 {
			log.Error("too many schedule billing configs", zap.Uint64("tenantID", orgEventData.OrgId), zap.Int("count", len(billingConfigs)))
			return nil
		} else if len(billingConfigs) == 0 {
			log.Info("no billing config need to be activated", zap.Uint64("tenantID", orgEventData.OrgId))
			return nil
		}
		billingConfigs[0].StartActiveTime = timeutils.GetNow()
		err = model.SwitchBillingConfig(billingConfigs[0], true)
		if err != nil {
			return err
		}

		return nil
	case constant.TenantEventTypeSchedule:
		for billingConfigType, partnerType := range map[string]account.PartnerType{
			entity.BillingConfigType_MSPCustomer:    account.PartnerType_PARTNER_TYPE_MSP,
			entity.BillingConfigType_ResaleCustomer: account.PartnerType_PARTNER_TYPE_RESALE,
		} {
			scheduleInfos, err := centralhandler.GetCentralHandler().GetPendingCustomerScheduleInfo(uint(orgEventData.OrgId), partnerType)
			if err != nil || len(scheduleInfos) > 1 {
				log.Error("failed to get a msp customer's schedule info", zap.Any("scheduleInfos", scheduleInfos), zap.Error(err), zap.Uint("tenantID", uint(orgEventData.OrgId)))
				return err
			}
			if len(scheduleInfos) == 0 {
				continue
			}
			scheduleInfo := scheduleInfos[0]
			log.Info("CreateOrUpdateBillingConfig ", zap.Any("scheduleInfo", scheduleInfo), zap.String("billingConfigType", billingConfigType))
			startActiveTime := scheduleInfo.EffectedAt
			billingConfig, err := model.CreateBillingConfigAndScheduled(forms.BillingConfig{
				TenantID:        uint(orgEventData.OrgId),
				StartActiveTime: &startActiveTime,
				EndActiveTime:   nil,
				Type:            billingConfigType,
			})
			if err != nil {
				if errors.Is(err, billing_errors.ObjectAlreadyExists) {
					log.Warn("billing config have been already created", zap.Error(err))
					return nil
				}
				return err
			}
			err = m.CreateCustomerPaymentByBillingConfig(billingConfig.TenantID, billingConfig.BillingConfigID)
			if err != nil {
				return err
			}
			return nil
		}
	case constant.TenantEventTypeScheduleCancel:
		_, err := centralhandler.GetCentralHandler().GetMspCustomerScheduleInfo(uint(orgEventData.OrgId))
		if err != nil {
			if errors.Is(err, billing_errors.ObjectNotExistsError) {
				log.Info("org has no msp customer schedule info, skip it", zap.Uint("tenantID", uint(orgEventData.OrgId)))
				return nil
			}
			log.Error("failed to get a msp customer's schedule info", zap.Error(err), zap.Uint("tenantID", uint(orgEventData.OrgId)))
			return err
		}
		return model.CancelScheduledBillingConfig(uint(orgEventData.OrgId))
	}
	return nil
}

func (m *BillingConfigEventHandler) RegisterEventHandler() error {
	eventHandlerFunc := func(event *eventbushandler.Event) error {
		return m.CreateOrUpdateBillingConfig(event.EventData.(*forms.OrgEventData))
	}
	err := eventbushandler.Get().RegisterEventHandleFunc(constant.AllTenantRegisteredEventType, "billing_config", forms.OrgEventData{}, eventHandlerFunc)
	if err != nil {
		return err
	}

	return nil
}
