package domain

import (
	"fmt"
	"net/url"
)

const (
	versionCodeMinLength = 2
	versionCodeMaxLength = 2
)

type Version struct {
	code     string
	name     string
	host     string
	timezone string
}

var versionModelName = "Version"

// UnmarshalVersionFromDatabase unmarshals Version from the database.
//
// It should be used only for unmarshalling from the database!
// You can't use UnmarshalVersionFromDatabase as constructor - It may put domain into the invalid state!
func UnmarshalVersionFromDatabase(
	code string,
	name string,
	host string,
	timezone string,
) (Version, error) {
	if code == "" {
		return Version{}, ValidationError{
			Model: versionModelName,
			Field: "code",
			Err:   ErrRequired,
		}
	}

	if name == "" {
		return Version{}, ValidationError{
			Model: versionModelName,
			Field: "name",
			Err:   ErrRequired,
		}
	}

	if host == "" {
		return Version{}, ValidationError{
			Model: versionModelName,
			Field: "host",
			Err:   ErrRequired,
		}
	}

	if timezone == "" {
		return Version{}, ValidationError{
			Model: versionModelName,
			Field: "timezone",
			Err:   ErrRequired,
		}
	}

	return Version{
		code:     code,
		name:     name,
		host:     host,
		timezone: timezone,
	}, nil
}

func (v Version) Code() string {
	return v.code
}

func (v Version) Name() string {
	return v.name
}

func (v Version) Host() string {
	return v.host
}

func (v Version) Timezone() string {
	return v.timezone
}

func (v Version) URL() *url.URL {
	return &url.URL{
		Scheme: "https",
		Host:   v.host,
	}
}

func (v Version) IsZero() bool {
	return v == Version{}
}

type Versions []Version

type VersionSort uint8

const (
	VersionSortCodeASC VersionSort = iota + 1
	VersionSortCodeDESC
)

type VersionCursor struct {
	code string
}

const versionCursorModelName = "VersionCursor"

func NewVersionCursor(code string) (VersionCursor, error) {
	if err := validateVersionCode(code); err != nil {
		return VersionCursor{}, ValidationError{
			Model: versionCursorModelName,
			Field: "code",
			Err:   err,
		}
	}

	return VersionCursor{
		code: code,
	}, nil
}

func decodeVersionCursor(encoded string) (VersionCursor, error) {
	m, err := decodeCursor(encoded)
	if err != nil {
		return VersionCursor{}, err
	}

	code, err := m.string("code")
	if err != nil {
		return VersionCursor{}, ErrInvalidCursor
	}

	vc, err := NewVersionCursor(code)
	if err != nil {
		return VersionCursor{}, ErrInvalidCursor
	}

	return vc, nil
}

func (vc VersionCursor) Code() string {
	return vc.code
}

func (vc VersionCursor) IsZero() bool {
	return vc == VersionCursor{}
}

func (vc VersionCursor) Encode() string {
	if vc.IsZero() {
		return ""
	}

	return encodeCursor([]keyValuePair{
		{"code", vc.code},
	})
}

type ListVersionsParams struct {
	codes  []string
	sort   []VersionSort
	cursor VersionCursor
	limit  int
}

const (
	VersionListMaxLimit         = 200
	listVersionsParamsModelName = "ListVersionsParams"
)

func NewListVersionsParams() ListVersionsParams {
	return ListVersionsParams{
		sort:  []VersionSort{VersionSortCodeASC},
		limit: VersionListMaxLimit,
	}
}

func (params *ListVersionsParams) Codes() []string {
	return params.codes
}

func (params *ListVersionsParams) SetCodes(codes []string) error {
	params.codes = codes
	return nil
}

const (
	versionSortMinLength = 1
	versionSortMaxLength = 1
)

func (params *ListVersionsParams) Sort() []VersionSort {
	return params.sort
}

func (params *ListVersionsParams) SetSort(sort []VersionSort) error {
	if err := validateSliceLen(sort, versionSortMinLength, versionSortMaxLength); err != nil {
		return ValidationError{
			Model: listVersionsParamsModelName,
			Field: "sort",
			Err:   err,
		}
	}

	params.sort = sort

	return nil
}

func (params *ListVersionsParams) Cursor() VersionCursor {
	return params.cursor
}

func (params *ListVersionsParams) SetCursor(vc VersionCursor) error {
	params.cursor = vc
	return nil
}

func (params *ListVersionsParams) SetEncodedCursor(encoded string) error {
	decoded, err := decodeVersionCursor(encoded)
	if err != nil {
		return ValidationError{
			Model: listVersionsParamsModelName,
			Field: "cursor",
			Err:   err,
		}
	}

	params.cursor = decoded

	return nil
}

func (params *ListVersionsParams) Limit() int {
	return params.limit
}

func (params *ListVersionsParams) SetLimit(limit int) error {
	if err := validateIntInRange(limit, 1, VersionListMaxLimit); err != nil {
		return ValidationError{
			Model: listVersionsParamsModelName,
			Field: "limit",
			Err:   err,
		}
	}

	params.limit = limit

	return nil
}

type ListVersionsResult struct {
	versions Versions
	self     VersionCursor
	next     VersionCursor
}

const listVersionsResultModelName = "ListVersionsResult"

func NewListVersionsResult(versions Versions, next Version) (ListVersionsResult, error) {
	var err error
	res := ListVersionsResult{
		versions: versions,
	}

	if len(versions) > 0 {
		res.self, err = NewVersionCursor(versions[0].Code())
		if err != nil {
			return ListVersionsResult{}, ValidationError{
				Model: listVersionsResultModelName,
				Field: "self",
				Err:   err,
			}
		}
	}

	if !next.IsZero() {
		res.next, err = NewVersionCursor(next.Code())
		if err != nil {
			return ListVersionsResult{}, ValidationError{
				Model: listVersionsResultModelName,
				Field: "next",
				Err:   err,
			}
		}
	}

	return res, nil
}

func (res ListVersionsResult) Versions() Versions {
	return res.versions
}

func (res ListVersionsResult) Self() VersionCursor {
	return res.self
}

func (res ListVersionsResult) Next() VersionCursor {
	return res.next
}

type VersionNotFoundError struct {
	VersionCode string
}

func (e VersionNotFoundError) Error() string {
	return fmt.Sprintf("version with code %s not found", e.VersionCode)
}

func (e VersionNotFoundError) Type() ErrorType {
	return ErrorTypeNotFound
}

func (e VersionNotFoundError) Code() string {
	return "version-not-found"
}

func (e VersionNotFoundError) Params() map[string]any {
	return map[string]any{
		"Code": e.VersionCode,
	}
}
