package message

import (
	"context"
	"fmt"

	"github.com/go-logr/logr"
	"github.com/tidbcloud/escalation-base/feishu_messager_v2/entity"
	"github.com/tidbcloud/escalation-base/feishu_messager_v2/pkg/infra/clients/alicli"
	"github.com/tidbcloud/escalation-base/feishu_messager_v2/pkg/infra/clients/larkcli"
	larkmessage "github.com/tidbcloud/escalation-base/feishu_messager_v2/pkg/infra/clients/larkcli/message"
	"github.com/tidbcloud/escalation-base/feishu_messager_v2/pkg/infra/clients/olympuscli"
	"github.com/tidbcloud/escalation-base/feishu_messager_v2/pkg/infra/clients/twiliocli"
	"github.com/tidbcloud/escalation-base/feishu_messager_v2/pkg/infra/config"
	"github.com/tidbcloud/escalation-base/feishu_messager_v2/pkg/infra/log"
	"github.com/tidbcloud/escalation-base/feishu_messager_v2/pkg/service"
	messagebackcall "github.com/tidbcloud/escalation-base/feishu_messager_v2/pkg/service/message_backcall"
	"github.com/tidbcloud/escalation-base/feishu_messager_v2/pkg/service/notify"
	"github.com/tidbcloud/pkgv2/db"
	"go.uber.org/dig"
)

type ServiceParam struct {
	dig.In

	Conf          *config.RootConfig
	Logger        logr.Logger
	DBProvider    db.TransProvider
	LarkClientSet *larkcli.LarkClientSet
	AliClient     *alicli.AliClient
	TwilioClient  *twiliocli.TwilioClient
	OlympusClient olympuscli.OlympusClient
	NotifySvc     notify.NotifyServiceAPI
	BackCallSvc   messagebackcall.MessageBackcallAPI
}

type ServiceResult struct {
	dig.Out

	API     MessageServiceAPI
	Service service.Service `group:"service"`
}

type Chats struct {
	ChatMap map[string]string
}

type messageSvc struct {
	logger       logr.Logger
	larkCliSet   *larkcli.LarkClientSet
	aliCli       *alicli.AliClient
	enableTwilio bool
	twiliocli    *twiliocli.TwilioClient
	olympuscli   olympuscli.OlympusClient
	notifySvc    notify.NotifyServiceAPI
	backCallSvc  messagebackcall.MessageBackcallAPI
	chatsMap     map[string]Chats // map client key to chats
}

func NewService(p ServiceParam) (ServiceResult, error) {
	svc := messageSvc{
		logger:       p.Logger.WithName("MessageService"),
		larkCliSet:   p.LarkClientSet,
		aliCli:       p.AliClient,
		enableTwilio: p.Conf.Services.MessageService.EnableTwilio,
		twiliocli:    p.TwilioClient,
		olympuscli:   p.OlympusClient,
		notifySvc:    p.NotifySvc,
		backCallSvc:  p.BackCallSvc,
	}

	chatsMap := make(map[string]Chats)
	for _, chatInCli := range p.Conf.Services.MessageService.ChatList {
		chatmap := make(map[string]string)
		for _, chat := range chatInCli.Chats {
			chatmap[chat.ChatTag] = chat.ChatId
		}
		chatsMap[chatInCli.ClientKey] = Chats{ChatMap: chatmap}
	}
	svc.chatsMap = chatsMap

	return ServiceResult{API: &svc, Service: &svc}, nil
}

func (s *messageSvc) SendMessageCard(ctx context.Context, message entity.Message) (string, error) {
	clientKey := message.BotTag
	if len(clientKey) == 0 {
		clientKey = DEFAULT_CLIENT_KEY
	}

	larkCli, err := s.larkCliSet.GetClient(clientKey)
	if err != nil {
		err = fmt.Errorf("cannot find larkcli for bot_tag %s: %w", message.BotTag, err)
	}
	return s.sendMessageCard(larkCli, message)
}

