package feishu

/*This file implements message senders with feishu oapi v3, which may not compatible with old api*/

import (
	"context"
	"errors"
	"feishu-messager/pkg/ali"
	"fmt"
	"log"
	"strings"
	"sync"

	lark "github.com/larksuite/oapi-sdk-go/v3"
	larkcard "github.com/larksuite/oapi-sdk-go/v3/card"
	im "github.com/larksuite/oapi-sdk-go/v3/service/im/v1"
)

type BotSetting struct {
	appId     string
	appSecret string
}

var clients map[BotSetting]*lark.Client
var managers map[BotSetting]*MessageManager

var getClientMu sync.Mutex

var getManagerMu sync.Mutex

func init() {
	clients = make(map[BotSetting]*lark.Client)
	managers = make(map[BotSetting]*MessageManager)
}

func getMessageManager(setting BotSetting) *MessageManager {
	getManagerMu.Lock()
	defer getManagerMu.Unlock()
	manager, ok := managers[setting]
	if ok {
		return manager
	} else {
		newManager := createMessageManager(setting)
		managers[setting] = newManager
		return newManager
	}
}

func getClient(setting BotSetting) *lark.Client {
	getClientMu.Lock()
	defer getClientMu.Unlock()
	client, ok := clients[setting]
	if ok {
		return client
	} else {
		newClient := lark.NewClient(setting.appId, setting.appSecret)
		clients[setting] = newClient
		return newClient
	}
	return nil
}

func sendMessageToChatV3(params FeishuChatParams, text string, chatId string) error {
	// Currently, you are visiting larksuite, which uses default storage and default log (error level). More optional configurations are as follows: core.NewConfig()
	//appSettings := getAppSettings(params)
	//conf := core.NewConfig(core.DomainFeiShu, appSettings, core.SetLoggerLevel(core.LoggerLevelError))

	// The content of the sent message
	var receiveIdType string

	if strings.Contains(chatId, "ou_") {
		receiveIdType = "open_id"
	} else if strings.Contains(chatId, "oc_") {
		receiveIdType = "chat_id"
	} else if strings.Contains(chatId, "on_") {
		receiveIdType = "union_id"
	} else if strings.Contains(chatId, "@") {
		receiveIdType = "email"
	} else {
		receiveIdType = "user_id"
	}

	content := im.NewTextMsgBuilder().
		Text(text).
		Build()
	larkClient := getClient(BotSetting{appId: params.AppId, appSecret: params.AppSecret})
	resp, err := larkClient.Im.Message.Create(context.Background(), im.NewCreateMessageReqBuilder().
		ReceiveIdType(receiveIdType).
		Body(im.NewCreateMessageReqBodyBuilder().
			MsgType(im.MsgTypeText).
			ReceiveId(chatId).
			Content(content).
			Build()).Build())
	if err != nil {
		log.Println("**Message Sent Failed**")
		log.Println(err)
		log.Println("failed message:", text)
		log.Println("failed receiver id:", chatId)
		return err
	}
	if !resp.Success() {
		log.Println("**Message Sent Failed")
		log.Println("Feishu messaging code:", resp.Code)
		log.Println("Feishu error message:", resp.Msg)
		log.Println("Failed request id", resp.RequestId())
		log.Println("Message failed to sent", text)
		log.Println("Receiver Id for the error message", chatId)
		return errors.New(fmt.Sprint(resp.Code) + ": " + resp.Msg)
	}
	return nil
}

