package routing

import (
	"context"
	"fmt"
)

// LLMProvider defines interface for LLM services
type LLMProvider interface {
	// Name returns provider name (e.g., "openai", "claude", "local")
	Name() string
	
	// Chat sends a message and returns response
	Chat(ctx context.Context, systemPrompt, userPrompt string) (string, error)
	
	// IsAvailable checks if provider is configured and reachable
	IsAvailable(ctx context.Context) error
}

// ProviderRegistry manages available LLM providers
type ProviderRegistry struct {
	providers map[string]LLMProvider
	default_  string
}

// NewProviderRegistry creates a new registry
func NewProviderRegistry() *ProviderRegistry {
	return &ProviderRegistry{
		providers: make(map[string]LLMProvider),
	}
}

// Register adds a provider
func (pr *ProviderRegistry) Register(provider LLMProvider) error {
	if provider.Name() == "" {
		return fmt.Errorf("provider name cannot be empty")
	}
	pr.providers[provider.Name()] = provider
	return nil
}

// SetDefault sets the default provider
func (pr *ProviderRegistry) SetDefault(name string) error {
	if _, exists := pr.providers[name]; !exists {
		return fmt.Errorf("provider %s not registered", name)
	}
	pr.default_ = name
	return nil
}

// Get retrieves a provider by name
func (pr *ProviderRegistry) Get(name string) (LLMProvider, error) {
	if name == "" {
		name = pr.default_
	}
	provider, exists := pr.providers[name]
	if !exists {
		return nil, fmt.Errorf("provider %s not found", name)
	}
	return provider, nil
}

// GetDefault returns the default provider
func (pr *ProviderRegistry) GetDefault() (LLMProvider, error) {
	if pr.default_ == "" {
		return nil, fmt.Errorf("no default provider set")
	}
	return pr.Get(pr.default_)
}

// RoutingProviderLLM routes between multiple LLM providers with fallback
type RoutingProviderLLM struct {
	registry      *ProviderRegistry
	fallbackOrder []string
}

// NewRoutingProviderLLM creates a routing LLM
func NewRoutingProviderLLM(registry *ProviderRegistry, order ...string) *RoutingProviderLLM {
	return &RoutingProviderLLM{
		registry:      registry,
		fallbackOrder: order,
	}
}

// Chat tries providers in order
func (rp *RoutingProviderLLM) Chat(ctx context.Context, systemPrompt, userPrompt string) (string, error) {
	for _, providerName := range rp.fallbackOrder {
		provider, err := rp.registry.Get(providerName)
		if err != nil {
			continue
		}
		
		if err := provider.IsAvailable(ctx); err != nil {
			continue
		}
		
		response, err := provider.Chat(ctx, systemPrompt, userPrompt)
		if err == nil {
			return response, nil
		}
	}
	
	return "", fmt.Errorf("all LLM providers failed")
}

// CachingLLMProvider wraps a provider with caching
type CachingLLMProvider struct {
	provider LLMProvider
	cache    map[string]string
}

// NewCachingLLMProvider creates a cached provider
func NewCachingLLMProvider(provider LLMProvider) *CachingLLMProvider {
	return &CachingLLMProvider{
		provider: provider,
		cache:    make(map[string]string),
	}
}

// Chat returns cached response if available
func (clp *CachingLLMProvider) Chat(ctx context.Context, systemPrompt, userPrompt string) (string, error) {
	key := systemPrompt + "|" + userPrompt
	
	if cached, exists := clp.cache[key]; exists {
		return cached, nil
	}
	
	response, err := clp.provider.Chat(ctx, systemPrompt, userPrompt)
	if err != nil {
		return "", err
	}
	
	clp.cache[key] = response
	return response, nil
}

// IsAvailable delegates to wrapped provider
func (clp *CachingLLMProvider) IsAvailable(ctx context.Context) error {
	return clp.provider.IsAvailable(ctx)
}

// Name delegates to wrapped provider
func (clp *CachingLLMProvider) Name() string {
	return clp.provider.Name() + "-cached"
}

// RetryingLLMProvider wraps a provider with retry logic
type RetryingLLMProvider struct {
	provider    LLMProvider
	maxRetries  int
	backoffFunc func(attempt int) interface{}
}

// NewRetryingLLMProvider creates a retrying provider
func NewRetryingLLMProvider(provider LLMProvider, maxRetries int) *RetryingLLMProvider {
	return &RetryingLLMProvider{
		provider:   provider,
		maxRetries: maxRetries,
		backoffFunc: func(attempt int) interface{} {
			// Exponential backoff: 1s, 2s, 4s...
			return 1 << uint(attempt)
		},
	}
}

// Chat retries on failure
func (rlp *RetryingLLMProvider) Chat(ctx context.Context, systemPrompt, userPrompt string) (string, error) {
	var lastErr error
	
	for attempt := 0; attempt <= rlp.maxRetries; attempt++ {
		response, err := rlp.provider.Chat(ctx, systemPrompt, userPrompt)
		if err == nil {
			return response, nil
		}
		lastErr = err
	}
	
	return "", fmt.Errorf("failed after %d retries: %w", rlp.maxRetries, lastErr)
}

// IsAvailable delegates to wrapped provider
func (rlp *RetryingLLMProvider) IsAvailable(ctx context.Context) error {
	return rlp.provider.IsAvailable(ctx)
}

// Name delegates to wrapped provider
func (rlp *RetryingLLMProvider) Name() string {
	return rlp.provider.Name() + "-retrying"
}

// PromptTemplate defines a reusable prompt structure
type PromptTemplate interface {
	// Render creates a prompt from values
	Render(values map[string]interface{}) (string, error)
}

// SimplePromptTemplate uses Go text/template syntax
type SimplePromptTemplate struct {
	template string
}

// NewSimplePromptTemplate creates a simple template
func NewSimplePromptTemplate(template string) *SimplePromptTemplate {
	return &SimplePromptTemplate{template: template}
}

// Render renders the template (placeholder implementation)
func (spt *SimplePromptTemplate) Render(values map[string]interface{}) (string, error) {
	// In real implementation, use text/template
	return spt.template, nil
}

// PromptBuilder builds prompts from components
type PromptBuilder struct {
	system   string
	sections []string
}

// NewPromptBuilder creates a new builder
func NewPromptBuilder() *PromptBuilder {
	return &PromptBuilder{
		sections: []string{},
	}
}

// System sets the system prompt
func (pb *PromptBuilder) System(prompt string) *PromptBuilder {
	pb.system = prompt
	return pb
}

// AddSection adds a prompt section
func (pb *PromptBuilder) AddSection(title, content string) *PromptBuilder {
	if title != "" {
		pb.sections = append(pb.sections, fmt.Sprintf("## %s\n%s", title, content))
	} else {
		pb.sections = append(pb.sections, content)
	}
	return pb
}

// Build returns the complete prompt
func (pb *PromptBuilder) Build() (system, user string) {
	user = ""
	for i, section := range pb.sections {
		if i > 0 {
			user += "\n\n"
		}
		user += section
	}
	return pb.system, user
}
