package domain

import (
	"errors"
	"fmt"
)

type ValidationError struct {
	Model string
	Field string
	Err   error
}

var _ ErrorWithParams = ValidationError{}
var _ ErrorWithPath = ValidationError{}

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

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

	return prefix + e.Err.Error()
}

func (e ValidationError) Path() ErrorPathSegment {
	return ErrorPathSegment{
		Model: e.Model,
		Field: e.Field,
		Index: -1,
	}
}

func (e ValidationError) Type() ErrorType {
	var domainErr Error
	if errors.As(e.Err, &domainErr) {
		return domainErr.Type()
	}
	return ErrorTypeIncorrectInput
}

// errorcode:ignore
const errorCodeValidationFailed = "validation-failed"

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

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{}
var _ ErrorWithPath = SliceElementValidationError{}

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

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

	return prefix + e.Err.Error()
}

func (e SliceElementValidationError) Path() ErrorPathSegment {
	return ErrorPathSegment{
		Model: e.Model,
		Field: e.Field,
		Index: e.Index,
	}
}

func (e SliceElementValidationError) Type() ErrorType {
	var domainErr Error
	if errors.As(e.Err, &domainErr) {
		return domainErr.Type()
	}
	return ErrorTypeIncorrectInput
}

// errorcode:ignore
const errorCodeSliceElementValidationFailed = "slice-element-validation-failed"

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

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) Type() ErrorType {
	return ErrorTypeIncorrectInput
}

const errorCodeMinGreaterEqual = "min-greater-equal"

func (e MinGreaterEqualError) Code() string {
	return errorCodeMinGreaterEqual
}

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) Type() ErrorType {
	return ErrorTypeIncorrectInput
}

const errorCodeMaxLessEqual = "max-less-equal"

func (e MaxLessEqualError) Code() string {
	return errorCodeMaxLessEqual
}

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) Type() ErrorType {
	return ErrorTypeIncorrectInput
}

const errorCodeLenOutOfRange = "length-out-of-range"

func (e LenOutOfRangeError) Code() string {
	return errorCodeLenOutOfRange
}

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) Type() ErrorType {
	return ErrorTypeIncorrectInput
}

const errorCodeInvalidURL = "invalid-url"

func (e InvalidURLError) Code() string {
	return errorCodeInvalidURL
}

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

type UnsupportedSortStringError struct {
	Sort string
}

var _ ErrorWithParams = UnsupportedSortStringError{}

func (e UnsupportedSortStringError) Error() string {
	return fmt.Sprintf("sort %s is unsupported", e.Sort)
}

func (e UnsupportedSortStringError) Type() ErrorType {
	return ErrorTypeIncorrectInput
}

const errorCodeUnsupportedSortString = "unsupported-sort-string"

func (e UnsupportedSortStringError) Code() string {
	return errorCodeUnsupportedSortString
}

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

type SortConflictError struct {
	Sort [2]string
}

var _ ErrorWithParams = SortConflictError{}

func (e SortConflictError) Error() string {
	return fmt.Sprintf("sort values %s are in conflict and can't be used together", e.Sort[0]+" and "+e.Sort[1])
}

func (e SortConflictError) Type() ErrorType {
	return ErrorTypeIncorrectInput
}

const errorCodeSortConflict = "sort-conflict"

func (e SortConflictError) Code() string {
	return errorCodeSortConflict
}

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

// This error code is returned when a value can't be blank.
const errorCodeRequired = "required"

var ErrRequired error = simpleError{
	msg:  "can't be blank",
	typ:  ErrorTypeIncorrectInput,
	code: errorCodeRequired,
}

// This error code is returned when a value can't be nil.
const errorCodeNil = "nil"

var ErrNil error = simpleError{
	msg:  "must not be nil",
	typ:  ErrorTypeIncorrectInput,
	code: errorCodeNil,
}

// This error code is returned when a cursor can't be decoded (e.g. is malformed).
const errorCodeInvalidCursor = "invalid-cursor"

// ErrInvalidCursor is an error that is returned when a cursor can't be decoded (e.g. is malformed).
var ErrInvalidCursor error = simpleError{
	msg:  "invalid cursor",
	typ:  ErrorTypeIncorrectInput,
	code: errorCodeInvalidCursor,
}

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
}

const (
	versionCodeMinLength = 2
	versionCodeMaxLength = 2
)

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

const (
	serverKeyMinLength = 1
	serverKeyMaxLength = 10
)

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
}

func validateSort[S ~[]E, E interface {
	IsInConflict(s E) bool
	String() string
}](sort S, min, max int) error {
	if err := validateSliceLen(sort, min, max); err != nil {
		return err
	}

	for i, s1 := range sort {
		for j := i + 1; j < len(sort); j++ {
			s2 := sort[j]
			if s1.IsInConflict(s2) {
				return SortConflictError{
					Sort: [2]string{s1.String(), s2.String()},
				}
			}
		}
	}

	return nil
}