func sendCardToChatV3(settingParams FeishuChatParams, params FeishuCardParams, chatId string) error {
	var receiveIdType string
	if strings.Contains(chatId, "ou_") {
		receiveIdType = "open_id"
	} else if strings.Contains(chatId, "oc_") {
		receiveIdType = "chat_id"
	} else if strings.Contains(chatId, "on_") {
		receiveIdType = "union_id"
	} else if strings.Contains(chatId, "@") {
		receiveIdType = "email"
	} else {
		receiveIdType = "user_id"
	}

	content, err := generateFeishuCardJsonStringV3(params)
	if err != nil {
		log.Println("**Message Parsing Failed**")
		log.Println(err)
		log.Println("failed message:", content)
		log.Println("failed receiver id:", chatId)
		return err
	}

	if strings.Contains(chatId, "ou_") {
		receiveIdType = "open_id"
	} else if strings.Contains(chatId, "oc_") {
		receiveIdType = "chat_id"
	} else if strings.Contains(chatId, "on_") {
		receiveIdType = "union_id"
	} else if strings.Contains(chatId, "@") {
		receiveIdType = "email"
	} else {
		receiveIdType = "user_id"
	}
	larkClient := getClient(BotSetting{appId: settingParams.AppId, appSecret: settingParams.AppSecret})
	resp, err := larkClient.Im.Message.Create(context.Background(), im.NewCreateMessageReqBuilder().
		ReceiveIdType(receiveIdType).
		Body(im.NewCreateMessageReqBodyBuilder().
			MsgType(im.MsgTypeInteractive).
			ReceiveId(chatId).
			Content(content).
			Build()).Build())
	if err != nil {
		log.Println("**Message Sent Failed**")
		log.Println(err)
		log.Println("failed message:", content)
		log.Println("failed receiver id:", chatId)
		return err
	}
	if !resp.Success() {
		log.Println("**Message Sent Failed")
		log.Println("Feishu messaging code:", resp.Code)
		log.Println("Feishu error message:", resp.Msg)
		log.Println("Failed request id", resp.RequestId())
		log.Println("Message failed to sent", content)
		log.Println("Receiver Id for the error message", chatId)
		return errors.New(fmt.Sprint(resp.Code) + ": " + resp.Msg)
	}
	return nil
}

func processInAppUrgentMessage(ctx context.Context, message *im.Message) error {
	return nil
}

func processSMSUrgentMessage(ctx context.Context, message *im.Message) error {
	return nil
}

func processPhoneUrgentMessage(ctx context.Context, message *im.Message) error {
	return nil
}

