package router

import (
	"encoding/binary"
	"math"
	"net/http"
	"time"

	"github.com/caarlos0/httperr"
	"github.com/gin-gonic/gin"
	swaggerFiles "github.com/swaggo/files"
	ginSwagger "github.com/swaggo/gin-swagger"
	"go.uber.org/zap"

	_ "github.com/tidbcloud/inference-service/docs" // Import docs for swagger
	"github.com/tidbcloud/inference-service/internal/config"
	"github.com/tidbcloud/inference-service/internal/metrics"
	"github.com/tidbcloud/inference-service/internal/service"
	"github.com/tidbcloud/inference-service/internal/version"
)

// Handler handles inference service HTTP requests
type Handler struct {
	bedrockService *service.BedrockService
	usageService   *service.UsageService
}

// NewHandler creates a new Handler
func NewHandler(bedrockService *service.BedrockService, usageService *service.UsageService) *Handler {
	return &Handler{
		bedrockService: bedrockService,
		usageService:   usageService,
	}
}

// CreateEmbeddings handles POST /api/v1/inference/embeddings/{tenantId}
// @Summary Create embeddings
// @Description Generate embeddings for the given texts using the specified model
// @Tags embeddings
// @Accept json
// @Produce json
// @Param tenantId path string true "Tenant ID"
// @Param request body service.EmbeddingRequest true "Embedding request"
// @Success 200 {object} service.EmbeddingResponse
// @Failure 400 {object} service.ErrorResponse
// @Failure 429 {object} service.ErrorResponse
// @Failure 500 {object} service.ErrorResponse
// @Router /api/v1/inference/embeddings/{tenantId} [post]
func (h *Handler) CreateEmbeddings(c *gin.Context) {
	start := time.Now()
	tenantID := c.Param("tenantId")
	if tenantID == "" {
		c.Error(httperr.Errorf(http.StatusBadRequest, "tenant ID is required"))
		return
	}
	var req service.EmbeddingRequest
	if err := c.ShouldBindJSON(&req); err != nil {
		c.Error(httperr.Errorf(http.StatusBadRequest, "Invalid request body: %v", err))
		return
	}
	// Validate request
	if req.Model == "" {
		c.Error(httperr.Errorf(http.StatusBadRequest, "Model is required"))
		return
	}
	if len(req.Texts) == 0 {
		c.Error(httperr.Errorf(http.StatusBadRequest, "Texts array cannot be empty"))
		return
	}
	if len(req.Texts) > 100 {
		c.Error(httperr.Errorf(http.StatusBadRequest, "Too many batched texts in request (max: 100)"))
		return
	}
	if _, exists := config.Global().Models[req.Model]; !exists {
		c.Error(httperr.Errorf(http.StatusBadRequest, "Unknown model '%s'", req.Model))
		return
	}

	// Check quota
	if err := h.usageService.CheckQuota(tenantID, 1); err != nil {
		metrics.EmbeddingAPIFailure.WithLabelValues(req.Model, "quota_exceeded").Inc()
		zap.L().Warn("Quota exceeded for tenant",
			zap.String("tenant_id", tenantID),
			zap.String("model", req.Model),
			zap.Error(err))
		c.Error(err)
		return
	}

	// Generate embeddings
	embeddings, costMicroDollars, err := h.bedrockService.GenerateEmbeddings(c.Request.Context(), &req)
	if err != nil {
		metrics.EmbeddingAPIFailure.WithLabelValues(req.Model, "generate_failed").Inc()
		c.Error(err)
		return
	}

	// Estimate token count for metrics and logging (reverse calculation)
	modelConfig := config.Global().Models[req.Model]
	totalTokens := int(costMicroDollars * 1000 / int64(modelConfig.MicroDollarPerKToken))
	h.usageService.RecordUsage(tenantID, req.Model, costMicroDollars, totalTokens)

	// Build response
	response := buildEmbeddingResponse(embeddings, totalTokens)

	zap.L().Info("Successfully generated embeddings",
		zap.String("tenant_id", tenantID),
		zap.String("model", req.Model),
		zap.Int("text_count", len(req.Texts)),
		zap.Int("total_tokens", totalTokens),
		zap.Float64("cost_dollars", float64(costMicroDollars)/1_000_000))

	// Record metrics for successful request
	duration := time.Since(start).Seconds()
	metrics.EmbeddingAPISuccess.WithLabelValues(req.Model).Inc()
	metrics.EmbeddingAPILatency.WithLabelValues(req.Model).Observe(duration)
	metrics.EmbeddingAPIUsedMicroDollars.WithLabelValues(req.Model).Add(float64(costMicroDollars))

	c.JSON(http.StatusOK, response)
}

