package templates

import (
	"testing"
	"time"

	"github.com/stretchr/testify/assert"
)

func TestNewTemplateEngine(t *testing.T) {
	engine := NewTemplateEngine(50)
	assert.NotNil(t, engine)
	assert.Equal(t, 50, engine.maxSize)
	assert.Equal(t, 0, engine.CacheSize())
}

func TestCompile(t *testing.T) {
	engine := NewTemplateEngine(50)

	tmpl, err := engine.Compile("test", "Hello {{.Name}}!")
	assert.NoError(t, err)
	assert.NotNil(t, tmpl)
	assert.True(t, engine.IsCached("test"))
}

func TestCompileDuplicate(t *testing.T) {
	engine := NewTemplateEngine(50)

	tmpl1, _ := engine.Compile("test", "Hello {{.Name}}!")
	tmpl2, _ := engine.Compile("test", "Hello {{.Name}}!")

	// Should return the same cached template
	assert.Equal(t, tmpl1, tmpl2)
	assert.Equal(t, 1, engine.CacheSize())
}

func TestRender(t *testing.T) {
	engine := NewTemplateEngine(50)

	engine.Compile("test", "Hello {{.Name}}!")
	result, err := engine.Render("test", map[string]string{"Name": "World"})

	assert.NoError(t, err)
	assert.Equal(t, "Hello World!", result)
}

func TestRenderNotFound(t *testing.T) {
	engine := NewTemplateEngine(50)

	_, err := engine.Render("nonexistent", map[string]string{})
	assert.Error(t, err)
}

func TestCompileAndRender(t *testing.T) {
	engine := NewTemplateEngine(50)

	result, err := engine.CompileAndRender("test", "{{.X}} + {{.Y}} = {{.Z}}", map[string]int{
		"X": 2,
		"Y": 3,
		"Z": 5,
	})

	assert.NoError(t, err)
	assert.Equal(t, "2 + 3 = 5", result)
}

func TestRenderMultipleTimes(t *testing.T) {
	engine := NewTemplateEngine(50)

	engine.Compile("test", "Count: {{.}}")

	result1, _ := engine.Render("test", 1)
	result2, _ := engine.Render("test", 2)
	result3, _ := engine.Render("test", 3)

	assert.Equal(t, "Count: 1", result1)
	assert.Equal(t, "Count: 2", result2)
	assert.Equal(t, "Count: 3", result3)

	stats, _ := engine.GetStats("test")
	assert.Equal(t, 3, stats.RenderCount)
}

func TestGetStats(t *testing.T) {
	engine := NewTemplateEngine(50)

	engine.Compile("test", "Hello")
	stats, exists := engine.GetStats("test")

	assert.True(t, exists)
	assert.NotNil(t, stats)
	assert.Equal(t, "test", stats.TemplateName)
	assert.NotZero(t, stats.CompileTime)
}

func TestGetAllStats(t *testing.T) {
	engine := NewTemplateEngine(50)

	engine.Compile("test1", "Template 1")
	engine.Compile("test2", "Template 2")
	engine.Compile("test3", "Template 3")

	allStats := engine.GetAllStats()
	assert.Equal(t, 3, len(allStats))
	assert.NotNil(t, allStats["test1"])
	assert.NotNil(t, allStats["test2"])
	assert.NotNil(t, allStats["test3"])
}

func TestClear(t *testing.T) {
	engine := NewTemplateEngine(50)

	engine.Compile("test1", "Template 1")
	engine.Compile("test2", "Template 2")
	assert.Equal(t, 2, engine.CacheSize())

	engine.Clear()
	assert.Equal(t, 0, engine.CacheSize())
	assert.False(t, engine.IsCached("test1"))
}

func TestRemove(t *testing.T) {
	engine := NewTemplateEngine(50)

	engine.Compile("test1", "Template 1")
	engine.Compile("test2", "Template 2")
	assert.Equal(t, 2, engine.CacheSize())

	engine.Remove("test1")
	assert.Equal(t, 1, engine.CacheSize())
	assert.False(t, engine.IsCached("test1"))
	assert.True(t, engine.IsCached("test2"))
}

func TestCacheEviction(t *testing.T) {
	engine := NewTemplateEngine(3)

	engine.Compile("test1", "Template 1")
	engine.Compile("test2", "Template 2")
	engine.Compile("test3", "Template 3")
	assert.Equal(t, 3, engine.CacheSize())

	// Adding a 4th template should evict the oldest (test1)
	time.Sleep(10 * time.Millisecond)
	engine.Compile("test4", "Template 4")

	assert.Equal(t, 3, engine.CacheSize())
	assert.False(t, engine.IsCached("test1"))
	assert.True(t, engine.IsCached("test2"))
	assert.True(t, engine.IsCached("test3"))
	assert.True(t, engine.IsCached("test4"))
}

func TestIsCached(t *testing.T) {
	engine := NewTemplateEngine(50)

	assert.False(t, engine.IsCached("test"))

	engine.Compile("test", "Template")

	assert.True(t, engine.IsCached("test"))
}

func TestGetCacheStats(t *testing.T) {
	engine := NewTemplateEngine(50)

	engine.Compile("test1", "Template 1")
	engine.Render("test1", "data")

	engine.Compile("test2", "Template 2")
	engine.Render("test2", "data")
	engine.Render("test2", "data")

	stats := engine.GetCacheStats()
	assert.Equal(t, 2, stats["cache_size"])
	assert.Equal(t, 50, stats["max_size"])
	assert.Equal(t, 3, stats["total_renders"])
	assert.NotZero(t, stats["total_render_time"])
}

func TestComplexTemplate(t *testing.T) {
	engine := NewTemplateEngine(50)

	templateStr := `
	{{range .Items}}
	- {{.Name}}: {{.Value}}
	{{end}}
	`

	data := map[string]interface{}{
		"Items": []map[string]interface{}{
			{"Name": "Item1", "Value": 10},
			{"Name": "Item2", "Value": 20},
		},
	}

	result, err := engine.CompileAndRender("list", templateStr, data)
	assert.NoError(t, err)
	assert.Contains(t, result, "Item1")
	assert.Contains(t, result, "Item2")
}

func TestRenderLatency(t *testing.T) {
	engine := NewTemplateEngine(50)

	engine.Compile("test", "Hello {{.Name}}!")

	start := time.Now()
	_, _ = engine.Render("test", map[string]string{"Name": "World"})
	latency := time.Since(start)

	// Should be < 100ms even accounting for slow systems
	assert.Less(t, latency, 100*time.Millisecond)
}

func TestParseError(t *testing.T) {
	engine := NewTemplateEngine(50)

	_, err := engine.Compile("test", "{{.Name} missing closing bracket")
	assert.Error(t, err)
}

func BenchmarkRender(b *testing.B) {
	engine := NewTemplateEngine(50)
	engine.Compile("test", "Hello {{.Name}}!")

	for i := 0; i < b.N; i++ {
		engine.Render("test", map[string]string{"Name": "World"})
	}
}

func BenchmarkCompileAndRender(b *testing.B) {
	engine := NewTemplateEngine(50)

	for i := 0; i < b.N; i++ {
		engine.CompileAndRender("test"+string(rune(i%10)), "Hello {{.}}", "World")
	}
}
