package handlers

import (
	"net/http"
	"strconv"

	"github.com/gin-gonic/gin"
	"go.uber.org/zap"

	"github.com/RyanCopley/skybridge/kms/internal/authorization"
	"github.com/RyanCopley/skybridge/kms/internal/domain"
	"github.com/RyanCopley/skybridge/kms/internal/errors"
	"github.com/RyanCopley/skybridge/kms/internal/services"
	"github.com/RyanCopley/skybridge/kms/internal/validation"
)

// ApplicationHandler handles application-related HTTP requests
type ApplicationHandler struct {
	appService    services.ApplicationService
	authService   services.AuthenticationService
	authzService  *authorization.AuthorizationService
	validator     *validation.Validator
	errorHandler  *errors.ErrorHandler
	logger        *zap.Logger
}

// NewApplicationHandler creates a new application handler
func NewApplicationHandler(
	appService services.ApplicationService,
	authService services.AuthenticationService,
	logger *zap.Logger,
) *ApplicationHandler {
	return &ApplicationHandler{
		appService:   appService,
		authService:  authService,
		authzService: authorization.NewAuthorizationService(logger),
		validator:    validation.NewValidator(logger),
		errorHandler: errors.NewErrorHandler(logger),
		logger:       logger,
	}
}

// Create handles POST /applications
func (h *ApplicationHandler) Create(c *gin.Context) {
	var req domain.CreateApplicationRequest
	if err := c.ShouldBindJSON(&req); err != nil {
		h.errorHandler.HandleValidationError(c, "request_body", "Invalid application request format")
		return
	}

	// Get user ID from authenticated context
	userID := h.getUserIDFromContext(c)
	if userID == "" {
		h.errorHandler.HandleAuthenticationError(c, errors.NewUnauthorizedError("User authentication required"))
		return
	}

	// Validate input (skip permissions validation for application creation)
	var validationErrors []validation.ValidationError
	
	// Validate app ID
	if result := h.validator.ValidateAppID(req.AppID); !result.Valid {
		validationErrors = append(validationErrors, result.Errors...)
	}
	
	// Validate app link URL
	if result := h.validator.ValidateURL(req.AppLink, "app_link"); !result.Valid {
		validationErrors = append(validationErrors, result.Errors...)
	}
	
	// Validate callback URL
	if result := h.validator.ValidateURL(req.CallbackURL, "callback_url"); !result.Valid {
		validationErrors = append(validationErrors, result.Errors...)
	}
	
	// Validate token prefix if provided
	if result := h.validator.ValidateTokenPrefix(req.TokenPrefix); !result.Valid {
		validationErrors = append(validationErrors, result.Errors...)
	}
	
	if len(validationErrors) > 0 {
		h.logger.Warn("Application validation failed", 
			zap.String("user_id", userID),
			zap.Any("errors", validationErrors))
		h.errorHandler.HandleValidationError(c, "validation", "Invalid application data")
		return
	}

	// Check authorization for creating applications
	authCtx := &authorization.AuthorizationContext{
		UserID:       userID,
		ResourceType: authorization.ResourceTypeApplication,
		Action:       authorization.ActionCreate,
	}
	
	if err := h.authzService.AuthorizeResourceAccess(c.Request.Context(), authCtx); err != nil {
		h.errorHandler.HandleAuthorizationError(c, "application creation")
		return
	}

	// Create the application
	app, err := h.appService.Create(c.Request.Context(), &req, userID)
	if err != nil {
		h.errorHandler.HandleInternalError(c, err)
		return
	}

	h.logger.Info("Application created successfully", 
		zap.String("app_id", app.AppID),
		zap.String("user_id", userID))

	c.JSON(http.StatusCreated, app)
}

// getUserIDFromContext extracts user ID from Gin context
func (h *ApplicationHandler) getUserIDFromContext(c *gin.Context) string {
	// Try to get from Gin context first (set by middleware)
	if userID, exists := c.Get("user_id"); exists {
		if id, ok := userID.(string); ok {
			return id
		}
	}

	// Fallback to header (for compatibility)
	userEmail := c.GetHeader("X-User-Email")
	if userEmail != "" {
		return userEmail
	}

	return ""
}

