package novuhandler

import (
	"fmt"
	"strconv"
	"strings"
	"time"

	"github.com/pingcap/dbaas/billing2/handler/cachehandler"

	"github.com/pingcap/dbaas/billing2/constant"

	"golang.org/x/exp/rand"

	"github.com/pingcap/dbaas/billing2/utils/timeutils"

	"github.com/pingcap/dbaas/billing2/billing_errors"
	"github.com/pingcap/dbaas/billing2/forms"
	"github.com/pingcap/dbaas/billing2/handler/centralhandler"
	"github.com/pingcap/dbaas/billing2/utils/log"
	"github.com/pingcap/dbaas/billing2/utils/role"
	"go.uber.org/zap"
)

type EventTo struct {
	SubscriberId string `json:"subscriberId"`
	//LastName     string `json:"lastName"`
	//FirstName    string `json:"firstName"`
	//Email        string `json:"email"`
}

type Event struct {
	Name          string                 `json:"name"`
	To            EventTo                `json:"to"`
	Payload       map[string]interface{} `json:"payload"`
	TransactionId string                 `json:"transactionId"`
}

type EventRequest struct {
	Events []Event `json:"events"`
}
type EventResponse struct {
	Acknowledged  bool   `json:"acknowledged"`
	Status        string `json:"status"`
	TransactionId string `json:"transactionId"`
}
type EventListResponse struct {
	Data []EventResponse `json:"data"`
}
type EventSingleResponse struct {
	Data EventResponse `json:"data"`
}
type SubscriberInfo struct {
	User         forms.User
	SubscriberID string `json:"subscriber_id"`
	Names        string `json:"names"`
}

func (m NovuHandlerImpl) GetSubscriberIdList(tenantID uint, trigger forms.Trigger) ([]SubscriberInfo, error) {
	var userListRes []SubscriberInfo
	var inputRoles []string

	cache := cachehandler.Get()
	isDup := false
	subscriberIDMap := map[string]bool{}
	funcName := trigger.TriggerID
	if len(trigger.TransactionId) > 0 {
		isDup = true
		cache.Get(funcName, &subscriberIDMap, trigger.TransactionId)
	}
	//serRoleEncode
	for _, inRole := range trigger.Roles {
		inputRoles = append(inputRoles, role.UserRoleEncode(inRole))

	}
	if trigger.ScopeType == constant.ScopeOrg {
		for _, orgID := range trigger.TargetIDList {

			tenant, err := strconv.ParseUint(orgID, 10, 0)
			if err != nil {
				return nil, billing_errors.NewSystemError(err)
			}
			tenantInfo, err := centralhandler.GetCentralHandler().GetTenant(uint(tenant))
			if err != nil {
				return nil, billing_errors.NewSystemError(err)
			}

			userList, err := centralhandler.GetCentralHandler().GetUserListByOrgID(uint(tenant), inputRoles)
			if err != nil {
				return nil, billing_errors.NewSystemError(err)
			}
			for _, user := range userList {
				subscriberID := fmt.Sprintf("%s_%s", orgID, strconv.FormatUint(uint64(user.UserID), 10))
				if isDup {
					if subscriberIDMap[subscriberID] {
						continue
					}
					subscriberIDMap[subscriberID] = true

				}
				userListRes = append(userListRes, SubscriberInfo{
					User:         user,
					SubscriberID: subscriberID,
					Names:        tenantInfo.Name + "," + user.Email,
				})
			}
		}
	}
	if trigger.ScopeType == constant.ScopeProject {
		tenantInfo, err := centralhandler.GetCentralHandler().GetTenant(tenantID)
		if err != nil {
			return nil, billing_errors.NewSystemError(err)
		}
		projectID, err := strconv.ParseUint(trigger.TargetIDList[0], 10, 0)
		if err != nil {
			return nil, billing_errors.NewSystemError(err)
		}
		userList, err := centralhandler.GetCentralHandler().GetUserListByProjectID(uint(projectID), inputRoles)
		if err != nil {
			return nil, billing_errors.NewSystemError(err)
		}
		for _, user := range userList {
			subscriberID := fmt.Sprintf("%s_%s", strconv.FormatUint(uint64(tenantID), 10), strconv.FormatUint(uint64(user.UserID), 10))
			if isDup {
				if subscriberIDMap[subscriberID] {
					continue
				}
				subscriberIDMap[subscriberID] = true
			}
			userListRes = append(userListRes, SubscriberInfo{
				User:         user,
				SubscriberID: subscriberID,
				Names:        tenantInfo.Name + "," + user.Email,
			})
		}

	}
	if isDup {
		cache.Set(funcName, subscriberIDMap, trigger.TransactionId)
	}

	return userListRes, nil
}
func (m NovuHandlerImpl) getSubjectAndGroupName(id string) (string, string, string, error) {
	workflow, err := m.GetWorkflow(id)
	if err != nil {
		return "", "", "", err
	}
	return workflow.Subject, workflow.NotificationGroup, workflow.WorkFlowName, nil

}

