package template

import (
	"fmt"
	"maps"
	"reflect"
	"strconv"
	"strings"
	"sync"
	"time"

	sushi "git.sharkk.net/Sharkk/Sushi"
)

type Template struct {
	name     string
	content  string
	modTime  time.Time
	filePath string
	cache    *TemplateCache
}

type TemplateFunc func(args ...any) any

var (
	funcRegistry = make(map[string]TemplateFunc)
	funcMutex    sync.RWMutex
)

func init() {
	// Built-in functions
	RegisterFunc("upper", func(args ...any) any {
		if len(args) == 0 {
			return ""
		}
		return strings.ToUpper(fmt.Sprintf("%v", args[0]))
	})

	RegisterFunc("lower", func(args ...any) any {
		if len(args) == 0 {
			return ""
		}
		return strings.ToLower(fmt.Sprintf("%v", args[0]))
	})

	RegisterFunc("len", func(args ...any) any {
		if len(args) == 0 {
			return 0
		}
		rv := reflect.ValueOf(args[0])
		switch rv.Kind() {
		case reflect.Slice, reflect.Array, reflect.Map, reflect.String:
			return rv.Len()
		default:
			return 0
		}
	})
}

func RegisterFunc(name string, fn TemplateFunc) {
	funcMutex.Lock()
	defer funcMutex.Unlock()
	funcRegistry[name] = fn
}

func (t *Template) RenderPositional(args ...any) string {
	result := t.content
	for i, arg := range args {
		result = strings.ReplaceAll(result, fmt.Sprintf("{%d}", i), fmt.Sprintf("%v", arg))
	}
	return result
}

func (t *Template) RenderNamed(data map[string]any) string {
	if data == nil {
		data = make(map[string]any)
	}

	blocks := make(map[string]string)
	result := t.processBlocks(t.content, blocks)
	result = t.processIncludes(result, data, blocks)
	result = t.processYields(result, blocks, data)
	result = t.processLoops(result, data)
	result = t.processConditionals(result, data)
	result = t.processVariables(result, data)

	return result
}

func (t *Template) Render(data any) string {
	switch v := data.(type) {
	case map[string]any:
		return t.RenderNamed(v)
	case []any:
		return t.RenderPositional(v...)
	default:
		rv := reflect.ValueOf(data)
		if rv.Kind() == reflect.Slice {
			args := make([]any, rv.Len())
			for i := 0; i < rv.Len(); i++ {
				args[i] = rv.Index(i).Interface()
			}
			return t.RenderPositional(args...)
		} else {
			return t.RenderPositional(data)
		}
	}
}

func (t *Template) WriteTo(ctx sushi.Ctx, data any) error {
	result := t.Render(data)
	ctx.SendHTML(result)
	return nil
}

func (t *Template) processBlocks(content string, blocks map[string]string) string {
	result := content

	for {
		start := strings.Index(result, "{block ")
		if start == -1 {
			break
		}

		nameEnd := strings.Index(result[start:], "}")
		if nameEnd == -1 {
			break
		}
		nameEnd += start

		blockName := strings.Trim(result[start+7:nameEnd], "\" ")
		contentStart := nameEnd + 1

		contentEnd := t.findMatchingEnd(result[contentStart:], "{block", "{/block}")
		if contentEnd == -1 {
			break
		}
		contentEnd += contentStart

		blocks[blockName] = result[contentStart:contentEnd]
		result = result[:start] + result[contentEnd+8:] // +8 for "{/block}"
	}

	return result
}

func (t *Template) processIncludes(content string, data map[string]any, blocks map[string]string) string {
	result := content

	for {
		start := strings.Index(result, "{include ")
		if start == -1 {
			break
		}

		end := strings.Index(result[start:], "}")
		if end == -1 {
			break
		}
		end += start

		templateName := strings.Trim(result[start+9:end], "\" ")

		if tmpl, err := t.cache.Load(templateName); err == nil {
			// Process included template with same blocks context
			included := tmpl.processBlocks(tmpl.content, blocks)
			included = tmpl.processIncludes(included, data, blocks)
			included = tmpl.processYields(included, blocks, data)
			included = tmpl.processLoops(included, data)
			included = tmpl.processConditionals(included, data)
			included = tmpl.processVariables(included, data)

			result = result[:start] + included + result[end+1:]
		} else {
			result = result[:start] + result[end+1:]
		}
	}

	return result
}

