package service

import (
	"context"
	"fmt"
	"log"
	"os"
	"time"

	"github.com/sony/gobreaker/v2"
	"github.com/zs/InsightReply/internal/service/llm"
)

type AIService struct {
	providers map[string]llm.Provider
	breakers  map[string]*gobreaker.CircuitBreaker[string]

	defaultProvider string
	defaultModel    string
}

func NewAIService() *AIService {
	s := &AIService{
		providers: make(map[string]llm.Provider),
		breakers:  make(map[string]*gobreaker.CircuitBreaker[string]),
	}

	// 1. Initialize Providers based on ENV
	if key := os.Getenv("OPENAI_API_KEY"); key != "" {
		s.providers["openai"] = llm.NewOpenAIProvider(key, os.Getenv("OPENAI_BASE_URL"), "openai")
	}
	if key := os.Getenv("ANTHROPIC_API_KEY"); key != "" {
		s.providers["anthropic"] = llm.NewAnthropicProvider(key, os.Getenv("ANTHROPIC_BASE_URL"))
	}
	if key := os.Getenv("DEEPSEEK_API_KEY"); key != "" {
		baseURL := os.Getenv("DEEPSEEK_BASE_URL")
		if baseURL == "" {
			baseURL = "https://api.deepseek.com/v1" // Add v1 as expected by OpenAI SDK compatibility
		}
		s.providers["deepseek"] = llm.NewOpenAIProvider(key, baseURL, "deepseek")
	}
	if key := os.Getenv("GEMINI_API_KEY"); key != "" {
		s.providers["gemini"] = llm.NewGeminiProvider(key, os.Getenv("GEMINI_BASE_URL"))
	}

	// 2. Initialize Circuit Breakers for each provider
	for name := range s.providers {
		st := gobreaker.Settings{
			Name:        name + "_cb",
			MaxRequests: 3,                // Requests allowed in half-open state
			Interval:    10 * time.Minute, // Cyclic period for closed state counters
			Timeout:     60 * time.Second, // Open state duration
			ReadyToTrip: func(counts gobreaker.Counts) bool {
				failureRatio := float64(counts.TotalFailures) / float64(counts.Requests)
				return counts.Requests >= 5 && failureRatio >= 0.6 // Trip if 60% fail after 5 reqs
			},
		}
		s.breakers[name] = gobreaker.NewCircuitBreaker[string](st)
	}

	s.defaultProvider = os.Getenv("LLM_PROVIDER")
	if s.defaultProvider == "" {
		s.defaultProvider = "openai"
	}
	s.defaultModel = os.Getenv("LLM_MODEL")
	if s.defaultModel == "" {
		s.defaultModel = "gpt-4o-mini"
	}

	return s
}

func (s *AIService) TestConnection(ctx context.Context) (string, error) {
	if len(s.providers) == 0 {
		return "", fmt.Errorf("no LLM providers configured")
	}
	return "Ready (Multi-LLM configured)", nil
}