func generateRandomString(length int) string {
	const charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
	b := make([]byte, length)

	if _, err := rand.Read(b); err != nil {
		rand.Seed(uint64(time.Now().UnixNano()))
		for i := range b {
			b[i] = charset[rand.Intn(len(charset))]
		}
		return string(b)
	}
	for i := range b {
		b[i] = charset[int(b[i])%len(charset)]
	}
	return string(b)
}

func (m NovuHandlerImpl) BulkTriggerEvent(tenantID uint, trigger forms.Trigger) (forms.Workflow, error) {

	userListRes, err := m.GetSubscriberIdList(tenantID, trigger)
	if err != nil {
		return forms.Workflow{}, err
	}
	if userListRes == nil {
		log.Info("subscriberId List is empty", zap.Uint("tenantID", tenantID), zap.Any("trigger", trigger))
		return forms.Workflow{}, nil
	}
	params := make(map[string]interface{})
	//deal system
	params[NovuParamOperator] = trigger.Operator
	if trigger.IsTest {
		params[NovuParamSource] = "test"

	}
	if trigger.IsSendNow {
		params[NovuIsSendNow] = "true"
	} else {
		params[NovuIsSendNow] = "false"

	}
	params[NovuSendAt] = trigger.ScheduleTime.Format("2006-01-02T15:04:05.000Z0700")
	subject, groupName, title, err := m.getSubjectAndGroupName(trigger.WorkFlowID)
	if err != nil {
		return forms.Workflow{}, err
	}
	params[NovuParamSubject] = subject
	params[NovuParamNotificationGroup] = groupName
	params[NovuNotificationTitle] = title
	params[NovuTargetRoles] = strings.Join(trigger.Roles, ",")

	//deal from trigger
	for k, v := range trigger.Payload {
		params[k] = v
	}

	var request EventRequest
	for _, user := range userListRes {
		paramsCopy := make(map[string]interface{})
		for k, v := range params {
			paramsCopy[k] = v
		}
		paramsCopy[NovuTargetName] = user.Names

		request.Events = append(request.Events, Event{
			Name:          trigger.TriggerID,
			TransactionId: timeutils.GetNow().Format("2006-01-02T15:04") + "_" + generateRandomString(10),

			To: EventTo{
				//FirstName:    user.User.FirstName,
				//LastName:     user.User.LastName,
				SubscriberId: user.SubscriberID,
			},
			Payload: paramsCopy,
		})

	}
	log.Info("BulkTriggerEvent", zap.Any("request", request))
	var outPut = EventResponse{}
	err = GetClient().Request("POST", "/v1/events/trigger/bulk", nil, request, &outPut)
	if err != nil {
		NotificationCounter.WithLabelValues("sendFailed").Add(float64(len(userListRes)))
		return forms.Workflow{}, err
	}
	NotificationCounter.WithLabelValues("send").Add(float64(len(userListRes)))
	log.Info("BulkTriggerEvent result ", zap.Any("outPut", outPut))

	return forms.Workflow{}, nil
}

func (m NovuHandlerImpl) BroadcastTriggerEvent(trigger forms.Trigger) (forms.Workflow, error) {

	params := make(map[string]interface{})
	for k, v := range trigger.Payload {
		params[k] = v
	}

	params[NovuParamOperator] = trigger.Operator
	if trigger.IsTest {
		params[NovuParamSource] = "test"

	}
	if trigger.IsSendNow {
		params[NovuIsSendNow] = "true"
	} else {
		params[NovuIsSendNow] = "false"

	}
	params[NovuSendAt] = trigger.ScheduleTime.Format("2006-01-02T15:04:05.000Z0700")
	subject, groupName, title, err := m.getSubjectAndGroupName(trigger.WorkFlowID)
	if err != nil {
		return forms.Workflow{}, err
	}
	params[NovuParamSubject] = subject
	params[NovuParamNotificationGroup] = groupName
	params[NovuNotificationTitle] = title
	params[NovuTargetRoles] = strings.Join(trigger.Roles, ",")
	params[NovuTargetName] = NovuTargetAllName

	type BroadcastTrigger struct {
		Name          string                 `json:"name"`
		Payload       map[string]interface{} `json:"payload"`
		TransactionId string                 `json:"transactionId"`
	}
	day := timeutils.GetNow().Format("2006-01-02")
	request := BroadcastTrigger{
		Name:          trigger.TriggerID,
		Payload:       params,
		TransactionId: day + "_" + trigger.Operator,
	}

	log.Info("BroadcastTriggerEvent", zap.Any("EventRequest", request))
	var outPut = EventSingleResponse{}
	err = GetClient().Request("POST", "/v1/events/trigger/broadcast", nil, request, &outPut)
	if err != nil {
		NotificationCounter.WithLabelValues("sendBroadcastFailed").Inc()

		return forms.Workflow{}, err
	}
	NotificationCounter.WithLabelValues("sendBroadcast").Inc()
	log.Info("BroadcastTriggerEvent result ", zap.Any("outPut", outPut))

	return forms.Workflow{}, nil
}