func (t *Template) processYields(content string, blocks map[string]string, data map[string]any) string {
	result := content

	// Process defined blocks
	for blockName, blockContent := range blocks {
		processed := t.processLoops(blockContent, data)
		processed = t.processConditionals(processed, data)
		processed = t.processVariables(processed, data)

		result = strings.ReplaceAll(result, fmt.Sprintf(`{yield "%s"}`, blockName), processed)
	}

	// Remove unused named yields
	start := 0
	for {
		yieldStart := strings.Index(result[start:], "{yield ")
		if yieldStart == -1 {
			break
		}
		yieldStart += start

		yieldEnd := strings.Index(result[yieldStart:], "}")
		if yieldEnd == -1 {
			break
		}
		yieldEnd += yieldStart

		result = result[:yieldStart] + result[yieldEnd+1:]
		start = yieldStart
	}

	// Remove any remaining unnamed yields
	result = strings.ReplaceAll(result, "{yield}", "")

	return result
}

func (t *Template) processLoops(content string, data map[string]any) string {
	result := content

	for {
		start := strings.Index(result, "{for ")
		if start == -1 {
			break
		}

		headerEnd := strings.Index(result[start:], "}")
		if headerEnd == -1 {
			break
		}
		headerEnd += start

		header := result[start+5 : headerEnd]
		contentStart := headerEnd + 1

		contentEnd := t.findMatchingEnd(result[contentStart:], "{for", "{/for}")
		if contentEnd == -1 {
			break
		}
		contentEnd += contentStart

		loopContent := result[contentStart:contentEnd]
		expanded := t.expandLoop(header, loopContent, data)

		result = result[:start] + expanded + result[contentEnd+6:] // +6 for "{/for}"
	}

	return result
}

func (t *Template) expandLoop(header, content string, data map[string]any) string {
	parts := strings.Split(strings.TrimSpace(header), " in ")
	if len(parts) != 2 {
		return ""
	}

	varPart := strings.TrimSpace(parts[0])
	sourcePart := strings.TrimSpace(parts[1])

	source := t.getNestedValue(data, sourcePart)
	if source == nil {
		return ""
	}

	var result strings.Builder
	rv := reflect.ValueOf(source)

	if strings.Contains(varPart, ",") {
		// Key,value iteration
		vars := strings.Split(varPart, ",")
		keyVar := strings.TrimSpace(vars[0])
		valueVar := strings.TrimSpace(vars[1])

		switch rv.Kind() {
		case reflect.Map:
			for _, key := range rv.MapKeys() {
				iterData := make(map[string]any)
				maps.Copy(iterData, data)
				iterData[keyVar] = key.Interface()
				iterData[valueVar] = rv.MapIndex(key).Interface()

				iterContent := t.processLoops(content, iterData)
				iterContent = t.processConditionals(iterContent, iterData)
				iterContent = t.processVariables(iterContent, iterData)
				result.WriteString(iterContent)
			}
		case reflect.Slice, reflect.Array:
			for i := 0; i < rv.Len(); i++ {
				iterData := make(map[string]any)
				maps.Copy(iterData, data)
				iterData[keyVar] = i
				iterData[valueVar] = rv.Index(i).Interface()

				iterContent := t.processLoops(content, iterData)
				iterContent = t.processConditionals(iterContent, iterData)
				iterContent = t.processVariables(iterContent, iterData)
				result.WriteString(iterContent)
			}
		}
	} else {
		// Single variable iteration
		switch rv.Kind() {
		case reflect.Slice, reflect.Array:
			for i := 0; i < rv.Len(); i++ {
				iterData := make(map[string]any)
				maps.Copy(iterData, data)
				iterData[varPart] = rv.Index(i).Interface()

				iterContent := t.processLoops(content, iterData)
				iterContent = t.processConditionals(iterContent, iterData)
				iterContent = t.processVariables(iterContent, iterData)
				result.WriteString(iterContent)
			}
		case reflect.Map:
			for _, key := range rv.MapKeys() {
				iterData := make(map[string]any)
				maps.Copy(iterData, data)
				iterData[varPart] = rv.MapIndex(key).Interface()

				iterContent := t.processLoops(content, iterData)
				iterContent = t.processConditionals(iterContent, iterData)
				iterContent = t.processVariables(iterContent, iterData)
				result.WriteString(iterContent)
			}
		}
	}

	return result.String()
}