func SendUrgentMessageToChat(params FeishuChatParams, text string, chatId string, urgentType string) (retMessageId string, err error) {
	ctx := context.Background()
	var receiveIdType string

	if strings.Contains(chatId, "ou_") {
		receiveIdType = "open_id"
	} else if strings.Contains(chatId, "oc_") {
		receiveIdType = "chat_id"
	} else if strings.Contains(chatId, "on_") {
		receiveIdType = "union_id"
	} else if strings.Contains(chatId, "@") {
		receiveIdType = "email"
	} else {
		receiveIdType = "user_id"
	}

	content := im.NewTextMsgBuilder().
		Text(text).
		Build()
	larkClient := getClient(BotSetting{appId: params.AppId, appSecret: params.AppSecret})
	resp, err := larkClient.Im.Message.Create(context.Background(), im.NewCreateMessageReqBuilder().
		ReceiveIdType(receiveIdType).
		Body(im.NewCreateMessageReqBodyBuilder().
			MsgType(im.MsgTypeText).
			ReceiveId(chatId).
			Content(content).
			Build()).Build())
	if err != nil {
		log.Println("**Message Sent Failed**")
		log.Println(err)
		log.Println("failed message:", text)
		log.Println("failed receiver id:", chatId)
		return "", err
	}
	if !resp.Success() {
		log.Println("**Message Sent Failed**")
		log.Println("Feishu messaging code:", resp.Code)
		log.Println("Feishu error message:", resp.Msg)
		log.Println("Failed request id", resp.RequestId())
		log.Println("Message failed to sent", text)
		log.Println("Receiver Id for the error message", chatId)
		return "", errors.New(fmt.Sprint(resp.Code) + ": " + resp.Msg)
	}

	messageData := resp.Data
	if messageData == nil {
		log.Println("**Message Response Error**")
		log.Println("The return body is empty")
		return "", errors.New("message response body is empty")
	}
	messageId := messageData.MessageId

	if urgentType == "in-app" {
		req := im.NewUrgentAppMessageReqBuilder().
			MessageId(*messageId).
			UserIdType(receiveIdType).
			UrgentReceivers(im.NewUrgentReceiversBuilder().
				UserIdList([]string{chatId}).
				Build()).
			Build()
		urgentResp, err := larkClient.Im.Message.UrgentApp(context.Background(), req)
		if err != nil {
			log.Println("**Urgent Message Sent Failed**")
			log.Println(err)
			log.Println("failed message:", text)
			log.Println("failed message id", *messageId)
			log.Println("failed receiver id:", chatId)
			return *messageId, err
		}
		if !urgentResp.Success() {
			log.Println("**Urgent Message Sent Failed**")
			log.Println("Feishu messaging code:", urgentResp.Code)
			log.Println("Feishu error message:", urgentResp.Msg)
			log.Println("Failed request id", urgentResp.RequestId())
			log.Println("Message failed to sent", text)
			log.Println("failed message id", *messageId)
			log.Println("Receiver Id for the error message", chatId)
			return *messageId, errors.New(fmt.Sprint(urgentResp.Code) + ": " + urgentResp.Msg)
		}
	} else if urgentType == "sms" {
		//TODO: add a token limiter here
		req := im.NewUrgentSmsMessageReqBuilder().
			MessageId(*messageId).
			UserIdType(receiveIdType).
			UrgentReceivers(im.NewUrgentReceiversBuilder().
				UserIdList([]string{chatId}).
				Build()).
			Build()
		urgentResp, err := larkClient.Im.Message.UrgentSms(context.Background(), req)
		if err != nil {
			log.Println("**Urgent SMS Sent Failed**")
			log.Println(err)
			log.Println("failed message:", text)
			log.Println("failed message id", *messageId)
			log.Println("failed receiver id:", chatId)
			return *messageId, err
		}
		if !urgentResp.Success() {
			log.Println("**Urgent SMS Sent Failed**")
			log.Println("Feishu messaging code:", urgentResp.Code)
			log.Println("Feishu error message:", urgentResp.Msg)
			log.Println("Failed request id", urgentResp.RequestId())
			log.Println("Message failed to sent", text)
			log.Println("failed message id", *messageId)
			log.Println("Receiver Id for the error message", chatId)
			return *messageId, errors.New(fmt.Sprint(urgentResp.Code) + ": " + urgentResp.Msg)
		}
	} else if urgentType == "phone" {
		// use ali call service here.
		// req := im.NewUrgentPhoneMessageReqBuilder().
		// 	MessageId(*messageId).
		// 	UserIdType(receiveIdType).
		// 	UrgentReceivers(im.NewUrgentReceiversBuilder().
		// 		UserIdList([]string{chatId}).
		// 		Build()).
		// 	Build()
		// urgentResp, err := larkClient.Im.Message.UrgentPhone(context.Background(), req)
		// if err != nil {
		// 	log.Println("**Urgent Phone Sent Failed**")
		// 	log.Println(err)
		// 	log.Println("failed message:", text)
		// 	log.Println("failed message id", *messageId)
		// 	log.Println("failed receiver id:", chatId)
		// 	return *messageId, err
		// }
		// if !urgentResp.Success() {
		// 	log.Println("**Urgent Phone Sent Failed**")
		// 	log.Println("Feishu messaging code:", urgentResp.Code)
		// 	log.Println("Feishu error message:", urgentResp.Msg)
		// 	log.Println("Failed request id", urgentResp.RequestId())
		// 	log.Println("Message failed to sent", text)
		// 	log.Println("failed message id", *messageId)
		// 	log.Println("Receiver Id for the error message", chatId)
		// 	return *messageId, errors.New(fmt.Sprint(urgentResp.Code) + ": " + urgentResp.Msg)
		// }

		// getPhoneNumber
		number, err := GetUserPhoneNumber(ctx, FeishuCliParam{
			AppId:     params.AppId,
			AppSecret: params.AppSecret,
		}, chatId)
		if err != nil {
			return "", err
		}

		if IsChinaPhoneNo(number) {
			// in-app urgent
			req := im.NewUrgentAppMessageReqBuilder().
				MessageId(*messageId).
				UserIdType(receiveIdType).
				UrgentReceivers(im.NewUrgentReceiversBuilder().
					UserIdList([]string{chatId}).
					Build()).
				Build()
			urgentResp, err := larkClient.Im.Message.UrgentApp(context.Background(), req)
			if err != nil || !urgentResp.Success() {
				log.Printf("failed to make urgent, message id: %s", *messageId)
			}
			// call
			aliCli, err := ali.NewDefaultClient()
			if err != nil {
				return "", err
			}
			err = aliCli.SingleCall(ali.SingleCallParam{
				CalledNumber: number,
			})
			if err != nil {
				log.Printf("failed to call number %s with ali: %s", number, err)
				return "", err
			}
		} else {
			req := im.NewUrgentPhoneMessageReqBuilder().
				MessageId(*messageId).
				UserIdType(receiveIdType).
				UrgentReceivers(im.NewUrgentReceiversBuilder().
					UserIdList([]string{chatId}).
					Build()).
				Build()
			urgentResp, err := larkClient.Im.Message.UrgentPhone(context.Background(), req)
			if err != nil || !urgentResp.Success() {
				log.Printf("failed to make urgent, message id: %s", *messageId)
			}
		}
	} else {
		err = errors.New("urgent type not supported")
	}
	return *messageId, err
}