// GenerateReply dynamically routes to the preferred LLM and uses a fallback chain if it fails.
func (s *AIService) GenerateReply(ctx context.Context, tweetContent, productContext, userIdentity string, preferredProvider, preferredModel string) (string, error) {
	systemPrompt := "You are a professional X (Twitter) ghostwriter. You MUST respond with valid JSON."
	userPrompt := fmt.Sprintf(`
		You are a social media expert. 
		User Identity: %s
		%s
		Target Tweet: "%s"
		
		Generate 3 high-quality, distinct replies for X (Twitter) using different strategic angles.
		Suggested angles depending on context: Contrarian, Analytical, Supportive, Data-driven, Founder's Experience, Quote Tweet.
		IMPORTANT: If "Available User Custom Strategies" are provided above, you MUST prioritize using those custom strategy angles for your replies.
		IMPORTANT: If a specific "IMMITATE STYLE" instruction is provided in the Identity or Context, you MUST perfectly clone that linguistic tone.
		
		Keep each reply natural, engaging, and under 280 characters. No hashtags unless highly relevant.
		
		Respond ONLY with a JSON array in the exact following format, without any markdown formatting wrappers (like markdown code blocks):
		[
		  {"strategy": "Name of Strategy 1", "content": "Reply content 1"},
		  {"strategy": "Name of Strategy 2", "content": "Reply content 2"},
		  {"strategy": "Name of Strategy 3", "content": "Reply content 3"}
		]
	`, userIdentity, productContext, tweetContent)

	targetProvider := preferredProvider
	if targetProvider == "" {
		targetProvider = s.defaultProvider
	}
	targetModel := preferredModel
	if targetModel == "" {
		targetModel = s.defaultModel
	}

	// Fallback chain (as designed in IMPLEMENTATION_PLAN: current -> Anthropic -> OpenAI -> Gemini -> DeepSeek)
	fallbackChain := []string{targetProvider, "anthropic", "openai", "gemini", "deepseek"}

	for _, pName := range fallbackChain {
		provider, ok := s.providers[pName]
		if !ok {
			log.Printf("Provider %s bypassed (not configured)", pName)
			continue
		}
		breaker, ok := s.breakers[pName]
		if !ok {
			continue // Should never happen
		}

		// Use the target model only on the initially requested provider. For fallbacks, use a safe default model.
		modelToUse := targetModel
		if pName != targetProvider {
			modelToUse = getDefaultModelFor(pName)
		}

		log.Printf("Routing request to LLM Provider: %s (Model: %s)", pName, modelToUse)

		// Execute through circuit breaker
		reply, err := breaker.Execute(func() (string, error) {
			// Add a simple 30s timeout per call
			callCtx, cancel := context.WithTimeout(ctx, 30*time.Second)
			defer cancel()
			return provider.GenerateReply(callCtx, modelToUse, systemPrompt, userPrompt)
		})

		if err == nil {
			return reply, nil // Success
		}

		log.Printf("Provider %s failed: %v. Attempting next in fallback chain...", pName, err)
	}
	
	return "", fmt.Errorf("all providers failed to generate reply")
}

// ExtractStyle consumes a viral AI reply and uses the LLM to reverse-engineer its linguistic fingerprint
func (s *AIService) ExtractStyle(ctx context.Context, viralReplyContent string) (string, error) {
	systemPrompt := "You are a master linguistic analyst and copywriter."
	userPrompt := fmt.Sprintf(`
		Analyze the following highly successful social media reply:
		"%s"
		
		Extract the core stylistic elements that made it successful. Focus on:
		1. Tone (e.g., witty, provocative, deadpan, empathetic)
		2. Sentence structure (e.g., short punchy sentences, questions, bullet points)
		3. Key jargon or vocabulary patterns
		
		Provide ONLY a concise, 2-3 sentence description of the style profile that another AI should imitate in the future.
		No conversational filler, just the exact instruction string to append to future system prompts.
	`, viralReplyContent)

	// Route through our Multi-LLM fallback logic
	// Try OpenAI first, fallback to Anthropic
	providers := []string{"openai", "anthropic", "gemini", "deepseek"}
	
	for _, pName := range providers {
		pConf, exists := s.providers[pName]
		cb, cbExists := s.breakers[pName]
		
		if !exists || !cbExists {
			continue
		}

		styleDesc, err := cb.Execute(func() (string, error) {
			// Use a default model for style extraction, as it's not user-facing and can be optimized for cost/speed
			modelToUse := getDefaultModelFor(pName)
			if modelToUse == "" { // Fallback if getDefaultModelFor doesn't have an entry
				modelToUse = "gpt-4o-mini" // A safe default
			}
			return pConf.GenerateReply(ctx, modelToUse, systemPrompt, userPrompt)
		})

		if err == nil && styleDesc != "" {
			return styleDesc, nil
		}
		log.Printf("Provider %s failed to extract style: %v. Attempting next...", pName, err)
	}
	
	return "", fmt.Errorf("failed to extract style from any provider")
}

func getDefaultModelFor(provider string) string {
	switch provider {
	case "openai":
		return "gpt-4o-mini"
	case "anthropic":
		return "claude-3-5-haiku-latest"
	case "deepseek":
		return "deepseek-chat"
	case "gemini":
		return "gemini-2.5-flash"
	default:
		return ""
	}
}
