package templates

import (
	"bytes"
	"fmt"
	"sync"
	"text/template"
	"time"
)

// TemplateEngine pre-compiles and caches Go templates for fast rendering
type TemplateEngine struct {
	mu        sync.RWMutex
	cache     map[string]*CachedTemplate
	maxSize   int
	compileStats map[string]*CompileStats
}

// CachedTemplate holds a compiled template with metrics
type CachedTemplate struct {
	Template  *template.Template
	CompiledAt time.Time
	RenderCount int
	RenderTime time.Duration
}

// CompileStats tracks compilation statistics
type CompileStats struct {
	TemplateName string
	CompileTime  time.Duration
	CompiledAt   time.Time
	RenderCount  int
	TotalRenderTime time.Duration
	AvgRenderTime time.Duration
}

// NewTemplateEngine creates a new template engine
func NewTemplateEngine(maxSize int) *TemplateEngine {
	if maxSize <= 0 {
		maxSize = 100
	}

	return &TemplateEngine{
		cache:        make(map[string]*CachedTemplate),
		maxSize:      maxSize,
		compileStats: make(map[string]*CompileStats),
	}
}

// Compile compiles and caches a template
func (te *TemplateEngine) Compile(name string, templateStr string) (*template.Template, error) {
	te.mu.Lock()
	defer te.mu.Unlock()

	// Check if already cached
	if cached, exists := te.cache[name]; exists {
		return cached.Template, nil
	}

	// Compile the template
	startTime := time.Now()
	tmpl, err := template.New(name).Parse(templateStr)
	compileTime := time.Since(startTime)

	if err != nil {
		return nil, err
	}

	// Check size limit
	if len(te.cache) >= te.maxSize {
		// Simple FIFO eviction
		var oldestName string
		var oldestTime time.Time

		for n, t := range te.cache {
			if oldestTime.IsZero() || t.CompiledAt.Before(oldestTime) {
				oldestName = n
				oldestTime = t.CompiledAt
			}
		}

		if oldestName != "" {
			delete(te.cache, oldestName)
			delete(te.compileStats, oldestName)
		}
	}

	// Cache the compiled template
	cached := &CachedTemplate{
		Template:   tmpl,
		CompiledAt: time.Now(),
	}

	te.cache[name] = cached

	// Track compilation stats
	te.compileStats[name] = &CompileStats{
		TemplateName: name,
		CompileTime:  compileTime,
		CompiledAt:   time.Now(),
	}

	return tmpl, nil
}

// Render renders a cached template with the given data
func (te *TemplateEngine) Render(name string, data interface{}) (string, error) {
	te.mu.RLock()
	cached, exists := te.cache[name]
	te.mu.RUnlock()

	if !exists {
		return "", fmt.Errorf("template not found: %s", name)
	}

	// Render template
	startTime := time.Now()
	var buf bytes.Buffer
	err := cached.Template.Execute(&buf, data)
	renderTime := time.Since(startTime)

	if err != nil {
		return "", err
	}

	// Update stats
	te.mu.Lock()
	cached.RenderCount++
	cached.RenderTime += renderTime

	if stats, exists := te.compileStats[name]; exists {
		stats.RenderCount++
		stats.TotalRenderTime += renderTime
		if stats.RenderCount > 0 {
			stats.AvgRenderTime = stats.TotalRenderTime / time.Duration(stats.RenderCount)
		}
	}
	te.mu.Unlock()

	return buf.String(), nil
}

// CompileAndRender compiles (if not cached) and renders a template
func (te *TemplateEngine) CompileAndRender(name string, templateStr string, data interface{}) (string, error) {
	_, err := te.Compile(name, templateStr)
	if err != nil {
		return "", err
	}

	return te.Render(name, data)
}

// GetStats returns compilation statistics
func (te *TemplateEngine) GetStats(name string) (*CompileStats, bool) {
	te.mu.RLock()
	defer te.mu.RUnlock()

	stats, exists := te.compileStats[name]
	return stats, exists
}

// GetAllStats returns all compilation statistics
func (te *TemplateEngine) GetAllStats() map[string]*CompileStats {
	te.mu.RLock()
	defer te.mu.RUnlock()

	statsCopy := make(map[string]*CompileStats)
	for name, stats := range te.compileStats {
		statsCopy[name] = stats
	}

	return statsCopy
}

// Clear clears all cached templates
func (te *TemplateEngine) Clear() {
	te.mu.Lock()
	defer te.mu.Unlock()

	te.cache = make(map[string]*CachedTemplate)
	te.compileStats = make(map[string]*CompileStats)
}

// CacheSize returns the current cache size
func (te *TemplateEngine) CacheSize() int {
	te.mu.RLock()
	defer te.mu.RUnlock()

	return len(te.cache)
}

// IsCached checks if a template is cached
func (te *TemplateEngine) IsCached(name string) bool {
	te.mu.RLock()
	defer te.mu.RUnlock()

	_, exists := te.cache[name]
	return exists
}

// Remove removes a template from cache
func (te *TemplateEngine) Remove(name string) {
	te.mu.Lock()
	defer te.mu.Unlock()

	delete(te.cache, name)
	delete(te.compileStats, name)
}

// GetCacheStats returns overall cache statistics
func (te *TemplateEngine) GetCacheStats() map[string]interface{} {
	te.mu.RLock()
	defer te.mu.RUnlock()

	totalRenders := 0
	totalRenderTime := time.Duration(0)

	for _, stats := range te.compileStats {
		totalRenders += stats.RenderCount
		totalRenderTime += stats.TotalRenderTime
	}

	avgRenderTime := time.Duration(0)
	if totalRenders > 0 {
		avgRenderTime = totalRenderTime / time.Duration(totalRenders)
	}

	return map[string]interface{}{
		"cache_size":           len(te.cache),
		"max_size":             te.maxSize,
		"total_renders":        totalRenders,
		"total_render_time":    totalRenderTime,
		"avg_render_time":      avgRenderTime,
		"usage_ratio":          float64(len(te.cache)) / float64(te.maxSize),
	}
}
