package server

import (
	"encoding/json"
	"context"
	"fmt"
	"io"
	"log"
	"net/http"
	"strings"
	"sync"
	"time"

	"gophergate/internal/config"
	"gophergate/internal/db"
	"gophergate/internal/middleware"
	"gophergate/internal/models"
	"gophergate/internal/providers"
	"gophergate/internal/router"
	"gophergate/internal/utils"

	"github.com/gin-gonic/gin"
)

type Server struct {
	router     *gin.Engine
	cfg        *config.Config
	database   *db.DB
	providers  map[string]providers.Provider
	sessions   *SessionManager
	hub        *Hub
	logger     *RequestLogger
	registry   *models.ModelRegistry
	registryMu sync.RWMutex
	modelRouter *router.Router
}

func NewServer(cfg *config.Config, database *db.DB) *Server {
	router := gin.Default()
	hub := NewHub()

	s := &Server{
		router:    router,
		cfg:       cfg,
		database:  database,
		providers: make(map[string]providers.Provider),
		sessions:  NewSessionManager(cfg.KeyBytes, 24*time.Hour),
		hub:       hub,
		logger:    NewRequestLogger(database, hub),
		registry:  &models.ModelRegistry{Providers: make(map[string]models.ProviderInfo)},
	}

	s.sessions.StartCleanup()
	// Fetch registry in background
	go func() {
		registry, err := utils.FetchRegistry()
		if err != nil {
			fmt.Printf("Warning: Failed to fetch initial model registry: %v\n", err)
		} else {
			s.registry = registry
		}
	}()

	// Initialize providers from DB and Config
	if err := s.RefreshProviders(); err != nil {
		fmt.Printf("Warning: Failed to initial refresh providers: %v\n", err)
	}

	s.setupRoutes()

	// Initialize model group router
	s.refreshRouter()
	return s
}

func (s *Server) RefreshProviders() error {
	var dbConfigs []db.ProviderConfig
	err := s.database.Select(&dbConfigs, "SELECT * FROM provider_configs")
	if err != nil {
		return fmt.Errorf("failed to fetch provider configs from db: %w", err)
	}

	dbMap := make(map[string]db.ProviderConfig)
	for _, cfg := range dbConfigs {
		dbMap[cfg.ID] = cfg
	}

	providerIDs := []string{"openai", "gemini", "deepseek", "moonshot", "grok", "ollama"}
	for _, id := range providerIDs {
		// Default values from config
		enabled := false
		baseURL := ""
		apiKey := ""

		switch id {
		case "openai":
			enabled = s.cfg.Providers.OpenAI.Enabled
			baseURL = s.cfg.Providers.OpenAI.BaseURL
			apiKey, _ = s.cfg.GetAPIKey("openai")
		case "gemini":
			enabled = s.cfg.Providers.Gemini.Enabled
			baseURL = s.cfg.Providers.Gemini.BaseURL
			apiKey, _ = s.cfg.GetAPIKey("gemini")
		case "deepseek":
			enabled = s.cfg.Providers.DeepSeek.Enabled
			baseURL = s.cfg.Providers.DeepSeek.BaseURL
			apiKey, _ = s.cfg.GetAPIKey("deepseek")
		case "moonshot":
			enabled = s.cfg.Providers.Moonshot.Enabled
			baseURL = s.cfg.Providers.Moonshot.BaseURL
			apiKey, _ = s.cfg.GetAPIKey("moonshot")
		case "grok":
			enabled = s.cfg.Providers.Grok.Enabled
			baseURL = s.cfg.Providers.Grok.BaseURL
			apiKey, _ = s.cfg.GetAPIKey("grok")
		}

		// Overrides from DB
		if dbCfg, ok := dbMap[id]; ok {
			enabled = dbCfg.Enabled
			if dbCfg.BaseURL != nil && *dbCfg.BaseURL != "" {
				baseURL = *dbCfg.BaseURL
			}
			if dbCfg.APIKey != nil && *dbCfg.APIKey != "" {
				key := *dbCfg.APIKey
				if dbCfg.APIKeyEncrypted {
					decrypted, err := utils.Decrypt(key, s.cfg.KeyBytes)
					if err == nil {
						key = decrypted
					} else {
						fmt.Printf("Warning: Failed to decrypt API key for %s: %v\n", id, err)
					}
				}
				apiKey = key
			}
		}

		if !enabled {
			delete(s.providers, id)
			continue
		}

		// Initialize provider
		var p providers.Provider
		switch id {
		case "openai":
			cfg := s.cfg.Providers.OpenAI
			cfg.BaseURL = baseURL
			p = providers.NewOpenAIProvider(cfg, apiKey)
		case "gemini":
			cfg := s.cfg.Providers.Gemini
			cfg.BaseURL = baseURL
			p = providers.NewGeminiProvider(cfg, apiKey)
		case "deepseek":
			cfg := s.cfg.Providers.DeepSeek
			cfg.BaseURL = baseURL
			p = providers.NewDeepSeekProvider(cfg, apiKey)
		case "moonshot":
			cfg := s.cfg.Providers.Moonshot
			cfg.BaseURL = baseURL
			p = providers.NewMoonshotProvider(cfg, apiKey)
		case "grok":
			cfg := s.cfg.Providers.Grok
			cfg.BaseURL = baseURL
			p = providers.NewGrokProvider(cfg, apiKey)
		case "ollama":
			cfg := s.cfg.Providers.Ollama
			cfg.BaseURL = baseURL
			p = providers.NewOllamaProvider(cfg)
		}

		if p != nil {
			s.providers[id] = providers.NewCircuitBreakerProvider(p)
		}
	}

	s.refreshRouter()
	return nil
}

