package plugins

import (
	"fmt"
	"github.com/alexandre-normand/figlet4go"
	"github.com/alexandre-normand/slackscot"
	"github.com/alexandre-normand/slackscot/actions"
	"github.com/alexandre-normand/slackscot/config"
	"github.com/alexandre-normand/slackscot/plugin"
	"github.com/pkg/errors"
	"io"
	"io/ioutil"
	"net/http"
	"net/url"
	"os"
	"path"
	"path/filepath"
	"regexp"
	"strings"
	"unicode"
)

const (
	figletFontURLKey = "figletFontUrl" // Optional, string (url) to a figlet font. Default font is used if not set. Fonts can be found on http://www.figlet.org/fontdb.cgi and url should be for the raw .flf file like http://www.figlet.org/fonts/banner.flf
)

const (
	// EmojiBannerPluginName holds identifying name for the emoji banner plugin
	EmojiBannerPluginName = "emojiBanner"
	bannerMaxWordLength   = 4
)

// EmojiBannerMaker holds the plugin data for the emoji banner maker plugin
type EmojiBannerMaker struct {
	*slackscot.Plugin
	tempDirFontPath string
}

// NewEmojiBannerMaker creates a new instance of the plugin. Note that since it creates a temporary
// directory to store fonts, the caller should make sure to defer Close on shutdown
func NewEmojiBannerMaker(c *config.PluginConfig) (toClose io.Closer, emojiBannerPlugin *slackscot.Plugin, err error) {
	emojiBannerRegex := regexp.MustCompile("(?i)(emoji banner) (.*)")

	options := figlet4go.NewRenderOptions()
	renderer := figlet4go.NewAsciiRender()

	tempDirFontPath, err := ioutil.TempDir("", EmojiBannerPluginName)
	if err != nil {
		os.RemoveAll(tempDirFontPath)
		return nil, nil, err
	}

	// Download all fonts and write them in the fontPath
	fontURL := c.GetString(figletFontURLKey)
	if fontURL != "" {
		fontName, err := downloadFontToDir(fontURL, tempDirFontPath)
		if err != nil {
			os.RemoveAll(tempDirFontPath)
			return nil, nil, err
		}

		err = renderer.LoadFont(tempDirFontPath)
		if err != nil {
			os.RemoveAll(tempDirFontPath)
			return nil, nil, fmt.Errorf("[%s] Can't load fonts from [%s]: %v", EmojiBannerPluginName, tempDirFontPath, err)
		}

		options.FontName = fontName
	}

	ebm := new(EmojiBannerMaker)
	ebm.Plugin = plugin.New(EmojiBannerPluginName).
		WithCommand(actions.NewCommand().
			WithMatcher(matchBannerCommand).
			WithUsage("emoji banner <word of 5 characters or less> <emoji>").
			WithDescription("Renders a single-word banner with the provided emoji").
			WithAnswerer(func(m *slackscot.IncomingMessage) *slackscot.Answer {
				return validateAndRenderEmoji(m.NormalizedText, emojiBannerRegex, renderer, options)
			}).
			Build()).
		Build()
	ebm.tempDirFontPath = tempDirFontPath

	return ebm, ebm.Plugin, nil
}

// matchBannerCommand returns true if the incoming message starts with emoji banner
func matchBannerCommand(m *slackscot.IncomingMessage) bool {
	return strings.HasPrefix(m.NormalizedText, "emoji banner")
}

// Close cleans up resources (temp font directory) used by the plugin
func (e *EmojiBannerMaker) Close() (err error) {
	return os.RemoveAll(e.tempDirFontPath)
}

func downloadFontToDir(fontURL string, fontPath string) (fontName string, err error) {
	url, b, err := downloadURL(fontURL)
	if err != nil {
		return "", errors.Wrapf(err, "Error reading data from font url [%s]: %v", fontURL, err)
	}

	filename := path.Base(url.EscapedPath())

	fullpath := filepath.Join(fontPath, filename)
	err = ioutil.WriteFile(fullpath, b, 0644)
	if err != nil {
		return "", errors.Wrapf(err, "Error saving file [%s] from font url [%s]", fullpath, fontURL)
	}

	return strings.TrimSuffix(filename, ".flf"), nil
}

func downloadURL(fontURL string) (parsedURL *url.URL, content []byte, err error) {
	url, err := url.Parse(fontURL)
	if err != nil {
		return nil, nil, errors.Wrapf(err, "Invalid font url [%s]", fontURL)
	}

	resp, err := http.Get(fontURL)
	if err != nil {
		return nil, nil, errors.Wrapf(err, "Error loading font url [%s]", fontURL)
	}

	b, err := ioutil.ReadAll(resp.Body)

	return url, b, err
}

func validateAndRenderEmoji(message string, regex *regexp.Regexp, renderer *figlet4go.AsciiRender, options *figlet4go.RenderOptions) *slackscot.Answer {
	commandParameters := regex.FindStringSubmatch(message)

	if len(commandParameters) > 0 {
		parameters := strings.Split(commandParameters[2], " ")

		if len(parameters) == 2 {
			word := parameters[0]
			emoji := parameters[1]

			if len(word) <= bannerMaxWordLength {
				return renderBanner(word, emoji, renderer, options)
			}

			return &slackscot.Answer{Text: fmt.Sprintf("`Wrong usage` (word *longer* than `%d` characters): emoji banner `<word of 5 characters or less>` `<emoji>`", bannerMaxWordLength)}
		}
	}

	return &slackscot.Answer{Text: fmt.Sprintf("`Wrong usage`: emoji banner `<word of %d characters or less>` `<emoji>`", bannerMaxWordLength)}
}

func renderBanner(word, emoji string, renderer *figlet4go.AsciiRender, options *figlet4go.RenderOptions) *slackscot.Answer {
	rendered, err := renderer.RenderOpts(word, options)
	if err != nil {
		return &slackscot.Answer{Text: fmt.Sprintf("Error generating: %v", err)}
	}

	var result strings.Builder
	result.WriteString("\r\n")
	for _, character := range rendered {
		if unicode.IsPrint(character) && character != ' ' {
			result.WriteString(emoji)
		} else if character == ' ' {
			result.WriteString("⬜️")
		} else {
			result.WriteString(string(character))
		}
	}

	return &slackscot.Answer{Text: result.String()}
}
