package internal

import (
	"bytes"
	"fmt"
	"strconv"
	"strings"
	"sync"
	"text/template"

	"golang.org/x/text/message/catalog"
)

const (
	// VarsKey is the key for the message's variables, per locale(global) or per key (local).
	VarsKey = "Vars"
	// PluralCountKey is the key for the template's message pluralization.
	PluralCountKey = "PluralCount"
	// VarCountKeySuffix is the key suffix for the template's variable's pluralization,
	// e.g. HousesCount for ${Houses}.
	VarCountKeySuffix = "Count"
	// VarsKeySuffix is the key which the template message's variables
	// are stored with,
	// e.g. welcome.human.other_vars
	VarsKeySuffix = "_vars"
)

// Template is a Renderer which renders template messages.
type Template struct {
	*Message
	tmpl    *template.Template
	bufPool *sync.Pool
}

// NewTemplate returns a new Template message based on the
// catalog and the base translation Message. See `Locale.Load` method.
func NewTemplate(c *Catalog, m *Message) (*Template, error) {
	tmpl, err := template.New(m.Key).
		Delims(m.Locale.Options.Left, m.Locale.Options.Right).
		Funcs(m.Locale.FuncMap).
		Parse(m.Value)

	if err != nil {
		return nil, err
	}

	if err := registerTemplateVars(c, m); err != nil {
		return nil, fmt.Errorf("template vars: <%s = %s>: %w", m.Key, m.Value, err)
	}

	bufPool := &sync.Pool{
		New: func() interface{} {
			return new(bytes.Buffer)
		},
	}

	t := &Template{
		Message: m,
		tmpl:    tmpl,
		bufPool: bufPool,
	}

	return t, nil
}

func registerTemplateVars(c *Catalog, m *Message) error {
	if len(m.Vars) == 0 {
		return nil
	}

	msgs := selectfVars(m.Vars, false)

	variableText := ""

	for _, variable := range m.Vars {
		variableText += variable.Literal + " "
	}

	variableText = variableText[0 : len(variableText)-1]

	fullKey := m.Key + "." + VarsKeySuffix

	return c.Set(m.Locale.tag, fullKey, append(msgs, catalog.String(variableText))...)
}

// Render completes the Renderer interface.
// It renders a template message.
// Each key has its own Template, plurals too.
func (t *Template) Render(args ...interface{}) (string, error) {
	var (
		data   interface{}
		result string
	)

	argsLength := len(args)

	if argsLength > 0 {
		data = args[0]
	}

	buf := t.bufPool.Get().(*bytes.Buffer)
	buf.Reset()

	if err := t.tmpl.Execute(buf, data); err != nil {
		t.bufPool.Put(buf)
		return "", err
	}

	result = buf.String()
	t.bufPool.Put(buf)

	if len(t.Vars) > 0 {
		// get the variables plurals.
		if argsLength > 1 {
			// if has more than the map/struct
			// then let's assume the user passes variable counts by raw integer arguments.
			args = args[1:]
		} else if data != nil {
			// otherwise try to resolve them by the map(%var_name%Count)/struct(PlrualCounter).
			args = findVarsCount(data, t.Vars)
		}
		result = t.replaceTmplVars(result, args...)
	}

	return result, nil
}

func findVarsCount(data interface{}, vars []Var) (args []interface{}) {
	if data == nil {
		return nil
	}

	switch dataValue := data.(type) {
	case PluralCounter:
		for _, v := range vars {
			if count := dataValue.VarCount(v.Name); count >= 0 {
				args = append(args, count)
			}
		}
	case Map:
		for _, v := range vars {
			varCountKey := v.Name + VarCountKeySuffix
			if value, ok := dataValue[varCountKey]; ok {
				args = append(args, value)
			}
		}
	case map[string]string:
		for _, v := range vars {
			varCountKey := v.Name + VarCountKeySuffix
			if value, ok := dataValue[varCountKey]; ok {
				if count, err := strconv.Atoi(value); err == nil {
					args = append(args, count)
				}
			}
		}
	case map[string]int:
		for _, v := range vars {
			varCountKey := v.Name + VarCountKeySuffix
			if value, ok := dataValue[varCountKey]; ok {
				args = append(args, value)
			}
		}
	default:
		return nil
	}

	return
}

func findPluralCount(data interface{}) (int, bool) {
	if data == nil {
		return -1, false
	}

	switch dataValue := data.(type) {
	case PluralCounter:
		if count := dataValue.PluralCount(); count >= 0 {
			return count, true
		}
	case Map:
		if v, ok := dataValue[PluralCountKey]; ok {
			if count, ok := v.(int); ok {
				return count, true
			}
		}
	case map[string]string:
		if v, ok := dataValue[PluralCountKey]; ok {
			count, err := strconv.Atoi(v)
			if err != nil {
				return -1, false
			}

			return count, true
		}

	case map[string]int:
		if count, ok := dataValue[PluralCountKey]; ok {
			return count, true
		}
	case int:
		return dataValue, true // when this is not a template data, the caller's argument should be args[1:] now.
	case int64:
		count := int(dataValue)
		return count, true
	}

	return -1, false
}

func (t *Template) replaceTmplVars(result string, args ...interface{}) string {
	varsKey := t.Key + "." + VarsKeySuffix
	translationVarsText := t.Locale.Printer.Sprintf(varsKey, args...)
	if translationVarsText != "" {
		translatioVars := strings.Split(translationVarsText, " ")
		for i, variable := range t.Vars {
			result = strings.Replace(result, variable.Literal, translatioVars[i], 1)
		}
	}

	return result
}

func stringIsTemplateValue(value, left, right string) bool {
	leftIdx, rightIdx := strings.Index(value, left), strings.Index(value, right)
	return leftIdx != -1 && rightIdx > leftIdx
}

func getFuncs(loc *Locale) template.FuncMap {
	// set the template funcs for this locale.
	funcs := template.FuncMap{
		"tr": loc.GetMessage,
	}

	if getFuncs := loc.Options.Funcs; getFuncs != nil {
		// set current locale's template's funcs.
		for k, v := range getFuncs(loc) {
			funcs[k] = v
		}
	}

	return funcs
}