func (s *Server) refreshRouter() {
	var groups []db.ModelGroup
	if err := s.database.Select(&groups, "SELECT * FROM model_groups"); err != nil {
		fmt.Printf("Warning: Failed to load model groups: %v\n", err)
		groups = nil
	}

	var classifyFn router.ClassifierFunc
	if openaiProvider, ok := s.providers["openai"]; ok {
		classifyFn = func(ctx context.Context, selectorModel, systemPrompt, userMessage string) (string, error) {
			req := &models.UnifiedRequest{
				Model: selectorModel,
				Messages: []models.UnifiedMessage{
					{Role: "system", Content: []models.UnifiedContentPart{{Type: "text", Text: systemPrompt}}},
					{Role: "user", Content: []models.UnifiedContentPart{{Type: "text", Text: userMessage}}},
				},
				MaxTokens: uint32Ptr(5),
				Stream:    false,
			}
			resp, err := openaiProvider.ChatCompletion(ctx, req)
			if err != nil {
				return "", err
			}
			if len(resp.Choices) == 0 {
				return "", fmt.Errorf("no choices in classifier response")
			}
			content, ok := resp.Choices[0].Message.Content.(string)
			if !ok {
				return "", fmt.Errorf("classifier response content is not a string")
			}
			return content, nil
		}
	}

	if s.modelRouter == nil {
		s.modelRouter = router.New(groups, classifyFn)
	} else {
		s.modelRouter.Reload(groups)
	}
}

