package validators

import (
	"fmt"
	"reflect"
	"regexp"
	"strings"
	"time"

	"github.com/go-playground/validator/v10"

	"wucher/internal/transport/http/jsonapi"
)

type Validator struct {
	validate *validator.Validate
}

var hoursPattern = regexp.MustCompile(`^\d+(\.\d+)?$`)

// validateHoursOrDate accepts an hours value (e.g. "1200" or "1200.5") or a calendar
// date "YYYY-MM-DD". Anything else (e.g. "1200 FH") is rejected.
func validateHoursOrDate(fl validator.FieldLevel) bool {
	s := strings.TrimSpace(fl.Field().String())
	if s == "" {
		return true
	}
	if hoursPattern.MatchString(s) {
		return true
	}
	_, err := time.Parse("2006-01-02", s)
	return err == nil
}

func New() *Validator {
	v := validator.New()
	v.RegisterTagNameFunc(func(fld reflect.StructField) string {
		name := fld.Tag.Get("json")
		if name == "" {
			return fld.Name
		}
		if idx := strings.Index(name, ","); idx != -1 {
			return name[:idx]
		}
		return name
	})
	_ = v.RegisterValidation("hours_or_date", validateHoursOrDate)
	return &Validator{validate: v}
}

func (v *Validator) ValidateStruct(s any) []jsonapi.ErrorObject {
	if err := v.validate.Struct(s); err != nil {
		if ve, ok := err.(validator.ValidationErrors); ok {
			return toJSONAPIErrors(ve)
		}
		return []jsonapi.ErrorObject{{
			Status: "400",
			Title:  "Invalid request",
			Detail: err.Error(),
		}}
	}
	return nil
}

func toJSONAPIErrors(ve validator.ValidationErrors) []jsonapi.ErrorObject {
	errs := make([]jsonapi.ErrorObject, 0, len(ve))
	for _, fe := range ve {
		field := fe.Field()
		jsonPath := "/data/attributes/" + strings.ReplaceAll(field, ".", "/")
		errs = append(errs, jsonapi.ErrorObject{
			Status: "422",
			Title:  "Validation error",
			Detail: validationMessage(field, fe),
			Source: &jsonapi.ErrorSource{Pointer: jsonPath},
		})
	}
	return errs
}

func validationMessage(field string, fe validator.FieldError) string {
	switch fe.Tag() {
	case "required":
		return field + " is required"
	case "uuid":
		return field + " must be a valid UUID"
	case "email":
		return field + " must be a valid email address"
	case "hours_or_date":
		return field + " must be flight hours (e.g. 1200) or a date in YYYY-MM-DD format"
	case "oneof":
		return field + " must be one of: " + fe.Param()
	default:
		return fmt.Sprintf("%s is invalid (%s)", field, fe.Tag())
	}
}
