package template

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

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

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

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

	// Process simple variables
	for key, value := range data {
		result = strings.ReplaceAll(result, fmt.Sprintf("{%s}", key), fmt.Sprintf("%v", value))
	}

	// Process dot notation
	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]

		if strings.Contains(placeholder, ".") {
			value := t.getNestedValue(data, placeholder)
			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
}

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 {
		if i == len(keys)-1 {
			switch v := current.(type) {
			case map[string]any:
				return v[key]
			default:
				return t.getStructField(current, key)
			}
		}

		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.Ptr {
		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.getConditionValue(left, data), t.getConditionValue(right, data), op)
			}
		}
	}

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

func (t *Template) getConditionValue(expr string, data map[string]any) any {
	expr = strings.TrimSpace(expr)

	if strings.HasPrefix(expr, "#") {
		return t.getLength(t.getNestedValue(data, expr[1:]))
	}

	if num, err := strconv.ParseFloat(expr, 64); err == nil {
		return num
	}

	if strings.HasPrefix(expr, "\"") && strings.HasSuffix(expr, "\"") {
		return expr[1 : len(expr)-1]
	}

	if strings.Contains(expr, ".") {
		return t.getNestedValue(data, expr)
	}

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

	return expr
}

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.Ptr:
			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
	}
}