func (s *Server) setupRoutes() {
	// Static files
	s.router.StaticFile("/", "./static/index.html")
	s.router.StaticFile("/favicon.ico", "./static/favicon.ico")
	s.router.Static("/css", "./static/css")
	s.router.Static("/js", "./static/js")
	s.router.Static("/img", "./static/img")

	// WebSocket
	s.router.GET("/ws", s.handleWebSocket)

	// API V1 (External LLM Access) - Secured with AuthMiddleware
	v1 := s.router.Group("/v1")
	v1.Use(middleware.AuthMiddleware(s.database, true))
	{
		v1.POST("/chat/completions", s.handleChatCompletions)
		v1.POST("/images/generations", s.handleImageGenerations)
		v1.GET("/models", s.handleListModels)
		v1.POST("/responses", s.handleResponses)
	}

	// Dashboard API Group
	api := s.router.Group("/api")
	{
		api.POST("/auth/login", s.handleLogin)
		api.GET("/auth/status", s.handleAuthStatus)
		api.POST("/auth/logout", s.handleLogout)
		api.POST("/auth/change-password", s.handleChangePassword)

		// Protected dashboard routes (need admin session)
		admin := api.Group("/")
		admin.Use(s.adminAuthMiddleware())
		{
			admin.GET("/usage/summary", s.handleUsageSummary)
			admin.GET("/usage/time-series", s.handleTimeSeries)
			admin.GET("/usage/providers", s.handleProvidersUsage)
			admin.GET("/usage/clients", s.handleClientsUsage)
			admin.GET("/usage/detailed", s.handleDetailedUsage)
			admin.GET("/analytics/breakdown", s.handleAnalyticsBreakdown)

			admin.GET("/clients", s.handleGetClients)
			admin.POST("/clients", s.handleCreateClient)
			admin.GET("/clients/:id", s.handleGetClient)
			admin.PUT("/clients/:id", s.handleUpdateClient)
			admin.DELETE("/clients/:id", s.handleDeleteClient)

			admin.GET("/clients/:id/tokens", s.handleGetClientTokens)
			admin.POST("/clients/:id/tokens", s.handleCreateClientToken)
			admin.DELETE("/clients/:id/tokens/:token_id", s.handleDeleteClientToken)

			admin.GET("/providers", s.handleGetProviders)
			admin.PUT("/providers/:name", s.handleUpdateProvider)
			admin.POST("/providers/:name/test", s.handleTestProvider)

			admin.GET("/models", s.handleGetModels)
			admin.PUT("/models/:id", s.handleUpdateModel)

			admin.GET("/model-groups", s.handleGetModelGroups)
			admin.POST("/model-groups", s.handleCreateModelGroup)
			admin.PUT("/model-groups/:id", s.handleUpdateModelGroup)
			admin.DELETE("/model-groups/:id", s.handleDeleteModelGroup)

			admin.GET("/users", s.handleGetUsers)
			admin.POST("/users", s.handleCreateUser)
			admin.PUT("/users/:id", s.handleUpdateUser)
			admin.DELETE("/users/:id", s.handleDeleteUser)

			admin.GET("/system/health", s.handleSystemHealth)
			admin.GET("/system/metrics", s.handleSystemMetrics)
			admin.GET("/system/settings", s.handleGetSettings)
			admin.POST("/system/backup", s.handleCreateBackup)
			admin.GET("/system/logs", s.handleGetLogs)
		}
	}

	s.router.GET("/health", func(c *gin.Context) {
		c.JSON(http.StatusOK, gin.H{"status": "ok"})
	})
}

