package domain

import (
	"errors"
	"fmt"
	"strconv"
)

type ValidationError struct {
	Model string
	Field string
	Err   error
}

var _ ErrorWithParams = ValidationError{}

func (e ValidationError) Error() string {
	prefix := e.Model

	if e.Field != "" {
		if len(prefix) > 0 {
			prefix += "."
		}
		prefix += e.Field
	}

	if prefix != "" {
		prefix += ": "
	}

	return prefix + e.Err.Error()
}

func (e ValidationError) Code() ErrorCode {
	var domainErr Error
	if errors.As(e.Err, &domainErr) {
		return domainErr.Code()
	}
	return ErrorCodeIncorrectInput
}

func (e ValidationError) Slug() string {
	var domainErr Error
	if errors.As(e.Err, &domainErr) {
		return domainErr.Slug()
	}
	return "validation-failed"
}

func (e ValidationError) Params() map[string]any {
	var withParams ErrorWithParams
	if ok := errors.As(e.Err, &withParams); !ok {
		return nil
	}
	return withParams.Params()
}

func (e ValidationError) Unwrap() error {
	return e.Err
}

type SliceElementValidationError struct {
	Model string
	Field string
	Index int
	Err   error
}

var _ ErrorWithParams = SliceElementValidationError{}

func (e SliceElementValidationError) Error() string {
	prefix := e.Model

	if e.Field != "" {
		if len(prefix) > 0 {
			prefix += "."
		}
		prefix += e.Field + "[" + strconv.Itoa(e.Index) + "]"
	}

	if prefix != "" {
		prefix += ": "
	}

	return prefix + e.Err.Error()
}

func (e SliceElementValidationError) Code() ErrorCode {
	var domainErr Error
	if errors.As(e.Err, &domainErr) {
		return domainErr.Code()
	}
	return ErrorCodeIncorrectInput
}

func (e SliceElementValidationError) Slug() string {
	var domainErr Error
	if errors.As(e.Err, &domainErr) {
		return domainErr.Slug()
	}
	return "slice-element-validation-failed"
}

func (e SliceElementValidationError) Params() map[string]any {
	var withParams ErrorWithParams
	if ok := errors.As(e.Err, &withParams); !ok {
		return nil
	}
	return withParams.Params()
}

func (e SliceElementValidationError) Unwrap() error {
	return e.Err
}

type MinGreaterEqualError struct {
	Min     int
	Current int
}

var _ ErrorWithParams = MinGreaterEqualError{}

func (e MinGreaterEqualError) Error() string {
	return fmt.Sprintf("must be no less than %d (current: %d)", e.Min, e.Current)
}

func (e MinGreaterEqualError) Code() ErrorCode {
	return ErrorCodeIncorrectInput
}

func (e MinGreaterEqualError) Slug() string {
	return "min-greater-equal"
}

func (e MinGreaterEqualError) Params() map[string]any {
	return map[string]any{
		"Min":     e.Min,
		"Current": e.Current,
	}
}

type MaxLessEqualError struct {
	Max     int
	Current int
}

var _ ErrorWithParams = MaxLessEqualError{}

func (e MaxLessEqualError) Error() string {
	return fmt.Sprintf("must be no greater than %d (current: %d)", e.Max, e.Current)
}

func (e MaxLessEqualError) Code() ErrorCode {
	return ErrorCodeIncorrectInput
}

func (e MaxLessEqualError) Slug() string {
	return "max-less-equal"
}

func (e MaxLessEqualError) Params() map[string]any {
	return map[string]any{
		"Max":     e.Max,
		"Current": e.Current,
	}
}

type LenOutOfRangeError struct {
	Min     int
	Max     int
	Current int
}

var _ ErrorWithParams = LenOutOfRangeError{}

func (e LenOutOfRangeError) Error() string {
	return fmt.Sprintf("length must be between %d and %d (current length: %d)", e.Min, e.Max, e.Current)
}

func (e LenOutOfRangeError) Code() ErrorCode {
	return ErrorCodeIncorrectInput
}

func (e LenOutOfRangeError) Slug() string {
	return "length-out-of-range"
}

func (e LenOutOfRangeError) Params() map[string]any {
	return map[string]any{
		"Min":     e.Min,
		"Max":     e.Max,
		"Current": e.Current,
	}
}

type InvalidURLError struct {
	URL string
}

var _ ErrorWithParams = InvalidURLError{}

func (e InvalidURLError) Error() string {
	return fmt.Sprintf("%s: invalid URL", e.URL)
}

func (e InvalidURLError) Code() ErrorCode {
	return ErrorCodeIncorrectInput
}

func (e InvalidURLError) Slug() string {
	return "invalid-url"
}

func (e InvalidURLError) Params() map[string]any {
	return map[string]any{
		"URL": e.URL,
	}
}

var ErrRequired error = simpleError{
	msg:  "can't be blank",
	code: ErrorCodeIncorrectInput,
	slug: "required",
}

var ErrNil error = simpleError{
	msg:  "must not be nil",
	code: ErrorCodeIncorrectInput,
	slug: "nil",
}

func validateSliceLen[S ~[]E, E any](s S, min, max int) error {
	if l := len(s); l > max || l < min {
		return LenOutOfRangeError{
			Min:     min,
			Max:     max,
			Current: l,
		}
	}

	return nil
}

func validateStringLen(s string, min, max int) error {
	if l := len(s); l > max || l < min {
		return LenOutOfRangeError{
			Min:     min,
			Max:     max,
			Current: l,
		}
	}

	return nil
}

func validateVersionCode(code string) error {
	return validateStringLen(code, versionCodeMinLength, versionCodeMaxLength)
}

func validateServerKey(key string) error {
	return validateStringLen(key, serverKeyMinLength, serverKeyMaxLength)
}

func validateIntInRange(current, min, max int) error {
	if current < min {
		return MinGreaterEqualError{
			Min:     min,
			Current: current,
		}
	}

	if current > max {
		return MaxLessEqualError{
			Max:     max,
			Current: current,
		}
	}

	return nil
}
