package msp

import (
	"context"
	"fmt"
	"strconv"
	"time"

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

	db "github.com/tidbcloud/account/commons/database"

	"github.com/tidbcloud/account/thirdparty/jsm"

	"github.com/tidbcloud/account/idl/pbgen/proto/account"
	accountpb "github.com/tidbcloud/account/idl/pbgen/proto/account"

	"github.com/tidbcloud/tiinf/errs"

	"github.com/duke-git/lancet/v2/slice"

	"gorm.io/gorm"

	"github.com/tidbcloud/account/central/utils"

	"github.com/tidbcloud/account/internel/dao/msp"
	"github.com/tidbcloud/account/internel/dao/org"

	orgDao "github.com/tidbcloud/account/internel/dao/org"

	"go.uber.org/zap"

	"github.com/tidbcloud/tiinf/monitor/tilog"

	"github.com/tidbcloud/account/internel/dao/entity"
)

var (
	PartnerTypes    = []string{string(entity.PartnerTypeMsp), string(entity.PartnerTypeCppo), string(entity.PartnerTypeMCPO), string(entity.PartnerTypeResale), string(entity.PartnerTypeAlicloud)}
	PartnerOrgTags  = []string{string(entity.OrgTagAwsCppo), string(entity.OrgTagMsp), string(entity.OrgTagGcpMCPOReseller), string(entity.OrgTagResalePartner), string(entity.OrgTagAlicloud)}
	CustomerOrgTags = []string{string(entity.OrgTagAwsCppoCustomer), string(entity.OrgTagMspCustomer), string(entity.OrgTagGcpMCPOCustomer), string(entity.OrgTagResaleCustomer), string(entity.OrgTagAlicloudCustomer)}
)

/*
return  map[PartnerCustomer] CustomerOrg
*/
func (s *MspSvc) GetCustomerByMspID(ctx context.Context, mspOrgID entity.ID) (map[entity.PartnerCustomer]entity.Tenant, error) {

	resultMap := make(map[entity.PartnerCustomer]entity.Tenant)

	tilog.Info(ctx, "MspSvc GetMspCustomerByMspID ", zap.Any("orgID", mspOrgID))
	filter := msp.MspCustomerFilter{PartnerOrgIDs: []entity.ID{mspOrgID}}
	customers, err := s.hub.Msp.ListCustomers(ctx, s.db.WithContext(ctx), filter)
	if err != nil {
		return resultMap, err
	}
	var customerIds []uint64
	for _, m := range customers {
		customerIds = append(customerIds, m.CustomerOrgID.Uint64())
	}
	if len(customerIds) == 0 {
		return resultMap, nil
	}
	tilog.Info(ctx, "MspSvc GetMspCustomerByMspID ", zap.Any("customerIds", customerIds))
	orgFilter := org.ListOrgsFilter{ID: customerIds}

	customerOrgs, err := s.hub.Org.ListOrgsByFilter(ctx, s.db.WithContext(ctx), orgFilter)
	if err != nil {
		return resultMap, err
	}

	for _, customer := range customers {
		isEven := func(i int, tenant entity.Tenant) bool {
			return customer.CustomerOrgID == tenant.ID
		}
		// find customerOrg  from customerOrgs,  who  customer.CustomerOrgID == tenant.ID
		customerOrg, ok := slice.Find(customerOrgs, isEven)
		if ok {
			resultMap[customer] = *customerOrg

		}

	}
	return resultMap, err
}

func (s *MspSvc) ListCustomers(ctx context.Context, req *account.ListPartnerCustomersReq) ([]entity.PartnerCustomer, error) {
	tilog.Info(ctx, "MspSvc ListCustomers ", zap.Any("ListPartnerCustomersReq", req))

	var status []entity.EntityStatus

	for _, s := range req.Status {
		status = append(status, utils.GetEntityStatus(s))
	}
	var partnerTypes []entity.PartnerType
	switch req.PartnerType {
	case accountpb.PartnerType_PARTNER_TYPE_MSP:
		partnerTypes = []entity.PartnerType{entity.PartnerTypeMsp}
	case accountpb.PartnerType_PARTNER_TYPE_CPPO:
		partnerTypes = []entity.PartnerType{entity.PartnerTypeCppo}
	case accountpb.PartnerType_PARTNER_TYPE_MCPO:
		partnerTypes = []entity.PartnerType{entity.PartnerTypeMCPO}
	case accountpb.PartnerType_PARTNER_TYPE_RESALE:
		partnerTypes = []entity.PartnerType{entity.PartnerTypeResale}
	default:
		partnerTypes = []entity.PartnerType{}
	}
	filter := msp.MspCustomerFilter{
		PartnerOrgIDs:  utils.UintToIDList(req.PartnerIds),
		CustomerOrgIDs: utils.UintToIDList(req.PartnerCustomerIds),
		Status:         status,
		PartnerTypes:   partnerTypes,
	}
	return s.hub.Msp.ListCustomers(ctx, s.db.WithContext(ctx), filter)

}

