package middleware

import (
	"net/http"
	"os"
	"strconv"
	"strings"

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

// InputGateConfig holds configuration for input validation.
type InputGateConfig struct {
	// Maximum request body size (default: 10MB)
	MaxBodySize int64

	// Maximum file upload size (default: 50MB)
	MaxFileSize int64

	// Allowed content types
	AllowedContentTypes map[string]bool

	// Allowed file types for uploads
	AllowedFileTypes map[string]bool

	// Blocked file extensions
	BlockedExtensions map[string]bool

	// Paths that allow larger uploads
	LargeUploadPaths []string

	// Paths excluded from validation
	ExcludedPaths []string

	// Enable strict content type checking
	StrictContentType bool
}

// DefaultInputGateConfig returns sensible default configuration.
func DefaultInputGateConfig() InputGateConfig {
	maxSize := int64(10 * 1024 * 1024) // 10MB
	if envSize := os.Getenv("MAX_REQUEST_BODY_SIZE"); envSize != "" {
		if size, err := strconv.ParseInt(envSize, 10, 64); err == nil {
			maxSize = size
		}
	}

	return InputGateConfig{
		MaxBodySize: maxSize,
		MaxFileSize: 50 * 1024 * 1024, // 50MB
		AllowedContentTypes: map[string]bool{
			"application/json":                  true,
			"application/x-www-form-urlencoded": true,
			"multipart/form-data":               true,
			"text/plain":                        true,
		},
		AllowedFileTypes: map[string]bool{
			"image/jpeg":      true,
			"image/png":       true,
			"image/gif":       true,
			"image/webp":      true,
			"application/pdf": true,
			"text/csv":        true,
			"application/msword": true,
			"application/vnd.openxmlformats-officedocument.wordprocessingml.document": true,
			"application/vnd.ms-excel": true,
			"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet": true,
		},
		BlockedExtensions: map[string]bool{
			".exe": true, ".bat": true, ".cmd": true, ".com": true, ".msi": true,
			".dll": true, ".scr": true, ".pif": true, ".vbs": true, ".js": true,
			".jar": true, ".sh": true, ".ps1": true, ".app": true,
		},
		LargeUploadPaths: []string{
			"/api/v1/files/upload",
			"/api/v1/documents/upload",
			"/api/v1/attachments",
		},
		ExcludedPaths: []string{
			"/health",
			"/metrics",
			"/api/v1/health",
		},
		StrictContentType: true,
	}
}

// isExcludedPath checks if path is excluded from validation.
func (c *InputGateConfig) isExcludedPath(path string) bool {
	for _, excluded := range c.ExcludedPaths {
		if path == excluded {
			return true
		}
	}
	return false
}

// isLargeUploadPath checks if path allows larger uploads.
func (c *InputGateConfig) isLargeUploadPath(path string) bool {
	for _, uploadPath := range c.LargeUploadPaths {
		if strings.HasPrefix(path, uploadPath) {
			return true
		}
	}
	return false
}

// getMaxSize returns the maximum allowed body size for the path.
func (c *InputGateConfig) getMaxSize(path string) int64 {
	if c.isLargeUploadPath(path) {
		return c.MaxFileSize
	}
	return c.MaxBodySize
}

// validateContentType validates the content type.
func (c *InputGateConfig) validateContentType(contentType string) (bool, string) {
	if contentType == "" {
		return true, ""
	}

	// Extract base content type (remove charset, boundary, etc.)
	baseType := strings.Split(contentType, ";")[0]
	baseType = strings.TrimSpace(strings.ToLower(baseType))

	if !c.AllowedContentTypes[baseType] {
		return false, "Content-Type '" + baseType + "' is not allowed"
	}

	return true, ""
}

// hasBlockedExtension checks if filename has a blocked extension.
func (c *InputGateConfig) hasBlockedExtension(filename string) bool {
	if filename == "" {
		return false
	}

	lowerFilename := strings.ToLower(filename)
	for ext := range c.BlockedExtensions {
		if strings.HasSuffix(lowerFilename, ext) {
			return true
		}
	}
	return false
}

// InputGate returns a middleware that validates incoming request bodies.
//
// Usage:
//
//	r.Use(middleware.InputGate())
//
//	// Or with custom config:
//	config := middleware.DefaultInputGateConfig()
//	config.MaxBodySize = 5 * 1024 * 1024 // 5MB
//	r.Use(middleware.InputGateWithConfig(config))
func InputGate() gin.HandlerFunc {
	return InputGateWithConfig(DefaultInputGateConfig())
}

// InputGateWithConfig returns an input gate middleware with custom configuration.
func InputGateWithConfig(config InputGateConfig) gin.HandlerFunc {
	return func(c *gin.Context) {
		// Skip excluded paths
		if config.isExcludedPath(c.Request.URL.Path) {
			c.Next()
			return
		}

		// Skip validation for GET, HEAD, OPTIONS requests
		method := c.Request.Method
		if method == "GET" || method == "HEAD" || method == "OPTIONS" {
			c.Next()
			return
		}

		// Validate content type for requests with body
		contentType := c.GetHeader("Content-Type")
		if config.StrictContentType {
			valid, errMsg := config.validateContentType(contentType)
			if !valid {
				c.AbortWithStatusJSON(http.StatusUnsupportedMediaType, gin.H{
					"error":   "unsupported_media_type",
					"message": errMsg,
				})
				return
			}
		}

		// Check Content-Length header
		contentLength := c.GetHeader("Content-Length")
		if contentLength != "" {
			length, err := strconv.ParseInt(contentLength, 10, 64)
			if err != nil {
				c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{
					"error":   "invalid_content_length",
					"message": "Invalid Content-Length header",
				})
				return
			}

			maxSize := config.getMaxSize(c.Request.URL.Path)
			if length > maxSize {
				c.AbortWithStatusJSON(http.StatusRequestEntityTooLarge, gin.H{
					"error":    "payload_too_large",
					"message":  "Request body exceeds maximum size",
					"max_size": maxSize,
				})
				return
			}
		}

		// Set max multipart memory for file uploads
		if strings.Contains(contentType, "multipart/form-data") {
			c.Request.Body = http.MaxBytesReader(c.Writer, c.Request.Body, config.MaxFileSize)
		}

		c.Next()
	}
}

// ValidateFileUpload validates a file upload.
// Use this in upload handlers for detailed validation.
func ValidateFileUpload(filename, contentType string, size int64, config *InputGateConfig) (bool, string) {
	if config == nil {
		defaultConfig := DefaultInputGateConfig()
		config = &defaultConfig
	}

	// Check size
	if size > config.MaxFileSize {
		return false, "File size exceeds maximum allowed"
	}

	// Check extension
	if config.hasBlockedExtension(filename) {
		return false, "File extension is not allowed"
	}

	// Check content type
	if contentType != "" && !config.AllowedFileTypes[contentType] {
		return false, "File type '" + contentType + "' is not allowed"
	}

	return true, ""
}