// GetByID handles GET /applications/:id
func (h *ApplicationHandler) GetByID(c *gin.Context) {
	appID := c.Param("id")
	
	// Get user ID from context
	userID := h.getUserIDFromContext(c)
	if userID == "" {
		h.errorHandler.HandleAuthenticationError(c, errors.NewUnauthorizedError("User authentication required"))
		return
	}

	// Validate app ID
	if result := h.validator.ValidateAppID(appID); !result.Valid {
		h.errorHandler.HandleValidationError(c, "app_id", "Invalid application ID")
		return
	}

	// Get the application first
	app, err := h.appService.GetByID(c.Request.Context(), appID)
	if err != nil {
		h.logger.Error("Failed to get application", zap.Error(err), zap.String("app_id", appID))
		h.errorHandler.HandleError(c, err, "Application not found")
		return
	}

	// Check authorization for reading this application
	if err := h.authzService.AuthorizeApplicationOwnership(userID, app); err != nil {
		h.errorHandler.HandleAuthorizationError(c, "application access")
		return
	}

	c.JSON(http.StatusOK, app)
}

// List handles GET /applications
func (h *ApplicationHandler) List(c *gin.Context) {
	// Parse pagination parameters
	limit := 50
	offset := 0

	if l := c.Query("limit"); l != "" {
		if parsed, err := strconv.Atoi(l); err == nil && parsed > 0 {
			limit = parsed
		}
	}

	if o := c.Query("offset"); o != "" {
		if parsed, err := strconv.Atoi(o); err == nil && parsed >= 0 {
			offset = parsed
		}
	}

	apps, err := h.appService.List(c.Request.Context(), limit, offset)
	if err != nil {
		h.logger.Error("Failed to list applications", zap.Error(err))
		c.JSON(http.StatusInternalServerError, gin.H{
			"error":   "Internal Server Error",
			"message": "Failed to list applications",
		})
		return
	}

	c.JSON(http.StatusOK, gin.H{
		"data":   apps,
		"limit":  limit,
		"offset": offset,
		"count":  len(apps),
	})
}

// Update handles PUT /applications/:id
func (h *ApplicationHandler) Update(c *gin.Context) {
	appID := c.Param("id")
	if appID == "" {
		c.JSON(http.StatusBadRequest, gin.H{
			"error":   "Bad Request",
			"message": "Application ID is required",
		})
		return
	}

	var req domain.UpdateApplicationRequest
	if err := c.ShouldBindJSON(&req); err != nil {
		h.logger.Warn("Invalid request body", zap.Error(err))
		c.JSON(http.StatusBadRequest, gin.H{
			"error":   "Bad Request",
			"message": "Invalid request body: " + err.Error(),
		})
		return
	}

	// Get user ID from context
	userID, exists := c.Get("user_id")
	if !exists {
		h.logger.Error("User ID not found in context")
		c.JSON(http.StatusInternalServerError, gin.H{
			"error":   "Internal Server Error",
			"message": "Authentication context not found",
		})
		return
	}

	app, err := h.appService.Update(c.Request.Context(), appID, &req, userID.(string))
	if err != nil {
		h.logger.Error("Failed to update application", zap.Error(err), zap.String("app_id", appID))
		c.JSON(http.StatusInternalServerError, gin.H{
			"error":   "Internal Server Error",
			"message": "Failed to update application",
		})
		return
	}

	h.logger.Info("Application updated", zap.String("app_id", appID))
	c.JSON(http.StatusOK, app)
}

// Delete handles DELETE /applications/:id
func (h *ApplicationHandler) Delete(c *gin.Context) {
	appID := c.Param("id")
	if appID == "" {
		c.JSON(http.StatusBadRequest, gin.H{
			"error":   "Bad Request",
			"message": "Application ID is required",
		})
		return
	}

	// Get user ID from context
	userID, exists := c.Get("user_id")
	if !exists {
		h.logger.Error("User ID not found in context")
		c.JSON(http.StatusInternalServerError, gin.H{
			"error":   "Internal Server Error",
			"message": "Authentication context not found",
		})
		return
	}

	err := h.appService.Delete(c.Request.Context(), appID, userID.(string))
	if err != nil {
		h.logger.Error("Failed to delete application", zap.Error(err), zap.String("app_id", appID))
		c.JSON(http.StatusInternalServerError, gin.H{
			"error":   "Internal Server Error",
			"message": "Failed to delete application",
		})
		return
	}

	h.logger.Info("Application deleted", zap.String("app_id", appID))
	c.JSON(http.StatusNoContent, nil)
}