func (s *MspSvc) GetCustomerByCustomerID(ctx context.Context, orgID entity.ID) (*entity.PartnerCustomer, error) {
	tilog.Info(ctx, "MspSvc GetMspCustomerByMspOrgID ", zap.Any("orgID", orgID))
	return s.hub.Msp.GetCustomerByCustomerID(ctx, s.db.WithContext(ctx), orgID)
}

func (s *MspSvc) CreateCustomer(ctx context.Context, customerReq *entity.PartnerCustomer, isCheckParam bool) error {
	tilog.Info(ctx, "MspSvc CreateCustomer ", zap.Any("PartnerCustomer", customerReq), zap.Any("isCheckParam", isCheckParam))
	if isCheckParam {
		msg := ""
		//if config.IsProdEnv() && customerReq.EffectedAt.Before(time.Now()) {
		//	msg = "the effective time must be after the current time"
		//}

		//tilog.Info(ctx, "MspSvc CreateMspCustomer CheckParam")
		if customerReq.PartnerOrgID == 0 || customerReq.CustomerOrgID == 0 {
			msg = "partner_org_id and customer_org_id must not null"

		}
		if customerReq.PartnerOrgID == customerReq.CustomerOrgID {
			msg = fmt.Sprintf("it is not allowed to put %d as both partner and customer", customerReq.PartnerOrgID)

		}
		if utils.GetPartnerType(customerReq.PartnerType) == "" {
			msg = "partner_type is invalid"
		}
		if msg != "" {
			tilog.Info(ctx, msg)
			return errs.NewBadRequest(msg, nil)
		}
		// check msp Org
		partnerOrg, err := s.hub.Org.GetOrgByOrgID(ctx, s.db.WithContext(ctx), customerReq.PartnerOrgID.Uint64())
		if err != nil && err != gorm.ErrRecordNotFound {
			return err
		}
		if partnerOrg == nil {
			errMsg := fmt.Sprintf(" partner_org_id(%d)  is invalid", customerReq.PartnerOrgID)
			return errs.NewBadRequest(errMsg, nil)
		}

		if partnerOrg.Status != entity.TenantStatusActive {
			errMsg := fmt.Sprintf("the partner_org_id  %d status is not avtive ", customerReq.PartnerOrgID)
			tilog.Info(ctx, errMsg)
			return errs.NewBadRequest(errMsg, nil)
		}

		if !slice.Contain(PartnerOrgTags, partnerOrg.OrgTag) {
			errMsg := fmt.Sprintf("the partner_org %d is not partner", customerReq.PartnerOrgID)
			tilog.Info(ctx, errMsg)
			return errs.NewBadRequest(errMsg, nil)
		}
		// check customer Org
		customerOrg, err := s.hub.Org.GetOrgByOrgID(ctx, s.db.WithContext(ctx), customerReq.CustomerOrgID.Uint64())
		if err != nil && err != gorm.ErrRecordNotFound {
			return err
		}
		if customerOrg == nil {
			errMsg := fmt.Sprintf(" customer_org_id(%d)  is invalid", customerReq.CustomerOrgID)
			return errs.NewBadRequest(errMsg, nil)
		}
		if customerOrg != nil && slice.Contain(PartnerOrgTags, customerOrg.OrgTag) && customerOrg.Status == entity.TenantStatusActive {
			errMsg := fmt.Sprintf("the customer %d is already %s", customerReq.CustomerOrgID, customerOrg.OrgTag)
			tilog.Info(ctx, errMsg)
			return errs.NewBadRequest(errMsg, nil)
		}
		// check customer status,  if status=pending,not allow
		customer, err := s.hub.Msp.GetCustomerByCustomerID(ctx, s.db.WithContext(ctx), customerReq.CustomerOrgID)
		if err != nil {
			return err
		}
		if customer != nil {
			errMsg := fmt.Sprintf("the %d is already a customer of partner", customerReq.CustomerOrgID)
			tilog.Info(ctx, errMsg)
			return errs.NewBadRequest(errMsg, nil)
		}
	}
	var err error
	//  is  cppo org will update ThirdPartyAccountProvider
	if customerReq.PartnerType == string(entity.PartnerTypeCppo) {
		err = db.Transaction(func(tx *gorm.DB) error {
			err := s.hub.Msp.CreateMspCustomer(ctx, s.db.WithContext(ctx), customerReq)
			if err != nil {
				tilog.Error(ctx, "failed to CreateCustomer", zap.Any("customerReq", customerReq), zap.Error(err))
				return err
			}

			thirdPartyAccountProvider := handler.AWSAccountProvider
			updateParam := orgDao.UpdateOrgParam{
				Id:                        customerReq.CustomerOrgID.Uint64(),
				ThirdPartyAccountProvider: &thirdPartyAccountProvider,

				IsUpdateThirdPartyAccountProvider: true,
			}
			err = s.hub.Org.UpdateOrg(ctx, s.db.WithContext(ctx), updateParam)
			if err != nil {
				tilog.Error(ctx, "failed to UpdateOrg", zap.Any("updateParam", updateParam), zap.Error(err))
			}
			return err
		})

	} else {
		err = s.hub.Msp.CreateMspCustomer(ctx, s.db.WithContext(ctx), customerReq)
		if err != nil {
			tilog.Error(ctx, "failed to CreateMspCustomer", zap.Any("customerReq", customerReq), zap.Error(err))
			return err
		}
	}

	tilog.Info(ctx, "MspSvc Send create-customer message to JSM ", zap.Any("PartnerCustomer", customerReq), zap.Any("isCheckParam", isCheckParam))
	jsmClient := jsm.GetJsmClientSingleton()
	if jsmClient.IsEnable() {
		err = jsmClient.NotifyLinkCustomerWithMspMessage(ctx, customerReq.CustomerOrgID.Uint64(), customerReq.PartnerOrgID.Uint64())
		if err != nil {
			tilog.Error(ctx, "NotifyLinkCustomerWithMspMessage err",
				zap.Any("CustomerOrgID", customerReq.CustomerOrgID.Uint64()),
				zap.Any("PartnerOrgID", customerReq.PartnerOrgID.Uint64()),
				zap.Error(err))
		}
	}
	return err
}
func (s *MspSvc) UpdateCustomer(ctx context.Context, mspCustomer entity.PartnerCustomer) error {
	tilog.Info(ctx, "MspSvc UpdateMspCustomer ", zap.Any("mspCustomer", mspCustomer))
	return s.hub.Msp.UpdateMspCustomer(ctx, s.db.WithContext(ctx), mspCustomer)
}
func (s *MspSvc) DeleteCustomer(ctx context.Context, mspCustomer entity.PartnerCustomer) error {
	tilog.Info(ctx, "MspSvc DeleteCustomer ", zap.Any("mspCustomer", mspCustomer))
	tx := s.db.WithContext(ctx)
	err := tx.Transaction(func(tx *gorm.DB) error {

		err := s.hub.Org.DeleteOrgFromPartner(ctx, tx, mspCustomer.CustomerOrgID.Uint64())
		if err != nil {
			tilog.Error(ctx, "DeleteOrgFromPartner err", zap.Any("mspCustomer", mspCustomer), zap.Error(err))
			return err
		}
		return s.hub.Msp.DeleteCustomer(ctx, tx, mspCustomer)
	})

	jsmClient := jsm.GetJsmClientSingleton()
	if jsmClient.IsEnable() {
		err = jsmClient.NotifyUnlinkCustomerWithMspMessage(ctx, mspCustomer.CustomerOrgID.Uint64(), mspCustomer.PartnerOrgID.Uint64())
		if err != nil {
			tilog.Error(ctx, "NotifyUnlinkCustomerWithMspMessage err",
				zap.Any("CustomerOrgID", mspCustomer.CustomerOrgID.Uint64()),
				zap.Any("PartnerOrgID", mspCustomer.PartnerOrgID.Uint64()),
				zap.Error(err))
		}
	}
	return err
}