func (s *Server) handleResponses(c *gin.Context) {
	startTime := time.Now()
	var req models.ResponsesRequest
	if err := c.ShouldBindJSON(&req); err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
		return
	}

	// Select provider based on model name
	providerName := "openai" // default for Responses API
	modelLower := strings.ToLower(req.Model)
	if strings.HasPrefix(modelLower, "gemini/") || strings.Contains(modelLower, "gemini") || strings.HasPrefix(modelLower, "google/") {
		providerName = "gemini"
	} else if strings.HasPrefix(modelLower, "deepseek/") || (strings.Contains(modelLower, "deepseek") && !strings.Contains(modelLower, "ollama")) {
		providerName = "deepseek"
	} else if strings.HasPrefix(modelLower, "moonshot/") || strings.Contains(modelLower, "kimi") || strings.Contains(modelLower, "moonshot") {
		providerName = "moonshot"
	} else if strings.HasPrefix(modelLower, "grok/") || strings.Contains(modelLower, "grok") {
		providerName = "grok"
	} else if strings.HasPrefix(modelLower, "ollama/") ||
		strings.Contains(modelLower, "glm-") ||
		strings.Contains(modelLower, "qwen") ||
		strings.Contains(modelLower, "gemma") ||
		strings.Contains(modelLower, "llama") ||
		strings.Contains(modelLower, "mistral") ||
		strings.Contains(modelLower, "phi") ||
		strings.Contains(modelLower, "yi") ||
		strings.Contains(modelLower, "codellama") ||
		strings.Contains(modelLower, "command-r") {
		providerName = "ollama"
	}

	provider, ok := s.providers[providerName]
	if !ok {
		c.JSON(http.StatusInternalServerError, gin.H{"error": fmt.Sprintf("Provider %s not enabled or supported", providerName)})
		return
	}

	// Strip common prefixes from model name
	modelID := req.Model
	prefixes := []string{"gemini/", "google/", "openai/", "deepseek/", "moonshot/", "grok/", "ollama/"}
	for _, p := range prefixes {
		if strings.HasPrefix(modelID, p) {
			modelID = strings.TrimPrefix(modelID, p)
			break
		}
	}

	// Use the stripped model name for the actual API call
	req.Model = modelID

	clientID := "default"
	if auth, ok := c.Get("auth"); ok {
		if authInfo, ok := auth.(models.AuthInfo); ok {
			clientID = authInfo.ClientID
		}
	}

	stream := req.Stream != nil && *req.Stream

	if stream {
		ch, err := provider.ResponsesStream(c.Request.Context(), &req)
		if err != nil {
			s.logRequest(startTime, clientID, providerName, req.Model, nil, err, false)
			c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
			return
		}

		c.Header("Content-Type", "text/event-stream")
		c.Header("Cache-Control", "no-cache")
		c.Header("Connection", "keep-alive")

		var lastUsage *models.ResponsesUsage
		c.Stream(func(w io.Writer) bool {
			chunk, ok := <-ch
			if !ok {
				fmt.Fprintf(w, "data: [DONE]\n\n")
				if lastUsage != nil {
					s.logRequest(startTime, clientID, providerName, req.Model, lastUsage.ToUsage(), nil, false)
				} else {
					s.logRequest(startTime, clientID, providerName, req.Model, nil, nil, false)
				}
				return false
			}
			// Capture usage from the response payload in streaming chunks
			if chunk.Response != nil && chunk.Response.Usage != nil {
				lastUsage = chunk.Response.Usage
			}
			data, err := json.Marshal(chunk)
			if err != nil {
				return false
			}
			fmt.Fprintf(w, "data: %s\n\n", data)
			return true
		})
		return
	}

	resp, err := provider.Responses(c.Request.Context(), &req)
	if err != nil {
		s.logRequest(startTime, clientID, providerName, req.Model, nil, err, false)
		c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
		return
	}

	if resp.Usage != nil {
		s.logRequest(startTime, clientID, providerName, req.Model, resp.Usage.ToUsage(), nil, false)
	} else {
		s.logRequest(startTime, clientID, providerName, req.Model, nil, nil, false)
	}
	c.JSON(http.StatusOK, resp)
}

