package templating

import (
	"fmt"
	"html/template"
	"io"
	"log"
	"net/http"
	"officeHours/models"
	"os"
	"time"
)

// parsed templates available for execution
var templates map[string]*template.Template = map[string]*template.Template{}

// Initialise and parse templates.
//
// Should only be called once.
//
// Since this is something which may error and feels like
// it should not be done automatically on import,
// put it into a function.
func InitTemplates() error {
	const templateDir = "templating/templates/"
	var funcs = template.FuncMap{
		"DayName": models.DayName,
		"divide":  func(i int, j int) int { return i / j },
	}
	var emptyTemplate = template.New("").Funcs(funcs)
	var baseTemplate, err = template.ParseFiles(templateDir + "base.html")
	if err != nil {
		return fmt.Errorf("parsing base template failed:\n%w", err)
	}
	baseTemplate.Funcs(funcs)

	type toCache struct {
		filename   string
		standalone bool
	}
	var toParse = map[string]toCache{
		// full html templates
		"addFailure":      {"addFailure.html", false},
		"addMask":         {"addMask.html", false},
		"addSuccess":      {"addSuccess.html", false},
		"deleteFailure":   {"deleteFailure.html", false},
		"deleteMailForm":  {"deleteMailForm.html", false},
		"deleteSuccess":   {"deleteSuccess.html", false},
		"executeFailure":  {"executeFailure.html", false},
		"executeSuccess":  {"executeSuccess.html", false},
		"index":           {"index.html", false},
		"requestNotFound": {"requestNotFound.html", false},
		// standalone templates
		"confirmationMail": {"confirmationMail", true},
		"officeHourTable":  {"officeHourTable.html", true},
		"td":               {"td.html", true},
	}

	// parse templates and add to global mapping
	for key, tmpl := range toParse {
		if _, exists := templates[key]; exists {
			return fmt.Errorf("template '%s' already parsed", key)
		}
		fullName := templateDir + tmpl.filename
		// check that template file
		info, err := os.Stat(fullName)
		if err != nil {
			return fmt.Errorf("adding template %s failed:\n%w", tmpl.filename, err)
		}
		if info.IsDir() {
			return fmt.Errorf("adding template %s failed: is a directory", tmpl.filename)
		}
		// parse
		var parsed *template.Template
		if tmpl.standalone {
			parsed, err = emptyTemplate.Clone()
			parsed = parsed.New(tmpl.filename)
		} else {
			parsed, err = baseTemplate.Clone()
		}
		if err != nil {
			return fmt.Errorf("cloning base template failed:\n%w", err)
		}
		parsed, err = parsed.ParseFiles(fullName)
		if err != nil {
			return fmt.Errorf("parsing template %s failed:\n%w", tmpl.filename, err)
		}
		templates[key] = parsed
	}
	return nil
}

// Execute a template and write it to the given writer.
//
// Parameters:
//   - name: name of the template to execute
//   - data: passed through to the template
func WriteTemplate(w io.Writer, name string, data any) error {
	tmpl, exists := templates[name]
	if !exists {
		return fmt.Errorf("template %s not available", name)
	}
	tmpl, err := tmpl.Clone()
	if err != nil {
		return fmt.Errorf("cloning template failed:\n%w", err)
	}
	err = tmpl.Execute(w, data)
	if err != nil {
		return fmt.Errorf("template execution failed:\n%w", err)
	}
	return nil
}

// Execute a template and write it to a http writer.
//
// Similar to WriteTemplate, but in error case this adds a corresponding
// status code and writes an error message to the writer.
//
// Typically, this is the final action in handling an http request.
//
// Parameters:
//   - name: name of the template to execute
//   - data: passed through to the template
func ServeTemplate(w http.ResponseWriter, name string, data any) {
	// TODO: make this return an error, handle on top of every request handler
	err := WriteTemplate(w, name, data)
	if err != nil {
		err = fmt.Errorf("writing template failed:\n%w", err)
		log.Println(err.Error())
		w.WriteHeader(http.StatusInternalServerError)
		io.WriteString(w, `Internal Server Error.

Du kannst uns helfen, indem du folgende Fehlermeldung per Mail
an sprechstunden@mathebau.de sendest:

Fehler um\n
`+time.Now().String()+"\n"+err.Error())
		return
	}
}