func SendUrgentCardToChat(settingParams FeishuChatParams, params FeishuCardParams, chatId string, urgentType string) (retMessageId string, err error) {
	ctx := context.Background()
	var receiveIdType string
	if strings.Contains(chatId, "ou_") {
		receiveIdType = "open_id"
	} else if strings.Contains(chatId, "oc_") {
		receiveIdType = "chat_id"
	} else if strings.Contains(chatId, "on_") {
		receiveIdType = "union_id"
	} else if strings.Contains(chatId, "@") {
		receiveIdType = "email"
	} else {
		receiveIdType = "user_id"
	}
	content, err := generateFeishuCardJsonStringV3(params)
	if err != nil {
		log.Println("**Message Parsing Failed**")
		log.Println(err)
		log.Println("failed message:", content)
		log.Println("failed receiver id:", chatId)
		return "", err
	}

	botSetting := BotSetting{appId: settingParams.AppId, appSecret: settingParams.AppSecret}

	larkClient := getClient(botSetting)
	resp, err := larkClient.Im.Message.Create(context.Background(), im.NewCreateMessageReqBuilder().
		ReceiveIdType(receiveIdType).
		Body(im.NewCreateMessageReqBodyBuilder().
			MsgType(im.MsgTypeInteractive).
			ReceiveId(chatId).
			Content(content).
			Build()).Build())
	if err != nil {
		log.Println("**Message Sent Failed**")
		log.Println(err)
		log.Println("failed message:", content)
		log.Println("failed receiver id:", chatId)
		return "", err
	}
	if !resp.Success() {
		log.Println("**Message Sent Failed**")
		log.Println("Feishu messaging code:", resp.Code)
		log.Println("Feishu error message:", resp.Msg)
		log.Println("Failed request id", resp.RequestId())
		log.Println("Message failed to sent", content)
		log.Println("Receiver Id for the error message", chatId)
		return "", errors.New(fmt.Sprint(resp.Code) + ": " + resp.Msg)
	}

	messageData := resp.Data
	if messageData == nil {
		log.Println("**Message Response Error**")
		log.Println("The return body is empty")
		return "", errors.New("Message response body is empty.")
	}
	messageId := messageData.MessageId

	// Put the message into message manager
	// TODO: make sure this segment of code should be here.
	manager := getMessageManager(botSetting)
	manager.Put(&MessageInfo{MessageId: *messageId, UserId: chatId, UserIdType: receiveIdType, IsRead: false})

	/*if urgentType == "in-app" {
		err = processInAppUrgentMessage(coreCtx, message)
	} else if urgentType == "sms" {
		err = processSMSUrgentMessage(coreCtx, message)
	} else if urgentType == "phone" {
		err = processPhoneUrgentMessage(coreCtx, message)
	} else {
		err = errors.New("Urgent type not supported.")
	}*/
	if urgentType == "in-app" {
		req := im.NewUrgentAppMessageReqBuilder().
			MessageId(*messageId).
			UserIdType(receiveIdType).
			UrgentReceivers(im.NewUrgentReceiversBuilder().
				UserIdList([]string{chatId}).
				Build()).
			Build()
		urgentResp, err := larkClient.Im.Message.UrgentApp(context.Background(), req)
		if err != nil {
			log.Println("**Urgent Message Sent Failed**")
			log.Println(err)
			log.Println("failed message:", content)
			log.Println("failed message id", *messageId)
			log.Println("failed receiver id:", chatId)
			return *messageId, err
		}
		if !urgentResp.Success() {
			log.Println("**Urgent Message Sent Failed**")
			log.Println("Feishu messaging code:", urgentResp.Code)
			log.Println("Feishu error message:", urgentResp.Msg)
			log.Println("Failed request id", urgentResp.RequestId())
			log.Println("Message failed to sent", content)
			log.Println("failed message id", *messageId)
			log.Println("Receiver Id for the error message", chatId)
			return *messageId, errors.New(fmt.Sprint(urgentResp.Code) + ": " + urgentResp.Msg)
		}
	} else if urgentType == "sms" {
		//TODO: add a token limiter here
		req := im.NewUrgentSmsMessageReqBuilder().
			MessageId(*messageId).
			UserIdType(receiveIdType).
			UrgentReceivers(im.NewUrgentReceiversBuilder().
				UserIdList([]string{chatId}).
				Build()).
			Build()
		urgentResp, err := larkClient.Im.Message.UrgentSms(context.Background(), req)
		if err != nil {
			log.Println("**Urgent SMS Sent Failed**")
			log.Println(err)
			log.Println("failed message:", content)
			log.Println("failed message id", *messageId)
			log.Println("failed receiver id:", chatId)
			return *messageId, err
		}
		if !urgentResp.Success() {
			log.Println("**Urgent SMS Sent Failed**")
			log.Println("Feishu messaging code:", urgentResp.Code)
			log.Println("Feishu error message:", urgentResp.Msg)
			log.Println("Failed request id", urgentResp.RequestId())
			log.Println("Message failed to sent", content)
			log.Println("failed message id", *messageId)
			log.Println("Receiver Id for the error message", chatId)
			return *messageId, errors.New(fmt.Sprint(urgentResp.Code) + ": " + urgentResp.Msg)
		}
	} else if urgentType == "phone" {
		//TODO: add a token limiter here
		// req := im.NewUrgentPhoneMessageReqBuilder().
		// 	MessageId(*messageId).
		// 	UserIdType(receiveIdType).
		// 	UrgentReceivers(im.NewUrgentReceiversBuilder().
		// 		UserIdList([]string{chatId}).
		// 		Build()).
		// 	Build()
		// urgentResp, err := larkClient.Im.Message.UrgentPhone(context.Background(), req)
		// if err != nil {
		// 	log.Println("**Urgent Phone Sent Failed**")
		// 	log.Println(err)
		// 	log.Println("failed message:", content)
		// 	log.Println("failed message id", *messageId)
		// 	log.Println("failed receiver id:", chatId)
		// 	return *messageId, err
		// }
		// if !urgentResp.Success() {
		// 	log.Println("**Urgent Phone Sent Failed**")
		// 	log.Println("Feishu messaging code:", urgentResp.Code)
		// 	log.Println("Feishu error message:", urgentResp.Msg)
		// 	log.Println("Failed request id", urgentResp.RequestId())
		// 	log.Println("Message failed to sent", content)
		// 	log.Println("failed message id", *messageId)
		// 	log.Println("Receiver Id for the error message", chatId)
		// 	return *messageId, errors.New(fmt.Sprint(urgentResp.Code) + ": " + urgentResp.Msg)
		// }

		// getPhoneNumber
		number, err := GetUserPhoneNumber(ctx, FeishuCliParam{
			AppId:     settingParams.AppId,
			AppSecret: settingParams.AppSecret,
		}, chatId)
		if err != nil {
			return "", err
		}

		if IsChinaPhoneNo(number) {
			// in-app urgent
			req := im.NewUrgentAppMessageReqBuilder().
				MessageId(*messageId).
				UserIdType(receiveIdType).
				UrgentReceivers(im.NewUrgentReceiversBuilder().
					UserIdList([]string{chatId}).
					Build()).
				Build()
			urgentResp, err := larkClient.Im.Message.UrgentApp(context.Background(), req)
			if err != nil || !urgentResp.Success() {
				log.Printf("failed to make urgent, message id: %s", *messageId)
			}
			// ali call
			aliCli, err := ali.NewDefaultClient()
			if err != nil {
				return "", err
			}
			aliCli.SingleCall(ali.SingleCallParam{
				CalledNumber: number,
			})
		} else {
			req := im.NewUrgentPhoneMessageReqBuilder().
				MessageId(*messageId).
				UserIdType(receiveIdType).
				UrgentReceivers(im.NewUrgentReceiversBuilder().
					UserIdList([]string{chatId}).
					Build()).
				Build()
			urgentResp, err := larkClient.Im.Message.UrgentPhone(context.Background(), req)
			if err != nil || !urgentResp.Success() {
				log.Printf("failed to make urgent, message id: %s", *messageId)
			}
		}
	} else {
		err = errors.New("urgent type not supported")
	}
	return *messageId, err
}

