package middleware

import (
	"net/http"
	"net/url"
	"strings"

	"github.com/gofiber/fiber/v2"

	"wucher/internal/service"
	"wucher/internal/shared/pkg/apperrorsx"
	"wucher/internal/shared/pkg/uuidv7"
)

const (
	wopiAccessTokenQuery = "access_token"
	wopiFileIDParam      = "fileId"
)

func WOPIRequired(svc *service.WOPIService) fiber.Handler {
	return func(c *fiber.Ctx) error {
		if svc == nil {
			return apperrorsx.Write(c, apperrorsx.New(apperrorsx.ErrFileManagerWOPIServiceUnavailable))
		}

		fileID, err := parseWOPIFileID(c)
		if err != nil {
			return apperrorsx.Write(c, apperrorsx.New(apperrorsx.ErrFileManagerWOPIInvalidFileID))
		}

		token := firstNonEmptyWOPIQueryValue(c, wopiAccessTokenQuery)
		if token == "" {
			token = firstNonEmptyWOPISrcQueryValue(c, wopiAccessTokenQuery)
		}
		if token == "" {
			authz := strings.TrimSpace(c.Get("Authorization"))
			if strings.HasPrefix(strings.ToLower(authz), "bearer ") {
				token = strings.TrimSpace(authz[7:])
			}
		}
		claims, err := svc.VerifyAccessToken(token, fileID)
		if err != nil {
			return apperrorsx.Write(c, apperrorsx.New(apperrorsx.ErrFileManagerWOPIInvalidToken).WithCause(err))
		}

		if err := svc.VerifyProof(c.Method(), c.Path(), token, c.Get("X-WOPI-TimeStamp"), c.Get("X-WOPI-Proof"), c.Get("X-WOPI-ProofOld")); err != nil {
			return apperrorsx.Write(c, apperrorsx.New(apperrorsx.ErrFileManagerWOPIInvalidProof).WithCause(err))
		}

		userID, _ := uuidv7.ParsePathLikeString(claims.UserID)
		c.Locals("user_id", userID)
		c.Locals("wopi_claims", claims)
		c.Locals("wopi_permission", strings.ToLower(strings.TrimSpace(claims.Permission)))
		c.Locals("wopi_takeover_id", claims.TakeoverID)
		c.Locals("wopi_access_token", token)
		return c.Next()
	}
}

func firstNonEmptyWOPIQueryValue(c *fiber.Ctx, key string) string {
	if c == nil || strings.TrimSpace(key) == "" {
		return ""
	}
	found := ""
	c.Context().QueryArgs().VisitAll(func(k, v []byte) {
		if found != "" {
			return
		}
		if !strings.EqualFold(string(k), key) {
			return
		}
		value := strings.TrimSpace(string(v))
		if value != "" {
			found = value
		}
	})
	if found != "" {
		return found
	}
	return strings.TrimSpace(c.Query(key))
}

func firstNonEmptyWOPISrcQueryValue(c *fiber.Ctx, key string) string {
	if c == nil || strings.TrimSpace(key) == "" {
		return ""
	}
	src := strings.TrimSpace(c.Query("WOPISrc"))
	if src == "" {
		return ""
	}
	decoded := src
	if unescaped, err := url.QueryUnescape(src); err == nil && strings.TrimSpace(unescaped) != "" {
		decoded = strings.TrimSpace(unescaped)
	}
	parsed, err := url.Parse(decoded)
	if err != nil {
		return ""
	}
	query := parsed.Query()
	if value := strings.TrimSpace(query.Get(key)); value != "" {
		return value
	}
	return ""
}

func parseWOPIFileID(c *fiber.Ctx) ([]byte, error) {
	if c == nil {
		return nil, fiber.ErrBadRequest
	}
	candidates := []string{
		c.Params(wopiFileIDParam),
		c.Path(),
		c.OriginalURL(),
		c.Query("WOPISrc"),
	}
	for _, candidate := range candidates {
		if fileID, err := uuidv7.ParsePathLikeString(candidate); err == nil {
			return fileID, nil
		}
	}
	return nil, fiber.ErrBadRequest
}

func WOPIRequestBodyLimit(maxBytes int) fiber.Handler {
	return func(c *fiber.Ctx) error {
		if maxBytes <= 0 {
			return c.Next()
		}
		if c.Method() == http.MethodPost || c.Method() == http.MethodPut {
			if c.Request().Header.ContentLength() > maxBytes {
				return apperrorsx.Write(c, apperrorsx.New(apperrorsx.ErrFileManagerWOPIValidationFailed))
			}
		}
		return c.Next()
	}
}

func WOPIPermissionRequired(required service.WOPIPermission) fiber.Handler {
	return func(c *fiber.Ctx) error {
		if strings.TrimSpace(string(required)) == "" {
			return c.Next()
		}
		raw := c.Locals("wopi_permission")
		current, _ := raw.(string)
		if !wopiPermissionAllowed(current, string(required)) {
			return apperrorsx.Write(c, apperrorsx.New(apperrorsx.ErrFileManagerWOPIPermissionDenied))
		}
		return c.Next()
	}
}

func wopiPermissionAllowed(current, required string) bool {
	switch strings.ToLower(strings.TrimSpace(required)) {
	case string(service.WOPIPermissionRead):
		return wopiPermissionRank(current) >= wopiPermissionRank(string(service.WOPIPermissionRead))
	case string(service.WOPIPermissionWrite):
		return wopiPermissionRank(current) >= wopiPermissionRank(string(service.WOPIPermissionWrite))
	default:
		return false
	}
}

func wopiPermissionRank(v string) int {
	switch strings.ToLower(strings.TrimSpace(v)) {
	case string(service.WOPIPermissionRead):
		return 1
	case string(service.WOPIPermissionWrite):
		return 2
	default:
		return 0
	}
}
