package errors

import (
	"crypto/rand"
	"encoding/hex"
	"fmt"
	"net/http"
	"time"

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

// SecureErrorResponse represents a sanitized error response for clients
type SecureErrorResponse struct {
	Error     string `json:"error"`
	Message   string `json:"message"`
	RequestID string `json:"request_id,omitempty"`
	Code      int    `json:"code"`
}

// ErrorHandler provides secure error handling for HTTP responses
type ErrorHandler struct {
	logger *zap.Logger
}

// NewErrorHandler creates a new secure error handler
func NewErrorHandler(logger *zap.Logger) *ErrorHandler {
	return &ErrorHandler{
		logger: logger,
	}
}

// HandleError handles errors securely by logging detailed information and returning sanitized responses
func (eh *ErrorHandler) HandleError(c *gin.Context, err error, userMessage string) {
	requestID := eh.getOrGenerateRequestID(c)
	
	// Log detailed error information for internal debugging
	eh.logger.Error("HTTP request error",
		zap.String("request_id", requestID),
		zap.String("path", c.Request.URL.Path),
		zap.String("method", c.Request.Method),
		zap.String("user_agent", c.Request.UserAgent()),
		zap.String("remote_addr", c.ClientIP()),
		zap.Error(err),
	)

	// Determine appropriate HTTP status code and error type
	statusCode, errorType := eh.determineErrorResponse(err)
	
	// Create sanitized response
	response := SecureErrorResponse{
		Error:     errorType,
		Message:   eh.sanitizeErrorMessage(userMessage, err),
		RequestID: requestID,
		Code:      statusCode,
	}

	c.JSON(statusCode, response)
}

// HandleValidationError handles input validation errors
func (eh *ErrorHandler) HandleValidationError(c *gin.Context, field string, message string) {
	requestID := eh.getOrGenerateRequestID(c)
	
	eh.logger.Warn("Validation error",
		zap.String("request_id", requestID),
		zap.String("field", field),
		zap.String("path", c.Request.URL.Path),
		zap.String("method", c.Request.Method),
	)

	response := SecureErrorResponse{
		Error:     "validation_error",
		Message:   "Invalid input provided",
		RequestID: requestID,
		Code:      http.StatusBadRequest,
	}

	c.JSON(http.StatusBadRequest, response)
}

// HandleAuthenticationError handles authentication failures
func (eh *ErrorHandler) HandleAuthenticationError(c *gin.Context, err error) {
	requestID := eh.getOrGenerateRequestID(c)
	
	eh.logger.Warn("Authentication error",
		zap.String("request_id", requestID),
		zap.String("path", c.Request.URL.Path),
		zap.String("method", c.Request.Method),
		zap.String("remote_addr", c.ClientIP()),
		zap.Error(err),
	)

	response := SecureErrorResponse{
		Error:     "authentication_failed",
		Message:   "Authentication required",
		RequestID: requestID,
		Code:      http.StatusUnauthorized,
	}

	c.JSON(http.StatusUnauthorized, response)
}

// HandleAuthorizationError handles authorization failures
func (eh *ErrorHandler) HandleAuthorizationError(c *gin.Context, resource string) {
	requestID := eh.getOrGenerateRequestID(c)
	
	eh.logger.Warn("Authorization error",
		zap.String("request_id", requestID),
		zap.String("resource", resource),
		zap.String("path", c.Request.URL.Path),
		zap.String("method", c.Request.Method),
		zap.String("remote_addr", c.ClientIP()),
	)

	response := SecureErrorResponse{
		Error:     "access_denied",
		Message:   "Insufficient permissions",
		RequestID: requestID,
		Code:      http.StatusForbidden,
	}

	c.JSON(http.StatusForbidden, response)
}

// HandleInternalError handles internal server errors
func (eh *ErrorHandler) HandleInternalError(c *gin.Context, err error) {
	requestID := eh.getOrGenerateRequestID(c)
	
	eh.logger.Error("Internal server error",
		zap.String("request_id", requestID),
		zap.String("path", c.Request.URL.Path),
		zap.String("method", c.Request.Method),
		zap.String("remote_addr", c.ClientIP()),
		zap.Error(err),
	)

	response := SecureErrorResponse{
		Error:     "internal_error",
		Message:   "An internal error occurred",
		RequestID: requestID,
		Code:      http.StatusInternalServerError,
	}

	c.JSON(http.StatusInternalServerError, response)
}

// HandleNotFoundError handles resource not found errors
func (eh *ErrorHandler) HandleNotFoundError(c *gin.Context, resource string, message string) {
	requestID := eh.getOrGenerateRequestID(c)
	
	eh.logger.Warn("Resource not found",
		zap.String("request_id", requestID),
		zap.String("resource", resource),
		zap.String("path", c.Request.URL.Path),
		zap.String("method", c.Request.Method),
		zap.String("remote_addr", c.ClientIP()),
	)

	response := SecureErrorResponse{
		Error:     "resource_not_found",
		Message:   message,
		RequestID: requestID,
		Code:      http.StatusNotFound,
	}

	c.JSON(http.StatusNotFound, response)
}

// determineErrorResponse determines the appropriate HTTP status and error type
func (eh *ErrorHandler) determineErrorResponse(err error) (int, string) {
	if appErr, ok := err.(*AppError); ok {
		return appErr.StatusCode, eh.getErrorTypeFromCode(appErr.Code)
	}
	
	// For unknown errors, log as internal error but don't expose details
	return http.StatusInternalServerError, "internal_error"
}

// sanitizeErrorMessage removes sensitive information from error messages
func (eh *ErrorHandler) sanitizeErrorMessage(userMessage string, err error) string {
	if userMessage != "" {
		return userMessage
	}

	// Provide generic messages for different error types
	if appErr, ok := err.(*AppError); ok {
		return eh.getGenericMessageFromCode(appErr.Code)
	}
	
	return "An error occurred"
}

// getErrorTypeFromCode converts an error code to a sanitized error type string
func (eh *ErrorHandler) getErrorTypeFromCode(code ErrorCode) string {
	switch code {
	case ErrValidationFailed, ErrInvalidInput, ErrMissingField, ErrInvalidFormat:
		return "validation_error"
	case ErrUnauthorized, ErrInvalidToken, ErrTokenExpired, ErrInvalidCredentials:
		return "authentication_failed"
	case ErrForbidden, ErrInsufficientPermissions:
		return "access_denied"
	case ErrNotFound, ErrApplicationNotFound, ErrTokenNotFound, ErrPermissionNotFound:
		return "resource_not_found"
	case ErrAlreadyExists, ErrConflict:
		return "resource_conflict"
	case ErrRateLimit:
		return "rate_limit_exceeded"
	case ErrTimeout:
		return "timeout"
	default:
		return "internal_error"
	}
}

// getGenericMessageFromCode provides generic user-safe messages for error codes
func (eh *ErrorHandler) getGenericMessageFromCode(code ErrorCode) string {
	switch code {
	case ErrValidationFailed, ErrInvalidInput, ErrMissingField, ErrInvalidFormat:
		return "Invalid input provided"
	case ErrUnauthorized, ErrInvalidToken, ErrTokenExpired, ErrInvalidCredentials:
		return "Authentication required"
	case ErrForbidden, ErrInsufficientPermissions:
		return "Access denied"
	case ErrNotFound, ErrApplicationNotFound, ErrTokenNotFound, ErrPermissionNotFound:
		return "Resource not found"
	case ErrAlreadyExists, ErrConflict:
		return "Resource conflict"
	case ErrRateLimit:
		return "Rate limit exceeded"
	case ErrTimeout:
		return "Request timeout"
	default:
		return "An error occurred"
	}
}

// getOrGenerateRequestID gets or generates a request ID for tracking
func (eh *ErrorHandler) getOrGenerateRequestID(c *gin.Context) string {
	// Try to get existing request ID from context
	if requestID, exists := c.Get("request_id"); exists {
		if id, ok := requestID.(string); ok {
			return id
		}
	}
	
	// Try to get from header
	requestID := c.GetHeader("X-Request-ID")
	if requestID != "" {
		return requestID
	}
	
	// Generate a simple request ID (in production, use a proper UUID library)
	return generateSimpleID()
}

// generateSimpleID generates a simple request ID
func generateSimpleID() string {
	// Simple implementation - in production use proper UUID generation
	bytes := make([]byte, 8)
	if _, err := rand.Read(bytes); err != nil {
		// Fallback to timestamp-based ID
		return fmt.Sprintf("req_%d", time.Now().UnixNano())
	}
	return "req_" + hex.EncodeToString(bytes)
}