func (t *Template) processConditionals(content string, data map[string]any) string {
	result := content

	for {
		start := strings.Index(result, "{if ")
		if start == -1 {
			break
		}

		headerEnd := strings.Index(result[start:], "}")
		if headerEnd == -1 {
			break
		}
		headerEnd += start

		condition := strings.TrimSpace(result[start+4 : headerEnd])
		contentStart := headerEnd + 1

		contentEnd := t.findMatchingEnd(result[contentStart:], "{if", "{/if}")
		if contentEnd == -1 {
			break
		}
		contentEnd += contentStart

		ifContent := result[contentStart:contentEnd]

		// Find else at top level
		elsePos := t.findElseAtLevel(ifContent)
		var trueContent, falseContent string

		if elsePos != -1 {
			trueContent = ifContent[:elsePos]
			falseContent = ifContent[elsePos+6:] // Skip "{else}"
		} else {
			trueContent = ifContent
		}

		var selectedContent string
		if t.evaluateCondition(condition, data) {
			selectedContent = trueContent
		} else {
			selectedContent = falseContent
		}

		// Process selected content recursively
		selectedContent = t.processLoops(selectedContent, data)
		selectedContent = t.processConditionals(selectedContent, data)

		result = result[:start] + selectedContent + result[contentEnd+5:] // +5 for "{/if}"
	}

	return result
}

func (t *Template) processVariables(content string, data map[string]any) string {
	result := content

	start := 0
	for {
		startIdx := strings.Index(result[start:], "{")
		if startIdx == -1 {
			break
		}
		startIdx += start

		endIdx := strings.Index(result[startIdx:], "}")
		if endIdx == -1 {
			break
		}
		endIdx += startIdx

		placeholder := result[startIdx+1 : endIdx]

		// Try to evaluate as expression
		value := t.evaluateExpression(placeholder, data)
		if value != nil {
			result = result[:startIdx] + fmt.Sprintf("%v", value) + result[endIdx+1:]
			start = startIdx + len(fmt.Sprintf("%v", value))
			continue
		}

		start = endIdx + 1
	}

	return result
}

// evaluateExpression handles expressions - tries arithmetic first, falls back to original logic
func (t *Template) evaluateExpression(expr string, data map[string]any) any {
	expr = strings.TrimSpace(expr)

	// Special handling for length operator
	if strings.HasPrefix(expr, "#") {
		return t.getLength(t.getNestedValue(data, expr[1:]))
	}

	// Check if it contains arithmetic operators (but not inside method calls)
	var foundOp string
	var opPos int = -1
	parenLevel := 0
	inQuotes := false

	// Scan for operators not inside parentheses or quotes
	for i, r := range expr {
		switch r {
		case '"':
			inQuotes = !inQuotes
		case '(':
			if !inQuotes {
				parenLevel++
			}
		case ')':
			if !inQuotes {
				parenLevel--
			}
		case '+', '-':
			if !inQuotes && parenLevel == 0 && i > 0 {
				foundOp = string(r)
				opPos = i
			}
		case '*', '/', '%':
			if !inQuotes && parenLevel == 0 && i > 0 && (opPos == -1 || (foundOp != "+" && foundOp != "-")) {
				foundOp = string(r)
				opPos = i
			}
		}
	}

	// If we found an arithmetic operator, evaluate it
	if opPos > 0 {
		left := strings.TrimSpace(expr[:opPos])
		right := strings.TrimSpace(expr[opPos+1:])

		leftVal := t.evaluateExpression(left, data)
		rightVal := t.evaluateExpression(right, data)

		return t.performArithmetic(leftVal, rightVal, foundOp)
	}

	// Handle parentheses (but only if they wrap the entire expression)
	if strings.HasPrefix(expr, "(") && strings.HasSuffix(expr, ")") && t.matchingParen(expr) == len(expr)-1 {
		return t.evaluateExpression(expr[1:len(expr)-1], data)
	}

	// Use original parsing logic for everything else
	return t.parseValue(expr, data)
}

