package server

import (
	"fmt"
	"net/http"
	"strings"
	"time"

	"github.com/gin-gonic/gin"
	"github.com/sirupsen/logrus"

	"github.com/tingly-dev/tingly-box/internal/loadbalance"
	"github.com/tingly-dev/tingly-box/internal/server/routing"
	smartrouting "github.com/tingly-dev/tingly-box/internal/smart_routing"
	"github.com/tingly-dev/tingly-box/internal/typ"
)

// GenerateToken handles token generation requests
func (s *Server) GenerateToken(c *gin.Context) {
	var req struct {
		ClientID string `json:"client_id" binding:"required"`
	}

	if err := c.ShouldBindJSON(&req); err != nil {
		c.JSON(http.StatusBadRequest, ErrorResponse{
			Error: ErrorDetail{
				Message: "Invalid request body: " + err.Error(),
				Type:    "invalid_request_error",
			},
		})
		return
	}

	token, err := s.jwtManager.GenerateToken(req.ClientID)
	if err != nil {
		c.JSON(http.StatusInternalServerError, ErrorResponse{
			Error: ErrorDetail{
				Message: "Failed to generate token: " + err.Error(),
				Type:    "api_error",
			},
		})
		return
	}

	token = "tingly-box-" + token
	err = s.config.SetModelToken(token)
	if err != nil {
		c.JSON(http.StatusInternalServerError, ErrorResponse{
			Error: ErrorDetail{
				Message: "Failed to save token: " + err.Error(),
				Type:    "api_error",
			},
		})
		return
	}

	response := struct {
		Success bool          `json:"success"`
		Data    TokenResponse `json:"data"`
	}{
		Success: true,
		Data:    TokenResponse{Token: token, Type: "Bearer"},
	}

	c.JSON(http.StatusOK, response)
}

// GetToken handles token retrieval requests - generates a token if it doesn't exist
func (s *Server) GetToken(c *gin.Context) {
	globalConfig := s.config

	// Check if token already exists
	if globalConfig != nil && globalConfig.HasModelToken() {
		token := globalConfig.GetModelToken()
		c.JSON(http.StatusOK, gin.H{
			"token": token,
			"type":  "Bearer",
		})
		return
	}

	// Generate a new token if it doesn't exist
	// Use a default client ID for automatic token generation
	clientID := "auto-generated"
	token, err := s.jwtManager.GenerateToken(clientID)
	if err != nil {
		c.JSON(http.StatusInternalServerError, ErrorResponse{
			Error: ErrorDetail{
				Message: "Failed to generate token: " + err.Error(),
				Type:    "api_error",
			},
		})
		return
	}

	// Save the token to config
	token = "tingly-box-" + token
	err = globalConfig.SetModelToken(token)
	if err != nil {
		c.JSON(http.StatusInternalServerError, ErrorResponse{
			Error: ErrorDetail{
				Message: "Failed to save token: " + err.Error(),
				Type:    "api_error",
			},
		})
		return
	}

	response := struct {
		Success bool          `json:"success"`
		Data    TokenResponse `json:"data"`
	}{
		Success: true,
		Data:    TokenResponse{Token: token, Type: "Bearer"},
	}

	c.JSON(http.StatusOK, response)
}

// resolveSessionID returns the session identifier for the current request as a string.
// It delegates to routing.ResolveSessionID which checks (in priority order):
// Anthropic metadata.user_id > X-Tingly-Session-ID header > ClientIP fallback.
func resolveSessionID(c *gin.Context, req interface{}) typ.SessionID {
	return routing.ResolveSessionID(c, req)
}