func (s *Server) handleListModels(c *gin.Context) {
	type OpenAIModel struct {
		ID      string `json:"id"`
		Object  string `json:"object"`
		Created int64  `json:"created"`
		OwnedBy string `json:"owned_by"`
	}

	modelMap := make(map[string]OpenAIModel)
	allowedProviders := map[string]bool{
		"openai":        true,
		"google":        true, // Models from models.dev use 'google' ID for Gemini
		"deepseek":      true,
		"moonshot":      true,
		"moonshotai":    true, // Official moonshotai ID in models.dev
		"moonshotai-cn": true, // Official moonshotai-cn ID in models.dev
		"xai":           true, // Models from models.dev use 'xai' ID for Grok
		"llmgateway":    true, // Catch-all for newer models
		"ollama":        true,
	}

	s.registryMu.RLock()
	if s.registry != nil {
		for pID, pInfo := range s.registry.Providers {
			if !allowedProviders[pID] {
				continue
			}
			for mID := range pInfo.Models {
				if _, exists := modelMap[mID]; !exists {
					modelMap[mID] = OpenAIModel{
						ID:      mID,
						Object:  "model",
						Created: 1700000000,
						OwnedBy: pID,
					}
				}
			}
		}
	}
	s.registryMu.RUnlock()

	// Add configured Ollama models
	if s.cfg.Providers.Ollama.Enabled {
		for _, mID := range s.cfg.Providers.Ollama.Models {
			if _, exists := modelMap[mID]; !exists {
				modelMap[mID] = OpenAIModel{
					ID:      mID,
					Object:  "model",
					Created: 1700000000,
					OwnedBy: "ollama",
				}
			}
		}
	}

	var data []OpenAIModel
	for _, m := range modelMap {
		data = append(data, m)
	}

	c.JSON(http.StatusOK, gin.H{
		"object": "list",
		"data":   data,
	})
}

func (s *Server) handleChatCompletions(c *gin.Context) {
	startTime := time.Now()
	var req models.ChatCompletionRequest
	if err := c.ShouldBindJSON(&req); err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
		return
	}

	// Select provider based on model name
	providerName := "openai" // default
	modelLower := strings.ToLower(req.Model)
	if strings.HasPrefix(modelLower, "gemini/") || strings.Contains(modelLower, "gemini") || strings.HasPrefix(modelLower, "google/") {
		providerName = "gemini"
	} else if strings.HasPrefix(modelLower, "deepseek/") || (strings.Contains(modelLower, "deepseek") && !strings.Contains(modelLower, "ollama")) {
		// Only use deepseek provider if it's not explicitly tagged for ollama
		providerName = "deepseek"
	} else if strings.HasPrefix(modelLower, "moonshot/") || strings.Contains(modelLower, "kimi") || strings.Contains(modelLower, "moonshot") {
		providerName = "moonshot"
	} else if strings.HasPrefix(modelLower, "grok/") || strings.Contains(modelLower, "grok") {
		providerName = "grok"
	} else if strings.HasPrefix(modelLower, "ollama/") ||
		strings.Contains(modelLower, "glm-") ||
		strings.Contains(modelLower, "qwen") ||
		strings.Contains(modelLower, "gemma") ||
		strings.Contains(modelLower, "llama") ||
		strings.Contains(modelLower, "mistral") ||
		strings.Contains(modelLower, "phi") ||
		strings.Contains(modelLower, "yi") ||
		strings.Contains(modelLower, "codellama") ||
		strings.Contains(modelLower, "command-r") {
		providerName = "ollama"
	}

	provider, ok := s.providers[providerName]
	if !ok {
		c.JSON(http.StatusInternalServerError, gin.H{"error": fmt.Sprintf("Provider %s not enabled or supported", providerName)})
		return
	}

	// Strip common prefixes
	modelID := req.Model
	prefixes := []string{"gemini/", "google/", "openai/", "deepseek/", "moonshot/", "grok/", "ollama/"}
	for _, p := range prefixes {
		if strings.HasPrefix(modelID, p) {
			modelID = strings.TrimPrefix(modelID, p)
			break
		}
	}

	// Check if model is a group and route to a concrete model
	if s.modelRouter != nil && s.modelRouter.IsGroup(modelID) {
		userMessage := extractUserMessage(req.Messages)
		decision, err := s.modelRouter.Route(c.Request.Context(), modelID, userMessage)
		if err != nil {
			c.JSON(http.StatusInternalServerError, gin.H{"error": fmt.Sprintf("model routing failed: %v", err)})
			return
		}
		modelID = decision.SelectedModel
		log.Printf("[ROUTER] %s -> %s (%s: %s)", req.Model, modelID, decision.Strategy, decision.Reason)
	}

	// Convert ChatCompletionRequest to UnifiedRequest
	unifiedReq := &models.UnifiedRequest{
		Model:            modelID,
		Messages:         []models.UnifiedMessage{},
		Temperature:      req.Temperature,
		TopP:             req.TopP,
		TopK:             req.TopK,
		N:                req.N,
	MaxTokens:        req.MaxTokens,
	PresencePenalty:  req.PresencePenalty,
	FrequencyPenalty: req.FrequencyPenalty,
	Stream:           req.Stream != nil && *req.Stream,
	Tools:            req.Tools,
	ToolChoice:       req.ToolChoice,
}