// CancelCustomer
//
//	@Description :CancelCustomer convert
//	@Author wangletian 2024-01-16 16:14:12
//	@receiver s
//	@param ctx
//	@param mspCustomer
//	@return error
func (s *MspSvc) CancelCustomer(ctx context.Context, mspCustomer entity.PartnerCustomer) error {
	tilog.Info(ctx, "MspSvc CancelCustomer ", zap.Any("mspCustomer", mspCustomer))
	customer, err := s.GetCustomerByCustomerID(ctx, mspCustomer.CustomerOrgID)
	if err != nil {
		return err
	}
	if customer.Status != entity.Pending {
		msg := "the status is not pending , cancellation is prohibited."
		tilog.Info(ctx, "CancelCustomer  "+msg)
		return errs.NewBadRequest(msg, nil)
	}

	now := time.Now().Format("2024/01/05 15:04:05")
	tilog.Info(ctx, "CancelCustomer  EffectedAt comparison", zap.Any("now", now), zap.Any("customer.EffectedAt", customer.EffectedAt))
	sub := time.Since(*customer.EffectedAt).Abs()
	tilog.Info(ctx, "")
	if sub.Hours() <= 1 {
		msg := "less than 1 hour before the effective time, cancellation is prohibited."
		tilog.Info(ctx, "CancelCustomer  "+msg)
		return errs.NewBadRequest(msg, nil)
	}

	return s.DeleteCustomer(ctx, mspCustomer)
}
func (s *MspSvc) MakeCustomerEffect(ctx context.Context, customer entity.PartnerCustomer) error {
	tilog.Info(ctx, "MspSvc MakeCustomerEffect ", zap.Any("customer", customer))
	partnerOrg, err := s.hub.Org.GetOrgByOrgID(ctx, s.db.WithContext(ctx), customer.PartnerOrgID.Uint64())
	if err != nil {
		return err
	}

	customerOrg, err := s.hub.Org.GetOrgByOrgID(ctx, s.db.WithContext(ctx), customer.CustomerOrgID.Uint64())
	if err != nil {
		return err
	}
	if partnerOrg == nil || customerOrg == nil {
		tilog.Warn(ctx, "partnerOrg == nil || customerOrg == nil , ignore this customer command", zap.Any("customer", customer))
		return nil
	}

	if partnerOrg.Status != entity.TenantStatusActive {
		tilog.Warn(ctx, "the partnerOrg.Status!=active, ignore this customer command", zap.Any("customer", customer))
		return nil
	}

	if customer.PartnerType == string(entity.PartnerTypeMsp) && partnerOrg.OrgTag != string(entity.OrgTagMsp) {
		tilog.Warn(ctx, "the PartnerType =MSP, partnerOrg.OrgTag !=msp , ignore this customer command", zap.Any("customer", customer))
		return nil
	} else if customer.PartnerType == string(entity.PartnerTypeCppo) && partnerOrg.OrgTag != string(entity.OrgTagAwsCppo) {
		tilog.Warn(ctx, "the PartnerType =CPPO, partnerOrg.OrgTag !=aws_cppo , ignore this customer command", zap.Any("customer", customer))
		return nil
	} else if customer.PartnerType == string(entity.PartnerTypeMCPO) && partnerOrg.OrgTag != string(entity.OrgTagGcpMCPOReseller) {
		tilog.Warn(ctx, "the PartnerType = MCPO, partnerOrg.OrgTag != gcp_mcpo , ignore this customer command", zap.Any("customer", customer))
		return nil
	} else if customer.PartnerType == string(entity.PartnerTypeResale) && partnerOrg.OrgTag != string(entity.OrgTagResaleCustomer) {
		tilog.Warn(ctx, "the PartnerType = Resale, partnerOrg.OrgTag != resale , ignore this customer command", zap.Any("customer", customer))
		return nil
	} else if customer.PartnerType == string(entity.PartnerTypeAlicloud) && partnerOrg.OrgTag != string(entity.OrgTagAlicloud) {
		tilog.Warn(ctx, "the PartnerType = alicloud, partnerOrg.OrgTag != alicloud , ignore this customer command", zap.Any("customer", customer))
		return nil
	}

	if customerOrg.Status != entity.TenantStatusActive {
		tilog.Warn(ctx, "the customerOrg.Status!=active, ignore this customer command", zap.Any("customer", customer))
		return nil
	}

	if slice.Contain(CustomerOrgTags, customerOrg.OrgTag) {
		tilog.Warn(ctx, "the customerOrg.OrgTag is already customer, ignore this customer command", zap.Any("customer", customer))
		return nil
	}
	tx := s.db.WithContext(ctx)
	err = tx.Transaction(func(tx *gorm.DB) error {
		c := entity.PartnerCustomer{Updatable: customer.Updatable}
		c.Status = entity.Active
		err := s.UpdateCustomer(ctx, c)
		if err != nil {
			tilog.Error(ctx, "UpdateCustomer err", zap.Any("customer", c), zap.Error(err))

		}
		var updateParam orgDao.UpdateOrgParam
		if customer.PartnerType == string(entity.PartnerTypeMsp) {
			orgIdStr := strconv.FormatUint(customer.PartnerOrgID.Uint64(), 10)
			thirdPartyAccountProvider := entity.MspThirdPartyAccountProvider
			updateParam = orgDao.UpdateOrgParam{
				Id:                                customer.CustomerOrgID.Uint64(),
				OrgTag:                            string(entity.OrgTagMspCustomer),
				ThirdPartyAccountProvider:         &thirdPartyAccountProvider,
				ThirdPartyAccountCode:             &orgIdStr,
				IsUpdateThirdPartyAccountCode:     true,
				IsUpdateThirdPartyAccountProvider: true,
			}
			return s.hub.Org.UpdateOrg(ctx, tx, updateParam)
		} else if customer.PartnerType == string(entity.PartnerTypeCppo) {
			updateParam = orgDao.UpdateOrgParam{
				Id:     customer.CustomerOrgID.Uint64(),
				OrgTag: string(entity.OrgTagAwsCppoCustomer),
			}
			return s.hub.Org.UpdateOrg(ctx, tx, updateParam)
		} else if customer.PartnerType == string(entity.PartnerTypeMCPO) {
			updateParam = orgDao.UpdateOrgParam{
				Id:     customer.CustomerOrgID.Uint64(),
				OrgTag: string(entity.OrgTagGcpMCPOCustomer),
			}
			return s.hub.Org.UpdateOrg(ctx, tx, updateParam)
		} else if customer.PartnerType == string(entity.PartnerTypeResale) {
			orgIdStr := strconv.FormatUint(customer.PartnerOrgID.Uint64(), 10)
			thirdPartyAccountProvider := entity.MspThirdPartyAccountProvider
			updateParam = orgDao.UpdateOrgParam{
				Id:                                customer.CustomerOrgID.Uint64(),
				OrgTag:                            string(entity.OrgTagResaleCustomer),
				ThirdPartyAccountProvider:         &thirdPartyAccountProvider,
				ThirdPartyAccountCode:             &orgIdStr,
				IsUpdateThirdPartyAccountCode:     true,
				IsUpdateThirdPartyAccountProvider: true,
			}
			return s.hub.Org.UpdateOrg(ctx, tx, updateParam)
		} else if customer.PartnerType == string(entity.PartnerTypeAlicloud) {
			updateParam = orgDao.UpdateOrgParam{
				Id:     customer.CustomerOrgID.Uint64(),
				OrgTag: string(entity.OrgTagAlicloudCustomer),
			}
			return s.hub.Org.UpdateOrg(ctx, tx, updateParam)
		}

		return nil
	})
	if err == nil {
		sendEmailNotices(ctx, customer, *partnerOrg, *customerOrg)
	}
	return err
}

func sendEmailNotices(ctx context.Context, customer entity.PartnerCustomer, partnerOrg, customerOrg entity.Tenant) {
	// 1. send to  customer notice
	switch customer.PartnerType {
	case string(entity.PartnerTypeMsp):
		noticeToMspCustomer(ctx, customer, partnerOrg, customerOrg)
	case string(entity.PartnerTypeCppo):
		noticeCppoCustomer(ctx, customer, partnerOrg, customerOrg)
	case string(entity.OrgTagGcpMCPOReseller):
		noticeMCPOCustomer(ctx, customer, partnerOrg, customerOrg)
	case string(entity.OrgTagResalePartner):
		noticeResaleCustomer(ctx, customer, partnerOrg, customerOrg)

	case string(entity.OrgTagAlicloud):
		// do nothing
		tilog.Info(ctx, "do not sendEmailNotices for alicloud customer", zap.Any("customer", customer))
	}

	// 2. send to  partner notice
	noticePartner(ctx, customer, partnerOrg, customerOrg)

	// 3. send to  PingCAP Deal desk
	noticePingCAPDealDesk(ctx, customer, partnerOrg, customerOrg)
	tilog.Info(ctx, "sendEmailNotices end")

}