// DetermineProviderAndModelWithScenario determines the provider and service for a request// based on the rule's configuration, smart routing rules, and optional affinity locking.
// sessionID is used for affinity locking when SmartAffinity is enabled.
//
// Deprecated: Use routing.SimpleSelector.SelectService() instead for cleaner code.
// This method is kept for backward compatibility with anthropic_token.go and passthrough.go.
// See internal/server/anthropic.go for migration example.
func (s *Server) DetermineProviderAndModelWithScenario(scenario typ.RuleScenario, rule *typ.Rule, req interface{}, sessionID string) (*typ.Provider, *loadbalance.Service, error) {
	modelName := rule.RequestModel
	c := s.config
	var selectedService *loadbalance.Service
	var err error

	// Affinity: check if we have a locked service for this session
	if rule.SmartEnabled && rule.SmartAffinity && sessionID != "" {
		if entry, ok := s.affinityStore.Get(rule.UUID, sessionID); ok {
			// Verify the locked provider still exists and is enabled
			provider, err := c.GetProviderByUUID(entry.Service.Provider)
			if err == nil && provider.Enabled {
				logrus.Infof("[affinity] using locked service for session %s: %s -> %s", sessionID, provider.Name, entry.Service.Model)
				return provider, entry.Service, nil
			}
			// Locked provider unavailable, clear affinity and fall through to smart routing
			logrus.Debugf("[affinity] locked provider unavailable, clearing affinity for session %s", sessionID)
			s.affinityStore.Delete(rule.UUID, sessionID)
		}
	}

	// Smart routing: check if enabled and try to match rules
	if rule.SmartEnabled && len(rule.SmartRouting) > 0 && req != nil {
		logrus.Debugf("[smart_routing] smart routing enabled for model %s", modelName)

		// Extract context from request (type switch handles different request types)
		ctx, err := s.ExtractRequestContext(req)
		if err == nil && ctx != nil {
			// Create router and evaluate
			router, err := smartrouting.NewRouter(rule.SmartRouting)
			if err == nil {
				if matchedServices, matched := router.EvaluateRequest(ctx); matched && len(matchedServices) > 0 {
					logrus.Debugf("[smart_routing] rule matched for model %s, selecting from %d services", modelName, len(matchedServices))
					// Select service from matched services using load balancing
					selectedService, err = s.SelectServiceFromSmartRouting(matchedServices, rule)
					if err == nil && selectedService != nil {
						// Verify the provider exists and is enabled
						provider, err := c.GetProviderByUUID(selectedService.Provider)
						if err == nil && provider.Enabled {
							logrus.Infof("[smart_routing] using smart routed service: %s -> %s", provider.Name, selectedService.Model)

							// Lock the service for this session if affinity is enabled
							if rule.SmartAffinity && sessionID != "" {
								s.affinityStore.Set(rule.UUID, sessionID, &routing.AffinityEntry{
									Service:   selectedService,
									LockedAt:  time.Now(),
									ExpiresAt: time.Now().Add(s.affinityStore.ttl),
								})
								logrus.Infof("[affinity] locked service %s -> %s for session %s", provider.Name, selectedService.Model, sessionID)
							}

							return provider, selectedService, nil
						}
					}
				} else {
					logrus.Debugf("[smart_routing] no rule matched, falling through to load balancer")
				}
			} else {
				logrus.Debugf("[smart_routing] failed to create router: %v", err)
			}
		} else {
			logrus.Debugf("[smart_routing] failed to extract context: %v", err)
		}
		// Fall through to normal load balancer on any error
	}

	// Normal load balancing path
	selectedService, err = s.loadBalancer.SelectService(rule)
	if err != nil {
		return nil, nil, fmt.Errorf("failed to select service: %w", err)
	}

	if selectedService == nil {
		return nil, nil, fmt.Errorf("no available service for request model '%s'", modelName)
	}

	// Verify the provider exists and is enabled
	provider, err := c.GetProviderByUUID(selectedService.Provider)
	if err != nil {
		return nil, nil, fmt.Errorf("provider '%s' not found: %w", selectedService.Provider, err)
	}

	if !provider.Enabled {
		return nil, nil, fmt.Errorf("provider '%s' is not enabled", selectedService.Provider)
	}

	// Update the current service index for the rule
	s.loadBalancer.UpdateServiceIndex(rule, selectedService)

	// Persist the updated CurrentServiceID to SQLite (not config.json)
	// This is critical for round-robin to work correctly across requests
	if err := c.SaveCurrentServiceID(rule.UUID, rule.CurrentServiceID); err != nil {
		// Log error but don't fail the request
		fmt.Printf("Warning: failed to persist CurrentServiceID: %v\n", err)
	}

	// Return provider, selected service, and rule
	return provider, selectedService, nil
}