// Inject max_tokens from model registry when client doesn't specify one.
// Prevents providers from applying a low default output cap.
// DEBUG: Trace max_tokens through the proxy
clientMaxTokens := "nil"
if unifiedReq.MaxTokens != nil {
	clientMaxTokens = fmt.Sprintf("%d", *unifiedReq.MaxTokens)
}
log.Printf("[DEBUG] %s: client max_tokens=%s", modelID, clientMaxTokens)
if unifiedReq.MaxTokens == nil {
	s.registryMu.RLock()
	meta := s.registry.FindModel(modelID)
	s.registryMu.RUnlock()
	if meta != nil && meta.Limit != nil && meta.Limit.Output > 0 {
		unifiedReq.MaxTokens = &meta.Limit.Output
		log.Printf("[DEBUG] %s: injected registry max_tokens=%d", modelID, meta.Limit.Output)
	} else {
		log.Printf("[DEBUG] %s: no registry limit found, leaving max_tokens nil (provider default)", modelID)
	}
} else {
	log.Printf("[DEBUG] %s: using client's max_tokens=%d", modelID, *unifiedReq.MaxTokens)
}

	// Handle Stop sequences
	if req.Stop != nil {
		var stop []string
		if err := json.Unmarshal(req.Stop, &stop); err == nil {
			unifiedReq.Stop = stop
		} else {
			var singleStop string
			if err := json.Unmarshal(req.Stop, &singleStop); err == nil {
				unifiedReq.Stop = []string{singleStop}
			}
		}
	}

	// Convert messages
	for _, msg := range req.Messages {
		unifiedMsg := models.UnifiedMessage{
			Role:             msg.Role,
			Content:          []models.UnifiedContentPart{},
			ReasoningContent: msg.ReasoningContent,
			ToolCalls:        msg.ToolCalls,
			Name:             msg.Name,
			ToolCallID:       msg.ToolCallID,
		}

		// Handle multimodal content
		if strContent, ok := msg.Content.(string); ok {
			unifiedMsg.Content = append(unifiedMsg.Content, models.UnifiedContentPart{
				Type: "text",
				Text: strContent,
			})
		} else if parts, ok := msg.Content.([]interface{}); ok {
			for _, part := range parts {
				if partMap, ok := part.(map[string]interface{}); ok {
					partType, _ := partMap["type"].(string)
					if partType == "text" {
						text, _ := partMap["text"].(string)
						unifiedMsg.Content = append(unifiedMsg.Content, models.UnifiedContentPart{
							Type: "text",
							Text: text,
						})
					} else if partType == "image_url" {
						if imgURLMap, ok := partMap["image_url"].(map[string]interface{}); ok {
							url, _ := imgURLMap["url"].(string)
							imageInput := &models.ImageInput{}
							if strings.HasPrefix(url, "data:") {
								mime, data, err := utils.ParseDataURL(url)
								if err == nil {
									imageInput.Base64 = data
									imageInput.MimeType = mime
								}
							} else {
								imageInput.URL = url
							}
							unifiedMsg.Content = append(unifiedMsg.Content, models.UnifiedContentPart{
								Type:  "image",
								Image: imageInput,
							})
							unifiedReq.HasImages = true
						}
					}
				}
			}
		}

		unifiedReq.Messages = append(unifiedReq.Messages, unifiedMsg)
	}

	clientID := "default"
	if auth, ok := c.Get("auth"); ok {
		if authInfo, ok := auth.(models.AuthInfo); ok {
			unifiedReq.ClientID = authInfo.ClientID
			clientID = authInfo.ClientID
		}
	} else {
		unifiedReq.ClientID = clientID
	}

	if unifiedReq.Stream {
		ch, err := provider.ChatCompletionStream(c.Request.Context(), unifiedReq)
		if err != nil {
			s.logRequest(startTime, clientID, providerName, req.Model, nil, err, unifiedReq.HasImages)
			c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
			return
		}

		c.Header("Content-Type", "text/event-stream")
		c.Header("Cache-Control", "no-cache")
		c.Header("Connection", "keep-alive")

		var lastUsage *models.Usage
		c.Stream(func(w io.Writer) bool {
			chunk, ok := <-ch
			if !ok {
				fmt.Fprintf(w, "data: [DONE]\n\n")
				s.logRequest(startTime, clientID, providerName, req.Model, lastUsage, nil, unifiedReq.HasImages)
				return false
			}
			if chunk.Usage != nil {
				lastUsage = chunk.Usage
			}
			data, err := json.Marshal(chunk)
			if err != nil {
				return false
			}
			fmt.Fprintf(w, "data: %s\n\n", data)
			return true
		})
		return
	}

	resp, err := provider.ChatCompletion(c.Request.Context(), unifiedReq)
	if err != nil {
		s.logRequest(startTime, clientID, providerName, req.Model, nil, err, unifiedReq.HasImages)
		c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
		return
	}

	s.logRequest(startTime, clientID, providerName, req.Model, resp.Usage, nil, unifiedReq.HasImages)
	c.JSON(http.StatusOK, resp)
}

