package validator

import (
	"errors"
	"net/url"
	"path"
	"reflect"
	"strings"

	"history-api/pkg/constants"

	"github.com/go-playground/validator/v10"
	"github.com/gofiber/fiber/v3"
	"github.com/google/uuid"
)

var validate = validator.New()

func init() {
	validate.RegisterTagNameFunc(func(fld reflect.StructField) string {
		name := strings.SplitN(fld.Tag.Get("json"), ",", 2)[0]
		if name == "-" {
			return ""
		}
		if name == "" {
			name = strings.SplitN(fld.Tag.Get("query"), ",", 2)[0]
		}
		return name
	})

	validate.RegisterValidation("image_url", func(fl validator.FieldLevel) bool {
		val := fl.Field().String()
		if val == "" {
			return true
		}
		return isImageURL(val)
	})

	validate.RegisterValidation("optional_url", func(fl validator.FieldLevel) bool {
		val := fl.Field().String()
		if val == "" {
			return true
		}
		return isValidURL(val)
	})

	validate.RegisterValidation("uuidv7", func(fl validator.FieldLevel) bool {
		val := fl.Field().String()
		if val == "" {
			return true
		}
		u, err := uuid.Parse(val)
		if err != nil {
			return false
		}
		return u.Version() == 7
	})

	validate.RegisterValidation("slug", func(fl validator.FieldLevel) bool {
		val := fl.Field().String()
		if val == "" {
			return true
		}
		return constants.SLUG_REGEX.MatchString(val)
	})
}

func isValidURL(s string) bool {
	u, err := url.ParseRequestURI(s)
	if err != nil {
		return false
	}

	if u.Scheme != "http" && u.Scheme != "https" {
		return false
	}

	if u.Host == "" || !strings.Contains(u.Host, ".") {
		return false
	}

	return true
}

func isImageURL(u string) bool {
	parsed, err := url.Parse(u)
	if err != nil {
		return false
	}

	ext := strings.ToLower(path.Ext(parsed.Path))

	switch ext {
	case ".jpg", ".jpeg", ".png", ".gif", ".webp":
		return true
	default:
		return false
	}
}

type ErrorResponse struct {
	FailedField string `json:"failed_field,omitempty"`
	Tag         string `json:"tag,omitempty"`
	Value       string `json:"value,omitempty"`
	Message     string `json:"message"`
}

func formatValidationError(err error) []*ErrorResponse {
	var validationErrors validator.ValidationErrors
	var errorsList []*ErrorResponse

	if errors.As(err, &validationErrors) {
		for _, fieldError := range validationErrors {
			message := ""
			switch fieldError.Tag() {
			case "required":
				message = fieldError.Field() + " is mandatory"
			case "email":
				message = "The email address is invalid"
			case "min":
				message = fieldError.Field() + " is too short (min " + fieldError.Param() + ")"
			case "max":
				message = fieldError.Field() + " is too long (max " + fieldError.Param() + ")"
			case "image_url":
				message = fieldError.Field() + " must be a link to an image (jpg, png, etc.)"
			case "uuidv7":
				message = fieldError.Field() + " must be a valid UUID v7"
			default:
				message = "Field " + fieldError.Field() + " failed on validation: " + fieldError.Tag()
			}

			errorsList = append(errorsList, &ErrorResponse{
				FailedField: fieldError.Field(),
				Tag:         fieldError.Tag(),
				Value:       fieldError.Param(),
				Message:     message,
			})
		}
	} else {
		errorsList = append(errorsList, &ErrorResponse{
			Message: "Invalid request payload: " + err.Error(),
		})
	}
	return errorsList
}

func ValidateQueryDto(c fiber.Ctx, dto any) []*ErrorResponse {
	if err := c.Bind().Query(dto); err != nil {
		return formatValidationError(err)
	}

	if err := validate.Struct(dto); err != nil {
		return formatValidationError(err)
	}

	return nil
}

func ValidateBodyDto(c fiber.Ctx, dto any) []*ErrorResponse {
	if err := c.Bind().Body(dto); err != nil {
		return formatValidationError(err)
	}

	if err := validate.Struct(dto); err != nil {
		return formatValidationError(err)
	}

	return nil
}
