package bmumble

import (
	"fmt"
	"mime"
	"net/http"
	"regexp"
	"strings"

	"github.com/42wim/matterbridge/bridge/config"
	"github.com/mattn/godown"
	"github.com/vincent-petithory/dataurl"
)

type MessagePart struct {
	Text          string
	FileExtension string
	Image         []byte
}

func (b *Bmumble) decodeImage(uri string, parts *[]MessagePart) error {
	// Decode the data:image/... URI
	image, err := dataurl.DecodeString(uri)
	if err != nil {
		b.Log.WithError(err).Info("No image extracted")
		return err
	}
	// Determine the file extensions for that image
	ext, err := mime.ExtensionsByType(image.MediaType.ContentType())
	if err != nil || len(ext) == 0 {
		b.Log.WithError(err).Infof("No file extension registered for MIME type '%s'", image.MediaType.ContentType())
		return err
	}
	// Add the image to the MessagePart slice
	*parts = append(*parts, MessagePart{"", ext[0], image.Data})
	return nil
}

func (b *Bmumble) tokenize(t *string) ([]MessagePart, error) {
	// `^(.*?)` matches everything before the image
	// `!\[[^\]]*\]\(` matches the `![alt](` part of markdown images
	// `(data:image\/[^)]+)` matches the data: URI used by Mumble
	// `\)` matches the closing parenthesis after the URI
	// `(.*)$` matches the remaining text to be examined in the next iteration
	p := regexp.MustCompile(`^(?ms)(.*?)!\[[^\]]*\]\((data:image\/[^)]+)\)(.*)$`)
	remaining := *t
	var parts []MessagePart
	for {
		tokens := p.FindStringSubmatch(remaining)
		if tokens == nil {
			// no match -> remaining string is non-image text
			pre := strings.TrimSpace(remaining)
			if len(pre) > 0 {
				parts = append(parts, MessagePart{pre, "", nil})
			}
			return parts, nil
		}

		// tokens[1] is the text before the image
		if len(tokens[1]) > 0 {
			pre := strings.TrimSpace(tokens[1])
			parts = append(parts, MessagePart{pre, "", nil})
		}
		// tokens[2] is the image URL
		uri, err := dataurl.UnescapeToString(strings.TrimSpace(strings.ReplaceAll(tokens[2], " ", "")))
		if err != nil {
			b.Log.WithError(err).Info("URL unescaping failed")
			remaining = strings.TrimSpace(tokens[3])
			continue
		}
		err = b.decodeImage(uri, &parts)
		if err != nil {
			b.Log.WithError(err).Info("Decoding the image failed")
		}
		// tokens[3] is the text after the image, processed in the next iteration
		remaining = strings.TrimSpace(tokens[3])
	}
}

func (b *Bmumble) convertHTMLtoMarkdown(html string) ([]MessagePart, error) {
	var sb strings.Builder
	err := godown.Convert(&sb, strings.NewReader(html), nil)
	if err != nil {
		return nil, err
	}
	markdown := sb.String()
	b.Log.Debugf("### to markdown: %s", markdown)
	return b.tokenize(&markdown)
}

func (b *Bmumble) extractFiles(msg *config.Message) []config.Message {
	var messages []config.Message
	if msg.Extra == nil || len(msg.Extra["file"]) == 0 {
		return messages
	}
	// Create a separate message for each file
	for _, f := range msg.Extra["file"] {
		fi := f.(config.FileInfo)
		imsg := config.Message{
			Channel:   msg.Channel,
			Username:  msg.Username,
			UserID:    msg.UserID,
			Account:   msg.Account,
			Protocol:  msg.Protocol,
			Timestamp: msg.Timestamp,
			Event:     "mumble_image",
		}
		// If no data is present for the file, send a link instead
		if fi.Data == nil || len(*fi.Data) == 0 {
			if len(fi.URL) > 0 {
				imsg.Text = fmt.Sprintf(`<a href="%s">%s</a>`, fi.URL, fi.URL)
				messages = append(messages, imsg)
			} else {
				b.Log.Infof("Not forwarding file without local data")
			}
			continue
		}
		mimeType := http.DetectContentType(*fi.Data)
		// Mumble only supports images natively, send a link instead
		if !strings.HasPrefix(mimeType, "image/") {
			if len(fi.URL) > 0 {
				imsg.Text = fmt.Sprintf(`<a href="%s">%s</a>`, fi.URL, fi.URL)
				messages = append(messages, imsg)
			} else {
				b.Log.Infof("Not forwarding file of type %s", mimeType)
			}
			continue
		}
		mimeType = strings.TrimSpace(strings.Split(mimeType, ";")[0])
		// Build data:image/...;base64,... style image URL and embed image directly into the message
		du := dataurl.New(*fi.Data, mimeType)
		dataURL, err := du.MarshalText()
		if err != nil {
			b.Log.WithError(err).Infof("Image Serialization into data URL failed (type: %s, length: %d)", mimeType, len(*fi.Data))
			continue
		}
		imsg.Text = fmt.Sprintf(`<img src="%s"/>`, dataURL)
		messages = append(messages, imsg)
	}
	// Remove files from original message
	msg.Extra["file"] = nil
	return messages
}