package templating
import (
	"fmt"
	"html/template"
	"io"
	"io/fs"
	"reflect"
	"strings"
	"sync"
	"github.com/Theodor-Springmann-Stiftung/kgpz_web/functions"
	"github.com/Theodor-Springmann-Stiftung/kgpz_web/helpers"
	"github.com/Theodor-Springmann-Stiftung/kgpz_web/helpers/templatefunctions"
	"github.com/Theodor-Springmann-Stiftung/kgpz_web/views"
	"github.com/gofiber/fiber/v2"
	"github.com/gofiber/fiber/v2/middleware/compress"
	"github.com/gofiber/fiber/v2/middleware/etag"
)
const (
	ASSETS_URL_PREFIX = "/assets"
	CLEAR_LAYOUT      = `{{ block "head" . }}{{ end }}{{ block "body" . }}{{ end }}`
)
type Engine struct {
	// NOTE: LayoutRegistry and TemplateRegistry have their own syncronization & cache and do not require a mutex here
	LayoutRegistry   *LayoutRegistry
	TemplateRegistry *TemplateRegistry
	mu         *sync.Mutex
	FuncMap    template.FuncMap
	GlobalData fiber.Map
}
// INFO: We pass the app here to be able to access the config and other data for functions
// which also means we must reload the engine if the app changes
func NewEngine(layouts, templates *fs.FS) *Engine {
	e := Engine{
		mu:               &sync.Mutex{},
		LayoutRegistry:   NewLayoutRegistry(*layouts),
		TemplateRegistry: NewTemplateRegistry(*templates),
		FuncMap:          make(template.FuncMap),
	}
	e.funcs()
	return &e
}
// PageIcon renders the appropriate icon HTML for a page based on its icon type
func PageIcon(iconType string) template.HTML {
	switch iconType {
	case "first":
		return template.HTML(``)
	case "last":
		return template.HTML(``)
	case "even":
		return template.HTML(``)
	case "odd":
		return template.HTML(``)
	case "single":
		return template.HTML(``)
	default:
		return template.HTML(``)
	}
}
// GetPieceURL generates a piece view URL from piece ID
func GetPieceURL(pieceID string) string {
	return "/beitrag/" + pieceID
}
// IssueContext formats an issue reference into a readable context string
func IssueContext(issueRef interface{}) string {
	// Handle both direct IssueRef and map formats
	switch ref := issueRef.(type) {
	case map[string]interface{}:
		if year, ok := ref["year"].(int); ok {
			if nr, ok := ref["nr"].(int); ok {
				return fmt.Sprintf("%d Nr. %d", year, nr)
			}
		}
		return "Unbekannte Ausgabe"
	default:
		return "Unbekannte Ausgabe"
	}
}
// shouldSwap determines if two pieces should be swapped for chronological ordering
func shouldSwap(item1, item2 interface{}) bool {
	// Use reflection to access IssueRefs field
	v1 := reflect.ValueOf(item1)
	v2 := reflect.ValueOf(item2)
	if v1.Kind() == reflect.Ptr {
		v1 = v1.Elem()
	}
	if v2.Kind() == reflect.Ptr {
		v2 = v2.Elem()
	}
	if v1.Kind() != reflect.Struct || v2.Kind() != reflect.Struct {
		return false
	}
	refs1 := v1.FieldByName("IssueRefs")
	refs2 := v2.FieldByName("IssueRefs")
	if !refs1.IsValid() || !refs2.IsValid() || refs1.Len() == 0 || refs2.Len() == 0 {
		return false
	}
	// Get first IssueRef for each piece
	ref1 := refs1.Index(0)
	ref2 := refs2.Index(0)
	// Get year
	when1 := ref1.FieldByName("When")
	when2 := ref2.FieldByName("When")
	if !when1.IsValid() || !when2.IsValid() {
		return false
	}
	year1 := when1.FieldByName("Year")
	year2 := when2.FieldByName("Year")
	if !year1.IsValid() || !year2.IsValid() {
		return false
	}
	y1 := int(year1.Int())
	y2 := int(year2.Int())
	if y1 != y2 {
		return y1 > y2 // Sort by year ascending
	}
	// If same year, sort by issue number
	nr1 := ref1.FieldByName("Nr")
	nr2 := ref2.FieldByName("Nr")
	if !nr1.IsValid() || !nr2.IsValid() {
		return false
	}
	n1 := int(nr1.Int())
	n2 := int(nr2.Int())
	if n1 != n2 {
		return n1 > n2 // Sort by issue number ascending
	}
	// If same issue, sort by order
	order1 := ref1.FieldByName("Order")
	order2 := ref2.FieldByName("Order")
	if !order1.IsValid() || !order2.IsValid() {
		return false
	}
	o1 := int(order1.Int())
	o2 := int(order2.Int())
	return o1 > o2 // Sort by order ascending
}
// extractItem extracts the Item field from a Resolved struct
func extractItem(piece interface{}) interface{} {
	v := reflect.ValueOf(piece)
	if v.Kind() == reflect.Ptr {
		v = v.Elem()
	}
	if v.Kind() != reflect.Struct {
		return nil
	}
	itemField := v.FieldByName("Item")
	if !itemField.IsValid() {
		return nil
	}
	return itemField.Interface()
}
func (e *Engine) funcs() error {
	e.mu.Lock()
	e.mu.Unlock()
	// Dates
	e.AddFunc("MonthName", functions.MonthName)
	e.AddFunc("WeekdayName", functions.WeekdayName)
	e.AddFunc("HRDateShort", functions.HRDateShort)
	e.AddFunc("HRDateYear", functions.HRDateYear)
	// Math
	e.AddFunc("sub", func(a, b int) int { return a - b })
	e.AddFunc("add", func(a, b int) int { return a + b })
	e.AddFunc("mod", func(a, b int) int { return a % b })
	e.AddFunc("seq", func(start, end int) []int {
		if start > end {
			return []int{}
		}
		result := make([]int, end-start+1)
		for i := range result {
			result[i] = start + i
		}
		return result
	})
	// Template helpers
	e.AddFunc("dict", func(values ...interface{}) (map[string]interface{}, error) {
		if len(values)%2 != 0 {
			return nil, fmt.Errorf("dict requires an even number of arguments")
		}
		dict := make(map[string]interface{})
		for i := 0; i < len(values); i += 2 {
			key, ok := values[i].(string)
			if !ok {
				return nil, fmt.Errorf("dict keys must be strings")
			}
			dict[key] = values[i+1]
		}
		return dict, nil
	})
	e.AddFunc("merge", func(dest map[string]interface{}, src map[string]interface{}) map[string]interface{} {
		result := make(map[string]interface{})
		// Copy from dest first
		for k, v := range dest {
			result[k] = v
		}
		// Override with src values
		for k, v := range src {
			result[k] = v
		}
		return result
	})
	e.AddFunc("append", func(slice interface{}, item interface{}) interface{} {
		v := reflect.ValueOf(slice)
		if v.Kind() != reflect.Slice {
			return slice
		}
		newSlice := reflect.Append(v, reflect.ValueOf(item))
		return newSlice.Interface()
	})
	e.AddFunc("slice", func(items ...interface{}) []interface{} {
		return items
	})
	e.AddFunc("keys", func(m map[string]interface{}) []string {
		keys := make([]string, 0, len(m))
		for k := range m {
			keys = append(keys, k)
		}
		return keys
	})
	e.AddFunc("has", func(slice interface{}, item interface{}) bool {
		v := reflect.ValueOf(slice)
		if v.Kind() != reflect.Slice {
			return false
		}
		for i := 0; i < v.Len(); i++ {
			if reflect.DeepEqual(v.Index(i).Interface(), item) {
				return true
			}
		}
		return false
	})
	e.AddFunc("joinWithUnd", func(items []string) string {
		if len(items) == 0 {
			return ""
		}
		if len(items) == 1 {
			return items[0]
		}
		if len(items) == 2 {
			return items[0] + " und " + items[1]
		}
		// For 3+ items: "A, B und C"
		result := ""
		for i, item := range items {
			if i == 0 {
				result = item
			} else if i == len(items)-1 {
				result += " und " + item
			} else {
				result += ", " + item
			}
		}
		return result
	})
	e.AddFunc("split", func(s, delimiter string) []string {
		if s == "" {
			return []string{}
		}
		return strings.Split(s, delimiter)
	})
	e.AddFunc("sortStrings", func(items interface{}) []string {
		v := reflect.ValueOf(items)
		if v.Kind() != reflect.Slice {
			return []string{}
		}
		// Convert to string slice
		result := make([]string, v.Len())
		for i := 0; i < v.Len(); i++ {
			if str, ok := v.Index(i).Interface().(string); ok {
				result[i] = str
			}
		}
		// Simple bubble sort
		for i := 0; i < len(result)-1; i++ {
			for j := i + 1; j < len(result); j++ {
				if result[i] > result[j] {
					result[i], result[j] = result[j], result[i]
				}
			}
		}
		return result
	})
	e.AddFunc("unique", func(items interface{}) []string {
		v := reflect.ValueOf(items)
		if v.Kind() != reflect.Slice {
			return []string{}
		}
		seen := make(map[string]bool)
		result := []string{}
		for i := 0; i < v.Len(); i++ {
			if str, ok := v.Index(i).Interface().(string); ok {
				if !seen[str] {
					seen[str] = true
					result = append(result, str)
				}
			}
		}
		return result
	})
	// Strings
	e.AddFunc("FirstLetter", functions.FirstLetter)
	e.AddFunc("Upper", strings.ToUpper)
	e.AddFunc("Lower", strings.ToLower)
	e.AddFunc("Safe", functions.Safe)
	// Sorting
	e.AddFunc("SortPiecesByDate", func(pieces interface{}) interface{} {
		// Use reflection to handle any slice type
		v := reflect.ValueOf(pieces)
		if v.Kind() != reflect.Slice {
			return pieces
		}
		length := v.Len()
		if length == 0 {
			return pieces
		}
		// Create indices for sorting
		indices := make([]int, length)
		for i := range indices {
			indices[i] = i
		}
		// Sort indices based on piece comparison
		for i := 0; i < len(indices)-1; i++ {
			for j := i + 1; j < len(indices); j++ {
				piece1 := v.Index(indices[i]).Interface()
				piece2 := v.Index(indices[j]).Interface()
				// Extract the Item field from each resolved piece
				item1 := extractItem(piece1)
				item2 := extractItem(piece2)
				if item1 != nil && item2 != nil && shouldSwap(item1, item2) {
					indices[i], indices[j] = indices[j], indices[i]
				}
			}
		}
		// Create sorted slice with same type as input
		sortedSlice := reflect.MakeSlice(v.Type(), length, length)
		for i, idx := range indices {
			sortedSlice.Index(i).Set(v.Index(idx))
		}
		return sortedSlice.Interface()
	})
	// Embedding of file contents
	embedder := functions.NewEmbedder(views.StaticFS)
	e.AddFunc("EmbedSafe", embedder.EmbedSafe())
	e.AddFunc("Embed", embedder.Embed())
	e.AddFunc("EmbedXSLT", embedder.EmbedXSLT())
	// Page icons for ausgabe templates
	e.AddFunc("PageIcon", PageIcon)
	// Piece view helpers
	e.AddFunc("GetPieceURL", GetPieceURL)
	e.AddFunc("IssueContext", IssueContext)
	e.AddFunc("GetCategoryFlags", templatefunctions.GetCategoryFlags)
	return nil
}
func (e Engine) Pre(srv *fiber.App) error {
	srv.Use(ASSETS_URL_PREFIX, compress.New(compress.Config{
		Level: compress.LevelBestSpeed,
	}), etag.New(), helpers.StaticHandler(&views.StaticFS))
	return nil
}
func (e *Engine) Globals(data fiber.Map) {
	e.mu.Lock()
	defer e.mu.Unlock()
	if e.GlobalData == nil {
		e.GlobalData = data
	} else {
		for k, v := range data {
			(e.GlobalData)[k] = v
		}
	}
}
// UpdateGitGlobals updates the git-related global data
func (e *Engine) UpdateGitGlobals(commit, date, url string) {
	e.mu.Lock()
	defer e.mu.Unlock()
	if e.GlobalData == nil {
		e.GlobalData = make(fiber.Map)
	}
	e.GlobalData["gitCommit"] = commit
	e.GlobalData["gitDate"] = date
	e.GlobalData["gitURL"] = url
}
func (e *Engine) Load() error {
	wg := sync.WaitGroup{}
	wg.Add(2)
	go func() {
		defer wg.Done()
		e.LayoutRegistry.Load()
	}()
	go func() {
		defer wg.Done()
		e.TemplateRegistry.Load()
	}()
	wg.Wait()
	return nil
}
func (e *Engine) Reload() error {
	wg := sync.WaitGroup{}
	wg.Add(2)
	go func() {
		defer wg.Done()
		e.LayoutRegistry.Reset()
	}()
	go func() {
		defer wg.Done()
		e.TemplateRegistry.Reset()
	}()
	wg.Wait()
	return nil
}
// INFO: fn is a function that returns either one value or two values, the second one being an error
func (e *Engine) AddFunc(name string, fn interface{}) {
	e.mu.Lock()
	defer e.mu.Unlock()
	e.FuncMap[name] = fn
}
func (e *Engine) AddFuncs(funcs map[string]interface{}) {
	e.mu.Lock()
	defer e.mu.Unlock()
	for k, v := range funcs {
		e.FuncMap[k] = v
	}
}
func (e *Engine) Render(out io.Writer, path string, data interface{}, layout ...string) error {
	// TODO: check if a reload is needed if files on disk have changed
	ld := data.(fiber.Map)
	gd := e.GlobalData
	if e.GlobalData != nil {
		for k, v := range ld {
			gd[k] = v
		}
	}
	e.mu.Lock()
	defer e.mu.Unlock()
	var l *template.Template
	if len(layout) == 0 {
		lay, err := e.LayoutRegistry.Default(&e.FuncMap)
		if err != nil {
			return err
		}
		l = lay
	} else {
		if layout[0] == "clear" {
			lay, err := template.New("clear").Funcs(e.FuncMap).Parse(CLEAR_LAYOUT)
			if err != nil {
				return err
			}
			l = lay
		} else {
			lay, err := e.LayoutRegistry.Layout(layout[0], &e.FuncMap)
			if err != nil {
				return err
			}
			l = lay
		}
	}
	lay, err := l.Clone()
	if err != nil {
		return err
	}
	err = e.TemplateRegistry.Add(path, lay, &e.FuncMap)
	if err != nil {
		return err
	}
	err = lay.Execute(out, gd)
	if err != nil {
		return err
	}
	return nil
}