func generateFeishuCardJsonStringV3(params FeishuCardParams) (string, error) {
	config := larkcard.NewMessageCardConfig().
		WideScreenMode(true).
		EnableForward(true).
		UpdateMulti(false).
		Build()
	header := larkcard.NewMessageCardHeader().
		Template(params.Color).
		Title(larkcard.NewMessageCardPlainText().
			Content(params.Title).
			Build()).
		Build()
	divElement := larkcard.NewMessageCardDiv().
		Fields([]*larkcard.MessageCardField{larkcard.NewMessageCardField().
			Text(larkcard.NewMessageCardLarkMd().
				Content(params.Text).
				Build()).
			IsShort(true).
			Build()}).
		Build()
	cardContent, err := larkcard.NewMessageCard().
		Config(config).
		Header(header).
		Elements([]larkcard.MessageCardElement{divElement}).
		String()
	return cardContent, err
}

func getAlreadyRead(setting BotSetting, messageId string, userIdType string) ([]string, error) {
	client := getClient(setting)
	if client == nil {
		return []string{}, errors.New("Fail to create client")
	}
	req := im.NewReadUsersMessageReqBuilder().MessageId(messageId).UserIdType(userIdType).Build()
	resp, err := client.Im.Message.ReadUsers(context.Background(), req)

	if err != nil {
		return []string{}, err
	}

	if !resp.Success() {
		return []string{}, errors.New(fmt.Sprint(resp.Code) + ": " + resp.Msg)
	}

	readUsers := []string{}
	for _, user := range resp.Data.Items {
		log.Println("read user:", *user.UserId)
		readUsers = append(readUsers, *user.UserId)
	}

	return readUsers, nil
}