// DetermineProviderAndModel resolves the model name and finds the appropriate provider using load balancing
//
// Deprecated: Use routing.SimpleSelector.SelectService() instead.
// This method is kept for backward compatibility with anthropic_token.go and passthrough.go.
// Currently used by:
//   - internal/server/anthropic_token.go:89
//   - internal/server/passthrough.go:81
func (s *Server) DetermineProviderAndModel(rule *typ.Rule) (*typ.Provider, *loadbalance.Service, error) {
	modelName := rule.RequestModel
	c := s.config

	// Set the rule in the context so middleware can use the same rule
	// We need to pass this context to the actual HTTP handler, but this function
	// doesn't have access to the Gin context. For now, we'll use a different approach.

	// Use the load balancer to select service
	selectedService, err := s.loadBalancer.SelectService(rule)
	if err != nil {
		return nil, nil, fmt.Errorf("failed to select service: %w", err)
	}

	if selectedService == nil {
		return nil, nil, fmt.Errorf("no available service for request model '%s'", modelName)
	}

	// Verify the provider exists and is enabled
	provider, err := c.GetProviderByUUID(selectedService.Provider)
	if err != nil {
		return nil, nil, fmt.Errorf("provider '%s' not found: %w", selectedService.Provider, err)
	}

	if !provider.Enabled {
		return nil, nil, fmt.Errorf("provider '%s' is not enabled", selectedService.Provider)
	}

	// Update the current service index for the rule
	s.loadBalancer.UpdateServiceIndex(rule, selectedService)

	// Persist the updated CurrentServiceID to SQLite (not config.json)
	// This is critical for round-robin to work correctly across requests
	if err := c.SaveCurrentServiceID(rule.UUID, rule.CurrentServiceID); err != nil {
		// Log error but don't fail the request
		fmt.Printf("Warning: failed to persist CurrentServiceID: %v\n", err)
	}

	// Return provider, selected service, and rule
	return provider, selectedService, nil
}

func (s *Server) determineRule(modelName string) (*typ.Rule, error) {
	c := s.config
	if c != nil && c.IsRequestModel(modelName) {

		// Get the Rule for this specific request model using the same method as middleware
		uuid := c.GetUUIDByRequestModel(modelName)
		rules := c.GetRequestConfigs()
		var rule *typ.Rule
		for i := range rules {
			if rules[i].UUID == uuid && rules[i].Active {
				rule = &rules[i] // Get pointer to actual rule in config
				return rule, nil
			}
		}
	}

	return nil, fmt.Errorf("provider or model not configured for request model '%s'", modelName)
}

func isEnterpriseContextPresent(c *gin.Context) bool {
	return strings.TrimSpace(c.GetString("enterprise_user_id")) != ""
}

func (s *Server) determineRuleWithScenario(ctx *gin.Context, scenario typ.RuleScenario, modelName string) (*typ.Rule, error) {
	cfg := s.config
	if cfg != nil {
		// Use the new MatchRuleByModelAndScenario which supports wildcard matching
		rule := cfg.MatchRuleByModelAndScenario(modelName, scenario)
		if rule != nil && rule.Active {
			return rule, nil
		}
		// Enterprise runtime context is already authorized by TBE.
		// If endpoint scenario has no matching rule, allow lookup by model across scenarios.
		if isEnterpriseContextPresent(ctx) {
			for _, anyRule := range cfg.GetRequestConfigs() {
				if anyRule.Active && anyRule.RequestModel == modelName {
					return &anyRule, nil
				}
			}
		}
	}

	return nil, fmt.Errorf("provider or model not configured for request model '%s'", modelName)
}
