package feishu

import (
	"bytes"
	"encoding/json"
	"errors"
	"log"
	"net/http"
	"sync"
	"time"

	"github.com/spf13/viper"
)

const (
	defaultAckProcessorNum int = 3
	defaultChannelCapacity int = 100
)

type MessageManager struct {
	messages        []*MessageInfo
	messageMu       sync.Mutex
	correlatedBot   BotSetting
	processors      []*AckProcessor
	callbackChannel chan string
	callbackUrl     string
}

type MessageInfo struct {
	MessageId  string
	UserId     string
	UserIdType string
	IsRead     bool
}

type AckProcessor struct {
	AckCh       chan string
	CallbackUrl string
}

func createMessageManager(setting BotSetting) *MessageManager {
	newManager := &MessageManager{}
	newManager.messages = []*MessageInfo{}
	newManager.correlatedBot = setting
	newManager.callbackChannel = make(chan string, defaultChannelCapacity)
	newManager.processors = make([]*AckProcessor, 0)
	newManager.callbackUrl = viper.GetString("callback_url")
	for i := 0; i < defaultAckProcessorNum; i++ {
		newManager.processors = append(newManager.processors, &AckProcessor{newManager.callbackChannel, newManager.callbackUrl})
	}
	// May be separated from here in the future
	newManager.Start()
	return newManager
}

func (processor *AckProcessor) ProcessAck() {
	for {
		messageId := <-processor.AckCh
		ok, err := sendMessageId(processor.CallbackUrl, messageId)
		if ok {
			log.Println("send", messageId, "succeeded")
		} else {
			log.Println(err)
			// Put back the failed request to the channel
			processor.AckCh <- messageId
		}
	}
}

func sendMessageId(url string, messageId string) (bool, error) {
	params := map[string]string{"message_id": messageId}
	jsonData, err := json.Marshal(params)
	if err != nil {
		return false, err
	}
	resp, err := http.Post(url, "application/json", bytes.NewBuffer(jsonData))
	if err != nil {
		return false, err
	}
	if resp.StatusCode != http.StatusOK {
		return false, errors.New("Fail to call callback url")
	}
	return true, nil
}

func (manager *MessageManager) Start() {
	for _, processor := range manager.processors {
		go processor.ProcessAck()
	}
	go func() {
		for {
			time.Sleep(5 * time.Second)
			manager.Update()
		}
	}()
}

func (manager *MessageManager) Put(msgInfo *MessageInfo) {
	manager.messageMu.Lock()
	manager.messages = append(manager.messages, msgInfo)
	manager.messageMu.Unlock()

}

func (manager *MessageManager) Get() {

}

func (manager *MessageManager) Update() {
	unreadMessages := []*MessageInfo{}
	manager.messageMu.Lock()
	for _, msgInfo := range manager.messages {
		if !msgInfo.IsRead {
			unreadMessages = append(unreadMessages, msgInfo)
		}
	}
	manager.messageMu.Unlock()
	for _, msgInfo := range unreadMessages {
		readPersons, err := getAlreadyRead(manager.correlatedBot, msgInfo.MessageId, msgInfo.UserIdType)
		if err != nil {
			log.Println("Fail to read Something")
		} else {
			if len(readPersons) > 0 {
				// Put into a callback queue
				manager.messageMu.Lock()
				msgInfo.IsRead = true
				manager.messageMu.Unlock()
				go func(msgId string) {
					okayToQuit := false
					for !okayToQuit {
						select {
						case manager.callbackChannel <- msgId:
							okayToQuit = true
						default:
							log.Println("Callback channel buffer is full")
							time.Sleep(5 * time.Second)
						}
					}
				}(msgInfo.MessageId)
			}
		}
	}
}