func extractUserMessage(messages []models.ChatMessage) string {
	for i := len(messages) - 1; i >= 0; i-- {
		if messages[i].Role == "user" {
			switch c := messages[i].Content.(type) {
			case string:
				return c
			default:
				return ""
			}
		}
	}
	return ""
}

func (s *Server) handleImageGenerations(c *gin.Context) {
	startTime := time.Now()
	var req models.ImageGenerationRequest
	if err := c.ShouldBindJSON(&req); err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
		return
	}

	// Determine provider based on model name
	providerName := "openai"
	modelLower := strings.ToLower(req.Model)
	switch {
	case strings.Contains(modelLower, "imagen"), strings.Contains(modelLower, "gemini"):
		providerName = "gemini"
	case strings.Contains(modelLower, "dall"), strings.HasPrefix(modelLower, "openai/"):
		providerName = "openai"
	}

	// Default model for each provider if not specified
	if req.Model == "" {
		if providerName == "openai" {
			req.Model = "dall-e-3"
		} else {
			req.Model = "imagen-3.0-generate-001"
		}
	}

	// Strip common prefixes
	prefixes := []string{"openai/", "gemini/", "google/"}
	for _, p := range prefixes {
		if strings.HasPrefix(req.Model, p) {
			req.Model = strings.TrimPrefix(req.Model, p)
			break
		}
	}

	provider, ok := s.providers[providerName]
	if !ok {
		c.JSON(http.StatusInternalServerError, gin.H{"error": fmt.Sprintf("Provider %s not enabled or supported", providerName)})
		return
	}

	clientID := "default"
	if auth, ok := c.Get("auth"); ok {
		if authInfo, ok := auth.(models.AuthInfo); ok {
			clientID = authInfo.ClientID
		}
	}

	resp, err := provider.ImageGeneration(c.Request.Context(), &req)
	if err != nil {
		s.logRequest(startTime, clientID, providerName, req.Model, nil, err, false)
		c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
		return
	}

	// Estimate tokens from prompt text (~4 chars per token)
	promptTokens := uint32(len(req.Prompt) / 4)
	if promptTokens < 1 {
		promptTokens = 1
	}

	// Calculate per-image cost (not per-token like chat)
	cost := imageGenCost(providerName, req.Model, req.Size, uint32(len(resp.Data)))

	s.logRequest(startTime, clientID, providerName, req.Model, &models.Usage{
		PromptTokens:     promptTokens,
		CompletionTokens: uint32(len(resp.Data)),
		TotalTokens:      promptTokens + uint32(len(resp.Data)),
	}, nil, false)

	// Update cost in DB — image gen is per-image, not per-token
	if cost > 0 {
		s.database.Exec("UPDATE llm_requests SET cost = ? WHERE id = (SELECT MAX(id) FROM llm_requests)", cost)
	}

	c.JSON(http.StatusOK, resp)
}