// matchingParen finds the matching closing paren for the opening paren at position 0
func (t *Template) matchingParen(expr string) int {
	level := 0
	inQuotes := false
	for i, r := range expr {
		switch r {
		case '"':
			inQuotes = !inQuotes
		case '(':
			if !inQuotes {
				level++
			}
		case ')':
			if !inQuotes {
				level--
				if level == 0 {
					return i
				}
			}
		}
	}
	return -1
}

// parseValue uses the original parsing logic
func (t *Template) parseValue(expr string, data map[string]any) any {
	// String literal
	if strings.HasPrefix(expr, "\"") && strings.HasSuffix(expr, "\"") {
		return expr[1 : len(expr)-1]
	}

	// Number
	if num, err := strconv.ParseFloat(expr, 64); err == nil {
		if strings.Contains(expr, ".") {
			return num
		}
		return int(num)
	}

	// Boolean
	if expr == "true" {
		return true
	}
	if expr == "false" {
		return false
	}

	// Function call (simple function name only, no dots)
	if parenIdx := strings.Index(expr, "("); parenIdx != -1 && strings.HasSuffix(expr, ")") {
		funcName := expr[:parenIdx]
		if !strings.Contains(funcName, ".") {
			return t.callFunction(expr, data)
		}
	}

	// Method calls or dot notation
	if strings.Contains(expr, ".") {
		return t.getNestedValue(data, expr)
	}

	// Simple variable
	if value, ok := data[expr]; ok {
		return value
	}

	return expr
}

func (t *Template) performArithmetic(left, right any, op string) any {
	leftNum, leftOk := t.toFloat(left)
	rightNum, rightOk := t.toFloat(right)

	if !leftOk || !rightOk {
		return nil
	}

	switch op {
	case "+":
		result := leftNum + rightNum
		if t.isInteger(left) && t.isInteger(right) {
			return int(result)
		}
		return result
	case "-":
		result := leftNum - rightNum
		if t.isInteger(left) && t.isInteger(right) {
			return int(result)
		}
		return result
	case "*":
		result := leftNum * rightNum
		if t.isInteger(left) && t.isInteger(right) {
			return int(result)
		}
		return result
	case "/":
		if rightNum == 0 {
			return nil
		}
		return leftNum / rightNum
	case "%":
		if rightNum == 0 {
			return nil
		}
		if t.isInteger(left) && t.isInteger(right) {
			return int(leftNum) % int(rightNum)
		}
		return nil
	}

	return nil
}

func (t *Template) isInteger(value any) bool {
	switch value.(type) {
	case int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64:
		return true
	}
	return false
}

func (t *Template) callFunction(expr string, data map[string]any) any {
	parenIdx := strings.Index(expr, "(")
	funcName := strings.TrimSpace(expr[:parenIdx])
	argsStr := strings.TrimSpace(expr[parenIdx+1 : len(expr)-1])

	funcMutex.RLock()
	fn, exists := funcRegistry[funcName]
	funcMutex.RUnlock()

	if !exists {
		return nil
	}

	args := t.parseArgs(argsStr, data)
	return fn(args...)
}

func (t *Template) callMethod(obj any, methodCall string, data map[string]any) any {
	if obj == nil {
		return nil
	}

	parenIdx := strings.Index(methodCall, "(")
	methodName := methodCall[:parenIdx]
	argsStr := methodCall[parenIdx+1 : len(methodCall)-1]

	rv := reflect.ValueOf(obj)
	method := rv.MethodByName(methodName)
	if !method.IsValid() {
		return nil
	}

	args := t.parseArgs(argsStr, data)
	methodType := method.Type()

	// Convert arguments to match method signature
	reflectArgs := make([]reflect.Value, len(args))
	for i, arg := range args {
		if i < methodType.NumIn() {
			reflectArgs[i] = t.convertArg(arg, methodType.In(i))
		} else {
			reflectArgs[i] = reflect.ValueOf(arg)
		}
	}

	// Handle variadic methods
	if methodType.IsVariadic() && len(reflectArgs) >= methodType.NumIn()-1 {
		result := method.CallSlice(reflectArgs)
		if len(result) > 0 {
			return result[0].Interface()
		}
	} else if len(reflectArgs) == methodType.NumIn() {
		result := method.Call(reflectArgs)
		if len(result) > 0 {
			return result[0].Interface()
		}
	}

	return nil
}

