package sv

import (
	"bytes"
	"io/fs"
	"os"
	"sort"
	"text/template"
	"time"

	"github.com/Masterminds/semver/v3"
)

type releaseNoteTemplateVariables struct {
	Release     string
	Tag         string
	Version     *semver.Version
	Date        time.Time
	Sections    []ReleaseNoteSection
	AuthorNames []string
}

// OutputFormatter output formatter interface.
type OutputFormatter interface {
	FormatReleaseNote(releasenote ReleaseNote) (string, error)
	FormatChangelog(releasenotes []ReleaseNote) (string, error)
}

// OutputFormatterImpl formater for release note and changelog.
type OutputFormatterImpl struct {
	templates *template.Template
}

// NewOutputFormatter TemplateProcessor constructor.
func NewOutputFormatter(templatesFS fs.FS) *OutputFormatterImpl {
	templateFNs := map[string]interface{}{
		"timefmt":    timeFormat,
		"getsection": getSection,
		"getenv":     os.Getenv,
	}
	tpls := template.Must(template.New("templates").Funcs(templateFNs).ParseFS(templatesFS, "*"))
	return &OutputFormatterImpl{templates: tpls}
}

// FormatReleaseNote format a release note.
func (p OutputFormatterImpl) FormatReleaseNote(releasenote ReleaseNote) (string, error) {
	var b bytes.Buffer
	if err := p.templates.ExecuteTemplate(&b, "releasenotes-md.tpl", releaseNoteVariables(releasenote)); err != nil {
		return "", err
	}
	return b.String(), nil
}

// FormatChangelog format a changelog.
func (p OutputFormatterImpl) FormatChangelog(releasenotes []ReleaseNote) (string, error) {
	templateVars := make([]releaseNoteTemplateVariables, len(releasenotes))
	for i, v := range releasenotes {
		templateVars[i] = releaseNoteVariables(v)
	}

	var b bytes.Buffer
	if err := p.templates.ExecuteTemplate(&b, "changelog-md.tpl", templateVars); err != nil {
		return "", err
	}
	return b.String(), nil
}

func releaseNoteVariables(releasenote ReleaseNote) releaseNoteTemplateVariables {
	release := releasenote.Tag
	if releasenote.Version != nil {
		release = "v" + releasenote.Version.String()
	}
	return releaseNoteTemplateVariables{
		Release:     release,
		Tag:         releasenote.Tag,
		Version:     releasenote.Version,
		Date:        releasenote.Date,
		Sections:    releasenote.Sections,
		AuthorNames: toSortedArray(releasenote.AuthorsNames),
	}
}

func toSortedArray(input map[string]struct{}) []string {
	result := make([]string, len(input))
	i := 0
	for k := range input {
		result[i] = k
		i++
	}
	sort.Strings(result)
	return result
}
