package cms

import (
	"fmt"
	"regexp"
	"strings"

	"github.com/asaskevich/govalidator"
	"github.com/labstack/echo/v4"
)

var (
	objectIDPattern = regexp.MustCompile(`^[a-fA-F0-9]{24}$`)
	filePathPattern = regexp.MustCompile(`^/[^/]*/v\d+/files/[a-fA-F0-9]{24}(/download)?$`)
)

// ValidationService registers CMS-specific govalidator rules.
type ValidationService struct{}

// NewValidationService registers custom validators used by CMS forms.
func NewValidationService() ValidationService {
	govalidator.CustomTypeTagMap.Set("mediaRef", govalidator.CustomTypeValidator(func(i interface{}, _ interface{}) bool {
		value, ok := i.(string)
		if !ok {
			return false
		}
		return isMediaReference(value)
	}))

	return ValidationService{}
}

func isMediaReference(value string) bool {
	if value == "" {
		return true
	}
	if govalidator.IsURL(value) {
		return true
	}
	if objectIDPattern.MatchString(value) {
		return true
	}
	return filePathPattern.MatchString(value)
}

// ValidateCMSForm validates a CMS create/update payload.
func ValidateCMSForm(form *CMSForm) error {
	_, err := govalidator.ValidateStruct(form)
	return err
}

// ParseForm binds and validates a CMS form from the request.
func ParseForm(c echo.Context) (*CMSForm, error) {
	var form CMSForm
	if err := c.Bind(&form); err != nil {
		return nil, fmt.Errorf("invalid request format: %w", err)
	}
	if err := ValidateCMSForm(&form); err != nil {
		return nil, err
	}
	return &form, nil
}

// FormatValidationErrors converts govalidator errors into user-facing messages.
func FormatValidationErrors(err error) string {
	if err == nil {
		return ""
	}

	errs, ok := err.(govalidator.Errors)
	if !ok {
		return humanizeValidationError(err.Error())
	}

	messages := make([]string, 0, len(errs))
	for _, item := range errs {
		messages = append(messages, humanizeValidationError(item.Error()))
	}
	return strings.Join(messages, "; ")
}

func humanizeValidationError(raw string) string {
	field, rule, ok := parseGovalidatorError(raw)
	if !ok {
		return raw
	}

	label := fieldLabel(field)
	lowerRule := strings.ToLower(rule)

	switch {
	case strings.Contains(lowerRule, "mediaref"):
		return label + " must be a valid URL or uploaded file"
	case strings.Contains(lowerRule, "url"):
		return label + " must be a valid URL"
	case strings.Contains(lowerRule, "email"):
		return label + " must be a valid email address"
	case strings.Contains(lowerRule, "length"):
		return label + " has an invalid length"
	case strings.Contains(lowerRule, "range"):
		return label + " is out of the allowed range"
	case strings.Contains(lowerRule, "in("):
		return label + " has an invalid value"
	default:
		return label + " is invalid"
	}
}

func parseGovalidatorError(raw string) (field, rule string, ok bool) {
	parts := strings.SplitN(raw, ": ", 2)
	if len(parts) != 2 {
		return "", "", false
	}
	return parts[0], parts[1], true
}

func fieldLabel(field string) string {
	field = strings.TrimPrefix(field, "CMSForm.")
	replacer := strings.NewReplacer(
		"Hero.", "Hero section: ",
		"Chart.", "Chart section: ",
		"Features.", "Features section: ",
		"Challenges.", "Challenges section: ",
		"Advantages.", "Advantages section: ",
		"Contact.", "Contact section: ",
		"Footer.", "Footer section: ",
		".", " ",
	)
	label := replacer.Replace(field)
	label = strings.ReplaceAll(label, "Cards 0", "card 1")
	label = strings.ReplaceAll(label, "Cards 1", "card 2")
	label = strings.ReplaceAll(label, "Cards 2", "card 3")
	label = strings.ReplaceAll(label, "Cards 3", "card 4")
	label = strings.ReplaceAll(label, "Cards 4", "card 5")
	return label
}