func (s *messageSvc) SendUrgentMessageCard(ctx context.Context, message entity.Message) (string, error) {
	logger := log.FromContextOrDef(ctx, s.logger)
	if !message.RawMode {
		return "", fmt.Errorf("`raw_mode` field should be true to send an urgent message")
	}

	userId := message.ReceiverId
	receiverIdType := getReceiverType(userId)
	if receiverIdType != "user_id" {
		return "", fmt.Errorf("`receiver_id` field should be a `user_id` to send an urgent message, the receiver_id type is %s", receiverIdType)
	}

	clientKey := message.BotTag
	if len(clientKey) == 0 {
		clientKey = DEFAULT_CLIENT_KEY
	}

	larkCli, err := s.larkCliSet.GetClient(clientKey)
	if err != nil {
		err = fmt.Errorf("cannot find larkcli for bot_tag %s: %w", message.BotTag, err)
	}

	msgId, err := s.sendMessageCard(larkCli, message)
	if err != nil {
		return "", err
	}
	if len(msgId) == 0 {
		// TODO: add APIwrapper here
		return "", fmt.Errorf("message id cannot be empty when send a urgent message")
	}

	// Shouldn't return error from here.
	var number string
	number, ok := s.olympuscli.GetPhoneNoByUserId(ctx, userId)
	if !ok {
		contactInfo, err := larkCli.Contact.GetUser(ctx, userId)
		if err != nil {
			err = fmt.Errorf("cannot find contact for user_id %s: %w", userId, err)
		}
		if contactInfo.Mobile == nil {
			err = fmt.Errorf("failed to get phoneNo in client %s for user_id %s: %w", clientKey, userId, err)
		}
		number = *contactInfo.Mobile
	}

	urgentType := "in-app"

	if isDomestic(number) {
		logger.Info("start to call phone number using AliCloud", "receiverId", message.ReceiverId, "number", number)
		err = s.aliCli.Call.SingleCall(number)
		if err != nil {
			logger.Error(err, "call phone number using AliCloud failed", "receiverId", message.ReceiverId, "number", number)
			s.notifySvc.Notify(ctx, fmt.Sprintf("call phone number failed for receiver %s: %s", message.ReceiverId, err))
		}
	} else {
		if s.enableTwilio {
			logger.Info("start to call phone number using Twilio", "receiverId", message.ReceiverId, "number", number)
			err = s.twiliocli.Call.SingleCall(number)
			if err != nil {
				logger.Error(err, "call phone number using Twilio failed", "receiverId", message.ReceiverId, "number", number)
				s.notifySvc.Notify(ctx, fmt.Sprintf("call phone number failed for receiver %s: %s", message.ReceiverId, err))
			}
		} else {
			urgentType = "phone"
		}
	}

	// make lark card urgent
	err = larkCli.Message.MakeUrgent(ctx, msgId, userId, urgentType)
	if err != nil {
		logger.Info("make card urgent failed", "msgId", msgId, "error", err.Error())
		s.notifySvc.Notify(ctx, fmt.Sprintf("make card urgent failed for msgId %s: %s", msgId, err))
	}

	s.backCallSvc.AddWatchMessage(ctx, clientKey, msgId)
	return msgId, nil
}

// send message card and return messageId if succeed.
func (s *messageSvc) sendMessageCard(larkCli *larkcli.LarkClient, message entity.Message) (string, error) {
	botTag := message.BotTag
	if len(botTag) == 0 {
		botTag = DEFAULT_CLIENT_KEY
	}

	var receiverId, receiverType string
	if message.RawMode {
		receiverId = message.ReceiverId
	} else {
		var ok bool
		chatTag := message.ChatTag
		receiverId, ok = s.GetChat(botTag, chatTag)
		if !ok {
			return "", fmt.Errorf("chat ID not found for chatTag: %s", chatTag)
		}
	}
	receiverType = getReceiverType(receiverId)

	ctx := context.Background()
	messageId := ""
	if receiverType == "webhook" {
		err := larkCli.Message.SendMessageViaWebhook(
			ctx,
			receiverId,
			larkmessage.CardParam{
				Title: message.Title,
				Color: message.Color,
				Text:  message.Text,
			},
		)
		if err != nil {
			return "", fmt.Errorf("cannot send via webhook: %w", err)
		}
	} else {
		messagePointer, err := larkCli.Message.SendMessage(
			ctx,
			receiverType,
			receiverId,
			larkmessage.CardParam{
				Title: message.Title,
				Color: message.Color,
				Text:  message.Text,
			},
		)
		if err != nil {
			return "", fmt.Errorf("cannot send message %w, clientKey: %s, receiverId: %s, receiverType: %s", err, botTag, receiverId, receiverType)
		}
		if messagePointer == nil {
			return "", fmt.Errorf("message id is empty")
		}
		messageId = *messagePointer
	}

	return messageId, nil
}

func (s *messageSvc) Name() string {
	return "Message"
}

func (s *messageSvc) GetChat(clientKey string, chatTag string) (string, bool) {
	chatMap, ok := s.chatsMap[clientKey]
	if !ok {
		return "", false
	}

	// `chat` can be chatId or webhook url
	chat, ok := chatMap.ChatMap[chatTag]
	if !ok {
		return "", false
	} else {
		return chat, true
	}
}