// GetTenantQuotaUsage handles GET /api/v1/inference/quota_usage/{tenantId}
// @Summary Get tenant quota usage
// @Description Get quota usage information for a specific tenant
// @Tags quota
// @Produce json
// @Param tenantId path string true "Tenant ID"
// @Success 200 {object} service.QuotaUsageResponse
// @Failure 400 {object} service.ErrorResponse
// @Router /api/v1/inference/quota_usage/{tenantId} [get]
func (h *Handler) GetTenantQuotaUsage(c *gin.Context) {
	tenantID := c.Param("tenantId")
	if tenantID == "" {
		c.Error(httperr.Errorf(http.StatusBadRequest, "tenant ID is required"))
		return
	}

	usage := h.usageService.GetTenantUsage(tenantID)
	c.JSON(http.StatusOK, usage)
}

// GetAllQuotaUsage handles GET /api/v1/inference/quota_usage
// @Summary Get all quota usage
// @Description Get quota usage information for all tenants
// @Tags quota
// @Produce json
// @Success 200 {object} service.AllQuotaUsageResponse
// @Router /api/v1/inference/quota_usage [get]
func (h *Handler) GetAllQuotaUsage(c *gin.Context) {
	usage := h.usageService.GetAllTenantUsage()
	c.JSON(http.StatusOK, usage)
}

// buildEmbeddingResponse builds the embedding response in binary format
func buildEmbeddingResponse(embeddings [][]float32, totalTokens int) *service.EmbeddingResponse {
	binaryEmbeddings := make([][]byte, len(embeddings))
	for i, embedding := range embeddings {
		binaryEmbeddings[i] = make([]byte, len(embedding)*4)
		for j, val := range embedding {
			binary.LittleEndian.PutUint32(binaryEmbeddings[i][j*4:], math.Float32bits(val))
		}
	}
	return &service.EmbeddingResponse{
		Embeddings: binaryEmbeddings,
		Usage: service.EmbeddingUsageInfo{
			PromptTokens: totalTokens,
			TotalTokens:  totalTokens,
		},
	}
}

// NewRouter creates a new router with all routes configured.
func NewRouter(bedrockService *service.BedrockService, usageService *service.UsageService) *gin.Engine {
	r := gin.New()
	r.Use(gin.Recovery())
	r.Use(mCatchError)

	handler := NewHandler(bedrockService, usageService)

	// API routes
	v1 := r.Group("/api/v1/inference")
	{
		// Embedding endpoints - require service API key
		v1.POST("/embeddings/:tenantId", RequireServiceAPIKey(), handler.CreateEmbeddings)

		// Quota usage endpoints - require management API key
		v1.GET("/quota_usage/:tenantId", RequireManagementAPIKey(), handler.GetTenantQuotaUsage)
		v1.GET("/quota_usage", RequireManagementAPIKey(), handler.GetAllQuotaUsage)
	}

	// Health check
	r.GET("/healthz", func(c *gin.Context) {
		c.JSON(http.StatusOK, gin.H{"status": "ok"})
	})

	// Version information
	r.GET("/version", func(c *gin.Context) {
		c.JSON(http.StatusOK, version.GetBuildInfo())
	})

	// Swagger documentation for development
	r.GET("/swagger/*any", ginSwagger.WrapHandler(swaggerFiles.Handler))

	return r
}
