package messagebackcall

import (
	"bytes"
	"context"
	"encoding/json"
	"io"
	"net/http"
	"strings"
	"sync"
	"time"

	"github.com/go-logr/logr"
	"github.com/tidbcloud/escalation-base/feishu_messager_v2/pkg/infra/clients/alicli"
	"github.com/tidbcloud/escalation-base/feishu_messager_v2/pkg/infra/clients/larkcli"
	"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"
	"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
}

type ServiceResult struct {
	dig.Out

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

type Message struct {
	ClientKey string
	MessageId string
}

type MessageStatus int

const (
	MessageStatusUnread   MessageStatus = 0
	MessageStatusRead     MessageStatus = 1
	MessageStatusNotified MessageStatus = 2
)

type svc struct {
	logger      logr.Logger
	larkCliSet  *larkcli.LarkClientSet
	httpCli     *http.Client
	backCallUrl string
	interval    time.Duration
	messages    map[Message]MessageStatus
	messageMu   sync.RWMutex
	notify      notify.NotifyServiceAPI
}

func NewService(p ServiceParam) (ServiceResult, error) {
	httpCli := &http.Client{}
	s := svc{
		logger:      p.Logger.WithName("MessageBackCallService"),
		httpCli:     httpCli,
		larkCliSet:  p.LarkClientSet,
		backCallUrl: p.Conf.Services.MessageServiceBackCall.CallUrl,
		interval:    p.Conf.Services.MessageServiceBackCall.PollMessageReadInterval,
		messages:    make(map[Message]MessageStatus),
		notify:      p.NotifySvc,
	}

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

func (s *svc) Name() string {
	return "MessageBackCall"
}

func (s *svc) Run(ctx context.Context) error {
	s.periodically(ctx)
	return nil
}

func (s *svc) AddWatchMessage(ctx context.Context, clientKey string, messageId string) {
	go s.addWatchMessage(ctx, clientKey, messageId)
}

func (s *svc) addWatchMessage(ctx context.Context, clientKey string, messageId string) {
	s.messageMu.Lock()
	defer s.messageMu.Unlock()
	_, ok := s.messages[Message{ClientKey: clientKey, MessageId: messageId}]
	if !ok {
		s.messages[Message{ClientKey: clientKey, MessageId: messageId}] = MessageStatusUnread
	}
	if len(s.messages) > 100 {
		s.notify.Notify(ctx, "too many messages accumulated, please take a look.")
	}
}

func (s *svc) periodically(ctx context.Context) {
	//logger := log.FromContextOrDef(ctx, s.logger)
	for {
		logger := s.logger.WithName("periodically")
		ctx := logr.NewContext(ctx, logger)
		s.pollMessages(ctx)
		s.callback(ctx)
		s.garbageCollect(ctx)
		select {
		case <-time.After(s.interval):
		case <-ctx.Done():
			return
		}
	}
}

func (s *svc) pollMessages(ctx context.Context) {
	logger := log.FromContextOrDef(ctx, s.logger)
	s.messageMu.RLock()
	arr := make([]Message, 0, len(s.messages))
	for message, status := range s.messages {
		if status == MessageStatusUnread {
			arr = append(arr, message)
		}
	}
	s.messageMu.RUnlock()

	for _, message := range arr {
		larkClient, err := s.larkCliSet.GetClient(message.ClientKey)
		if err != nil || larkClient == nil {
			logger.Info("get lark client failed", "client_key", message.ClientKey, "err", err)
			continue
		}
		readUsers, err := larkClient.Message.ReadUsers(ctx, message.MessageId)
		if err != nil {
			logger.Info("read user failed", "client_key", message.ClientKey, "message_id", message.MessageId, "err", err)
		} else if len(readUsers) > 0 {
			s.messageMu.Lock()
			s.messages[message] = MessageStatusRead
			s.messageMu.Unlock()
		}
	}

}

func (s *svc) callback(ctx context.Context) {
	logger := log.FromContextOrDef(ctx, s.logger)
	s.messageMu.RLock()
	callbackList := make([]Message, 0, len(s.messages))
	for message, status := range s.messages {
		if status == MessageStatusRead {
			callbackList = append(callbackList, message)
		}
	}
	s.messageMu.RUnlock()

	for _, message := range callbackList {
		params := map[string]string{"message_id": message.MessageId}
		jsonData, err := json.Marshal(params)
		if err != nil {
			logger.Info("marshal callback payload failed", "message_id", message.MessageId, "err", err)
			continue
		}
		req, err := http.NewRequestWithContext(ctx, http.MethodPost, s.backCallUrl, bytes.NewBuffer(jsonData))
		resp, err := s.httpCli.Do(req)
		if err != nil {
			logger.Info("callback failed", "messageId", message.MessageId, "error", err.Error())
			continue
		}
		if resp.StatusCode != http.StatusOK {
			buf := new(strings.Builder)
			_, err = io.Copy(buf, resp.Body)
			logger.Info("callback failed", "messageId", message.MessageId, "response", buf.String())
			continue
		}

		logger.Info("callback succeed", "messageId", message.MessageId)

		s.messageMu.Lock()
		s.messages[message] = MessageStatusNotified
		s.messageMu.Unlock()
	}
}

func (s *svc) garbageCollect(ctx context.Context) {
	//logger := log.FromContextOrDef(ctx, s.logger)
	s.messageMu.RLock()
	arr := make([]Message, 0, len(s.messages))
	for message, status := range s.messages {
		if status == MessageStatusNotified {
			arr = append(arr, message)
		}
	}
	s.messageMu.RUnlock()

	for _, message := range arr {
		s.messageMu.Lock()
		delete(s.messages, message)
		s.messageMu.Unlock()
	}
}
