/*
 * SPDX-FileCopyrightText: 2023 jordi fita mas <jfita@peritasoft.com>
 * SPDX-License-Identifier: AGPL-3.0-only
 */

package template

import (
	"fmt"
	"html/template"
	"io"
	"math"
	"net/http"
	"net/url"
	"path"
	"strconv"
	"strings"
	"time"

	"golang.org/x/text/language"
	"golang.org/x/text/message"
	"golang.org/x/text/number"

	"dev.tandem.ws/tandem/camper/pkg/auth"
	"dev.tandem.ws/tandem/camper/pkg/build"
	"dev.tandem.ws/tandem/camper/pkg/database"
	httplib "dev.tandem.ws/tandem/camper/pkg/http"
)

func adminTemplateFile(name string) string {
	return "web/templates/admin/" + name
}

func publicTemplateFile(name string) string {
	return "web/templates/public/" + name
}

func MustRenderAdmin(w io.Writer, r *http.Request, user *auth.User, company *auth.Company, filename string, data interface{}) {
	layout := "layout.gohtml"
	if httplib.IsHTMxRequest(r) {
		layout = "htmx.gohtml"
	}
	mustRenderLayout(w, user, company, adminTemplateFile, data, layout, filename)
}

func MustRenderAdminFiles(w io.Writer, r *http.Request, user *auth.User, company *auth.Company, data interface{}, filenames ...string) {
	layout := "layout.gohtml"
	if httplib.IsHTMxRequest(r) {
		layout = "htmx.gohtml"
	}
	filenames = append([]string{layout}, filenames...)
	mustRenderLayout(w, user, company, adminTemplateFile, data, filenames...)
}

func MustRenderAdminNoLayout(w io.Writer, r *http.Request, user *auth.User, company *auth.Company, filename string, data interface{}) {
	mustRenderLayout(w, user, company, adminTemplateFile, data, filename)
}

func MustRenderAdminNoLayoutFiles(w io.Writer, r *http.Request, user *auth.User, company *auth.Company, data interface{}, filenames ...string) {
	mustRenderLayout(w, user, company, adminTemplateFile, data, filenames...)
}

func MustRenderPublic(w io.Writer, r *http.Request, user *auth.User, company *auth.Company, filename string, data interface{}) {
	layout := "layout.gohtml"
	mustRenderLayout(w, user, company, publicTemplateFile, data, layout, filename)
}

func MustRenderPublicNoLayout(w io.Writer, r *http.Request, user *auth.User, company *auth.Company, filename string, data interface{}) {
	mustRenderLayout(w, user, company, publicTemplateFile, data, filename)
}

func MustRenderPublicFiles(w io.Writer, r *http.Request, user *auth.User, company *auth.Company, data interface{}, filenames ...string) {
	layout := "layout.gohtml"
	filenames = append([]string{layout}, filenames...)
	mustRenderLayout(w, user, company, publicTemplateFile, data, filenames...)
}

func mustRenderLayout(w io.Writer, user *auth.User, company *auth.Company, templateFile func(string) string, data interface{}, templates ...string) {
	t := template.New(templates[len(templates)-1])
	t.Funcs(template.FuncMap{
		"camperVersion": func() string {
			return build.Version
		},
		"gettext":  user.Locale.Get,
		"pgettext": user.Locale.GetC,
		"currentLocale": func() string {
			return user.Locale.Language.String()
		},
		"isLoggedIn": func() bool {
			return user.LoggedIn
		},
		"isAdmin": user.IsAdmin,
		"CSRFHeader": func() string {
			return fmt.Sprintf(`"%s": "%s"`, auth.CSRFTokenHeader, user.CSRFToken)
		},
		"CSRFInput": func() template.HTML {
			return template.HTML(fmt.Sprintf(`<input type="hidden" name="%s" value="%s">`, auth.CSRFTokenField, user.CSRFToken))
		},
		"raw": func(s string) template.HTML {
			return template.HTML(s)
		},
		"replaceAll": func(s, old, new string) string {
			return strings.ReplaceAll(s, old, new)
		},
		"humanizeBytes": func(bytes int64) string {
			return humanizeBytes(bytes)
		},
		"formatPrice": func(price string) string {
			return FormatPrice(price, user.Locale.Language, user.Locale.CurrencyPattern, company.DecimalDigits, company.CurrencySymbol)
		},
		"formatDate": func(time time.Time) template.HTML {
			return template.HTML(`<time datetime="` + time.Format(database.ISODateFormat) + `">` + time.Format("02/01/2006") + "</time>")
		},
		"formatDateTime": func(time time.Time) template.HTML {
			return template.HTML(`<time datetime="` + time.Format(database.ISODateFormat) + `">` + time.Format("02/01/2006 15:04:05") + "</time>")
		},
		"formatDateAttr": func(time time.Time) string {
			return time.Format(database.ISODateFormat)
		},
		"formatPercent": func(value int) string {
			return fmt.Sprintf("%d %%", value)
		},
		"today": func() string {
			return time.Now().Format(database.ISODateFormat)
		},
		"queryEscape": func(s string) string {
			return url.QueryEscape(s)
		},
		"inc": func(i int) int {
			return i + 1
		},
		"dec": func(i int) int {
			return i - 1
		},
		"add": func(y, x int) int {
			return x + y
		},
		"sub": func(y, x int) int {
			return x - y
		},
		"int": func(v interface{}) int {
			switch v := v.(type) {
			case int:
				return v
			case bool:
				if v {
					return 1
				} else {
					return 0
				}
			case time.Weekday:
				return int(v)
			case time.Month:
				return int(v)
			default:
				panic(fmt.Errorf("could not convert to integer"))
			}
		},
		"hexToDec": func(s string) int {
			num, _ := strconv.ParseInt(s, 16, 0)
			return int(num)
		},
		"slugify": Slugify,
	})
	templates = append(templates, "form.gohtml")
	files := make([]string, len(templates))
	for i, tmpl := range templates {
		if len(tmpl) > 4 && tmpl[0] == 'w' && tmpl[1] == 'e' && tmpl[2] == 'b' && tmpl[3] == '/' {
			files[i] = tmpl
		} else {
			files[i] = templateFile(tmpl)
		}
	}
	if _, err := t.ParseFiles(files...); err != nil {
		panic(err)
	}
	if rw, ok := w.(http.ResponseWriter); ok {
		rw.Header().Set("Content-Type", "text/html; charset=utf-8")
	}
	if err := t.ExecuteTemplate(w, path.Base(templates[0]), data); err != nil {
		panic(err)
	}
}

func FormatPrice(price string, language language.Tag, currencyPattern string, decimalDigits int, currencySymbol string) string {
	p := message.NewPrinter(language)
	f, err := strconv.ParseFloat(price, 64)
	if err != nil {
		f = math.NaN()
	}
	return p.Sprintf(currencyPattern, decimalDigits, number.Decimal(f), currencySymbol)
}