func (t *Template) parseArgs(argsStr string, data map[string]any) []any {
	if argsStr == "" {
		return nil
	}

	args := []any{}
	current := ""
	inQuotes := false
	parenLevel := 0

	for _, r := range argsStr {
		switch r {
		case '"':
			inQuotes = !inQuotes
			current += string(r)
		case '(':
			parenLevel++
			current += string(r)
		case ')':
			parenLevel--
			current += string(r)
		case ',':
			if !inQuotes && parenLevel == 0 {
				args = append(args, t.evaluateExpression(strings.TrimSpace(current), data))
				current = ""
				continue
			}
			current += string(r)
		default:
			current += string(r)
		}
	}

	if current != "" {
		args = append(args, t.evaluateExpression(strings.TrimSpace(current), data))
	}

	return args
}

func (t *Template) convertArg(arg any, targetType reflect.Type) reflect.Value {
	argValue := reflect.ValueOf(arg)

	if argValue.Type().ConvertibleTo(targetType) {
		return argValue.Convert(targetType)
	}

	return argValue
}

func (t *Template) findMatchingEnd(content, startTag, endTag string) int {
	level := 1
	pos := 0

	for pos < len(content) && level > 0 {
		nextStart := strings.Index(content[pos:], startTag)
		nextEnd := strings.Index(content[pos:], endTag)

		if nextStart != -1 && (nextEnd == -1 || nextStart < nextEnd) {
			level++
			pos += nextStart + len(startTag)
		} else if nextEnd != -1 {
			level--
			if level == 0 {
				return pos + nextEnd
			}
			pos += nextEnd + len(endTag)
		} else {
			break
		}
	}

	return -1
}

func (t *Template) findElseAtLevel(content string) int {
	level := 0
	pos := 0

	for pos < len(content) {
		nextIf := strings.Index(content[pos:], "{if ")
		nextElse := strings.Index(content[pos:], "{else}")
		nextEnd := strings.Index(content[pos:], "{/if}")

		earliest := -1
		var tag string

		if nextIf != -1 && (earliest == -1 || nextIf < earliest-pos) {
			earliest = pos + nextIf
			tag = "if"
		}
		if nextElse != -1 && (earliest == -1 || nextElse < earliest-pos) {
			earliest = pos + nextElse
			tag = "else"
		}
		if nextEnd != -1 && (earliest == -1 || nextEnd < earliest-pos) {
			earliest = pos + nextEnd
			tag = "end"
		}

		if earliest == -1 {
			break
		}

		switch tag {
		case "if":
			level++
			pos = earliest + 4
		case "else":
			if level == 0 {
				return earliest
			}
			pos = earliest + 6
		case "end":
			level--
			pos = earliest + 5
		}
	}

	return -1
}

func (t *Template) getNestedValue(data map[string]any, path string) any {
	keys := strings.Split(path, ".")
	var current any = data

	for i, key := range keys {
		// Check for method call at any point in the chain
		if strings.Contains(key, "(") && strings.HasSuffix(key, ")") {
			result := t.callMethod(current, key, data)
			if i == len(keys)-1 {
				// This was the final key, return the method result
				return result
			}
			// Continue with the method result for further chaining
			current = t.convertToStringMap(result)
			continue
		}

		if i == len(keys)-1 {
			// Final key - get field/value
			switch v := current.(type) {
			case map[string]any:
				return v[key]
			default:
				return t.getStructField(current, key)
			}
		}

		// Intermediate key - get next object in chain
		var next any
		switch v := current.(type) {
		case map[string]any:
			var ok bool
			next, ok = v[key]
			if !ok {
				return nil
			}
		default:
			next = t.getStructField(current, key)
			if next == nil {
				return nil
			}
		}

		current = t.convertToStringMap(next)
	}

	return nil
}

