package middleware

import (
	"fmt"
	"path/filepath"
	"strings"

	"github.com/gofiber/fiber/v2"
)

// PathJail middleware ensures all file path parameters are confined within
// the storage root. Any path that resolves outside the jail is rejected
// immediately, preventing directory traversal attacks.
func PathJail(storageRoot string) fiber.Handler {
	// Resolve the absolute jail root once at startup
	absRoot, err := filepath.Abs(storageRoot)
	if err != nil {
		panic(fmt.Sprintf("pathjail: invalid storage root: %v", err))
	}
	// Ensure it ends with separator for prefix matching
	absRootSlash := absRoot + string(filepath.Separator)

	return func(c *fiber.Ctx) error {
		// Extract the path parameter from the request.
		// Handlers attach the relative file path as "filepath" param or query.
		rawPath := c.Params("*")
		if rawPath == "" {
			rawPath = c.Query("path", "")
		}

		// If no path param, assume root path '.'
		if rawPath == "" {
			rawPath = "."
		}

		// Clean and resolve the path
		cleaned := filepath.Clean(rawPath)

		// Reject obvious traversal attempts before even joining
		if strings.Contains(cleaned, "..") {
			return c.Status(fiber.StatusForbidden).JSON(fiber.Map{
				"error": "path traversal detected",
			})
		}

		// Build the full absolute path within the jail
		fullPath := filepath.Join(absRoot, cleaned)
		absPath, err := filepath.Abs(fullPath)
		if err != nil {
			return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
				"error": "invalid path",
			})
		}

		// Final check: resolved path must be within the jail root
		if absPath != absRoot && !strings.HasPrefix(absPath, absRootSlash) {
			return c.Status(fiber.StatusForbidden).JSON(fiber.Map{
				"error": "access denied: path outside storage boundary",
			})
		}

		// Store the validated absolute path for downstream handlers
		c.Locals("resolvedPath", absPath)
		c.Locals("relativePath", cleaned)

		return c.Next()
	}
}
