package slack_utils

import (
	"fmt"
	"github.com/samber/lo"
	"github.com/slack-go/slack"
	"gitlab.bob.co.za/bob-public-utils/bobgroup-go-utils/errors"
	"gitlab.bob.co.za/bob-public-utils/bobgroup-go-utils/logs"
	"reflect"
	"regexp"
)

type SlackField struct {
	Label string
	Value any
}

type SlackFile struct {
	File            string
	FileSize        int
	Content         string
	Filename        string
	Title           string
	InitialComment  string
	ThreadTimestamp string
	AltTxt          string
	SnippetText     string
}

type SlackClient struct {
	Client *slack.Client
}

func GetClient(apiKey string) *SlackClient {
	return &SlackClient{
		Client: slack.New(apiKey),
	}
}

func (s *SlackClient) SendAlert(message string, channel string, parentMessageTimestamp ...string) string {
	if s == nil || s.Client == nil {
		// If the slack client isn't set, log the message to Raygun instead
		logs.ErrorMsg(message)
		return ""
	}

	slackMessageOptions := []slack.MsgOption{
		slack.MsgOptionText(message, false),
	}

	if len(parentMessageTimestamp) > 0 {
		slackMessageOptions = append(slackMessageOptions, slack.MsgOptionTS(parentMessageTimestamp[0]))
	}

	return s.postMessage(channel, slackMessageOptions)
}

func (s *SlackClient) SendAlertWithFields(message string, channel string, slackFields []SlackField, parentMessageTimestamp ...string) string {
	if s == nil || s.Client == nil {
		// If the slack client isn't set, log the message to Raygun instead
		slackFieldMap := slackFieldsToMap(slackFields)
		logs.ErrorWithFields(errors.Error(message), slackFieldMap)
		return ""
	}

	slackFieldsPerMessage := lo.Chunk(slackFields, 50) // Slack has a limit of 50 blocks per message

	var messageTimestamp string
	for i, slackFieldsForMessage := range slackFieldsPerMessage {
		messageText := message
		if len(slackFieldsPerMessage) > 1 {
			messageText = fmt.Sprintf("%s (%d/%d)", message, i+1, len(slackFieldsPerMessage))
		}

		blocks := []slack.Block{slack.NewSectionBlock(slack.NewTextBlockObject(
			"plain_text",
			messageText,
			hasEmoji(messageText),
			false),
			nil,
			nil,
		),
		}

		slackFieldsChunked := lo.Chunk(slackFieldsForMessage, 2)

		for _, slackFieldsChunk := range slackFieldsChunked {
			fieldBlocksForChunk := []*slack.TextBlockObject{}
			for _, slackField := range slackFieldsChunk {
				slackFieldValue := slackField.Value
				// if slackField.Value is a pointer, dereference it
				if reflect.ValueOf(slackField.Value).Kind() == reflect.Ptr {
					slackFieldValue = reflect.ValueOf(slackField.Value).Elem().Interface()
				}

				text := fmt.Sprintf("*%s*\n%v", slackField.Label, slackFieldValue)
				fieldBlocksForChunk = append(fieldBlocksForChunk, slack.NewTextBlockObject(
					"mrkdwn",
					text,
					false,
					false,
				))
			}

			blocks = append(blocks, slack.NewSectionBlock(nil, fieldBlocksForChunk, nil))
		}

		slackMessageOptions := []slack.MsgOption{
			slack.MsgOptionBlocks(blocks...),
		}

		if len(parentMessageTimestamp) > 0 {
			slackMessageOptions = append(slackMessageOptions, slack.MsgOptionTS(parentMessageTimestamp[0]))
		}

		messageTimestamp = s.postMessage(channel, slackMessageOptions)
	}

	// Return the last message timestamp
	return messageTimestamp
}

func (s *SlackClient) postMessage(channel string, messageOptions []slack.MsgOption) string {
	_, messageTimestamp, err := s.Client.PostMessage(channel, messageOptions...)
	if err != nil {
		logs.ErrorWithMsg(err, "Error sending slack:")
	}
	return messageTimestamp
}

func (s *SlackClient) SendFile(channel string, file SlackFile) error {
	fileParameters := slack.UploadFileV2Parameters{
		Channel:         channel,
		File:            file.File,
		FileSize:        file.FileSize,
		Content:         file.Content,
		Filename:        file.Filename,
		Title:           file.Title,
		InitialComment:  file.InitialComment,
		ThreadTimestamp: file.ThreadTimestamp,
		AltTxt:          file.AltTxt,
		SnippetText:     file.SnippetText,
	}

	_, err := s.Client.UploadFileV2(fileParameters)
	if err != nil {
		logs.ErrorWithMsg(err, "Error uploading file to slack:")
		return err
	}
	return nil
}

func hasEmoji(message string) bool {
	emojiRegex := regexp.MustCompile(`:[a-zA-Z0-9_]+:`)
	return emojiRegex.MatchString(message)
}

func slackFieldsToMap(fields []SlackField) map[string]any {
	slackFieldsMap := make(map[string]any)
	for _, field := range fields {
		slackFieldsMap[field.Label] = field.Value
	}
	return slackFieldsMap
}