func (t *Template) getStructField(obj any, fieldName string) any {
	if obj == nil {
		return nil
	}

	rv := reflect.ValueOf(obj)
	if rv.Kind() == reflect.Pointer {
		if rv.IsNil() {
			return nil
		}
		rv = rv.Elem()
	}

	if rv.Kind() != reflect.Struct {
		return nil
	}

	field := rv.FieldByName(fieldName)
	if !field.IsValid() {
		return nil
	}

	return field.Interface()
}

func (t *Template) convertToStringMap(value any) any {
	switch v := value.(type) {
	case map[string]any:
		return v
	case map[any]any:
		newMap := make(map[string]any)
		for k, val := range v {
			newMap[fmt.Sprintf("%v", k)] = val
		}
		return newMap
	default:
		rv := reflect.ValueOf(value)
		if rv.Kind() == reflect.Map {
			newMap := make(map[string]any)
			for _, k := range rv.MapKeys() {
				newMap[fmt.Sprintf("%v", k.Interface())] = rv.MapIndex(k).Interface()
			}
			return newMap
		}
		return value
	}
}

func (t *Template) evaluateCondition(condition string, data map[string]any) bool {
	condition = strings.TrimSpace(condition)

	if strings.Contains(condition, " or ") {
		for _, part := range strings.Split(condition, " or ") {
			if t.evaluateCondition(strings.TrimSpace(part), data) {
				return true
			}
		}
		return false
	}

	if strings.Contains(condition, " and ") {
		for _, part := range strings.Split(condition, " and ") {
			if !t.evaluateCondition(strings.TrimSpace(part), data) {
				return false
			}
		}
		return true
	}

	for _, op := range []string{">=", "<=", "!=", "==", ">", "<"} {
		if strings.Contains(condition, op) {
			parts := strings.Split(condition, op)
			if len(parts) == 2 {
				left := strings.TrimSpace(parts[0])
				right := strings.TrimSpace(parts[1])
				return t.compareValues(t.evaluateExpression(left, data), t.evaluateExpression(right, data), op)
			}
		}
	}

	return t.isTruthy(t.evaluateExpression(condition, data))
}

func (t *Template) compareValues(left, right any, op string) bool {
	switch op {
	case "==":
		return fmt.Sprintf("%v", left) == fmt.Sprintf("%v", right)
	case "!=":
		return fmt.Sprintf("%v", left) != fmt.Sprintf("%v", right)
	case ">", ">=", "<", "<=":
		leftNum, leftOk := t.toFloat(left)
		rightNum, rightOk := t.toFloat(right)
		if !leftOk || !rightOk {
			return false
		}
		switch op {
		case ">":
			return leftNum > rightNum
		case ">=":
			return leftNum >= rightNum
		case "<":
			return leftNum < rightNum
		case "<=":
			return leftNum <= rightNum
		}
	}
	return false
}

func (t *Template) toFloat(value any) (float64, bool) {
	switch v := value.(type) {
	case int:
		return float64(v), true
	case int64:
		return float64(v), true
	case float32:
		return float64(v), true
	case float64:
		return v, true
	case string:
		if f, err := strconv.ParseFloat(v, 64); err == nil {
			return f, true
		}
	}
	return 0, false
}

func (t *Template) isTruthy(value any) bool {
	if value == nil {
		return false
	}

	switch v := value.(type) {
	case bool:
		return v
	case int:
		return v != 0
	case float64:
		return v != 0
	case string:
		return v != ""
	default:
		rv := reflect.ValueOf(value)
		switch rv.Kind() {
		case reflect.Slice, reflect.Array, reflect.Map:
			return rv.Len() > 0
		case reflect.Pointer:
			return !rv.IsNil()
		}
		return true
	}
}

func (t *Template) getLength(value any) int {
	if value == nil {
		return 0
	}

	rv := reflect.ValueOf(value)
	switch rv.Kind() {
	case reflect.Slice, reflect.Array, reflect.Map, reflect.String:
		return rv.Len()
	default:
		return 0
	}
}