// imageGenCost returns per-image pricing for known image generation models.
func imageGenCost(provider, model string, size *string, n uint32) float64 {
	if n == 0 {
		return 0
	}
	modelLower := strings.ToLower(model)
	var perImage float64

	switch {
	case strings.Contains(modelLower, "dall-e-3"):
		perImage = 0.040 // standard 1024x1024
		if size != nil {
			s := *size
			if s == "1024x1792" || s == "1792x1024" {
				perImage = 0.080
			}
		}
	case strings.Contains(modelLower, "dall-e-2"):
		perImage = 0.020
	case strings.Contains(modelLower, "imagen"):
		perImage = 0.040 // approximate
	default:
		return 0
	}

	return perImage * float64(n)
}

func (s *Server) logRequest(start time.Time, clientID, provider, model string, usage *models.Usage, err error, hasImages bool) {
	entry := RequestLog{
		Timestamp:  start,
		ClientID:   clientID,
		Provider:   provider,
		Model:      model,
		Status:     "success",
		DurationMS: time.Since(start).Milliseconds(),
		HasImages:  hasImages,
	}

	if err != nil {
		entry.Status = "error"
		entry.ErrorMessage = err.Error()
	}

	if usage != nil {
		entry.PromptTokens = usage.PromptTokens
		entry.CompletionTokens = usage.CompletionTokens
		entry.TotalTokens = usage.TotalTokens
		if usage.ReasoningTokens != nil {
			entry.ReasoningTokens = *usage.ReasoningTokens
		}
		if usage.CacheReadTokens != nil {
			entry.CacheReadTokens = *usage.CacheReadTokens
		}
		if usage.CacheWriteTokens != nil {
			entry.CacheWriteTokens = *usage.CacheWriteTokens
		}

		// Calculate cost using registry
		s.registryMu.RLock()
		entry.Cost = utils.CalculateCost(s.registry, model, entry.PromptTokens, entry.CompletionTokens, entry.ReasoningTokens, entry.CacheReadTokens, entry.CacheWriteTokens)
		s.registryMu.RUnlock()
	}

	s.logger.LogRequest(entry)
}

func (s *Server) Run() error {
	go s.hub.Run()
	s.logger.Start()

	// Start registry refresher
	go func() {
		ticker := time.NewTicker(24 * time.Hour)
		for range ticker.C {
			newRegistry, err := utils.FetchRegistry()
			if err == nil {
				s.registryMu.Lock()
				s.registry = newRegistry
				s.registryMu.Unlock()
			}
		}
	}()

	addr := fmt.Sprintf("%s:%d", s.cfg.Server.Host, s.cfg.Server.Port)
	return s.router.Run(addr)
}

func uint32Ptr(v uint32) *uint32 { return &v }
