package domain

import (
	"fmt"
	"math"
	"time"
)

const EnnoblementRetentionForClosedServers = 365 * 24 * time.Hour

type Ennoblement struct {
	id         int
	serverKey  string
	villageID  int
	newOwnerID int
	newTribeID int
	oldOwnerID int
	oldTribeID int
	points     int
	createdAt  time.Time
}

const ennoblementModelName = "Ennoblement"

// UnmarshalEnnoblementFromDatabase unmarshals Ennoblement from the database.
//
// It should be used only for unmarshalling from the database!
// You can't use UnmarshalEnnoblementFromDatabase as constructor - It may put domain into the invalid state!
func UnmarshalEnnoblementFromDatabase(
	id int,
	serverKey string,
	villageID int,
	newOwnerID int,
	newTribeID int,
	oldOwnerID int,
	oldTribeID int,
	points int,
	createdAt time.Time,
) (Ennoblement, error) {
	if err := validateIntInRange(id, 1, math.MaxInt); err != nil {
		return Ennoblement{}, ValidationError{
			Model: ennoblementModelName,
			Field: "id",
			Err:   err,
		}
	}

	if err := validateServerKey(serverKey); err != nil {
		return Ennoblement{}, ValidationError{
			Model: ennoblementModelName,
			Field: "serverKey",
			Err:   err,
		}
	}

	return Ennoblement{
		id:         id,
		serverKey:  serverKey,
		villageID:  villageID,
		newOwnerID: newOwnerID,
		newTribeID: newTribeID,
		oldOwnerID: oldOwnerID,
		oldTribeID: oldTribeID,
		points:     points,
		createdAt:  createdAt,
	}, nil
}

func (e Ennoblement) ID() int {
	return e.id
}

func (e Ennoblement) ServerKey() string {
	return e.serverKey
}

func (e Ennoblement) VillageID() int {
	return e.villageID
}

func (e Ennoblement) NewOwnerID() int {
	return e.newOwnerID
}

func (e Ennoblement) NewTribeID() int {
	return e.newTribeID
}

func (e Ennoblement) OldOwnerID() int {
	return e.oldOwnerID
}

func (e Ennoblement) OldTribeID() int {
	return e.oldTribeID
}

func (e Ennoblement) Points() int {
	return e.points
}

func (e Ennoblement) CreatedAt() time.Time {
	return e.createdAt
}

func (e Ennoblement) WithRelations(
	village VillageMeta,
	newOwner PlayerMeta,
	newTribe NullTribeMeta,
	oldOwner NullPlayerMeta,
	oldTribe NullTribeMeta,
) EnnoblementWithRelations {
	return EnnoblementWithRelations{
		ennoblement: e,
		village:     village,
		newOwner:    newOwner,
		newTribe:    newTribe,
		oldOwner:    oldOwner,
		oldTribe:    oldTribe,
	}
}

func (e Ennoblement) ToCursor() (EnnoblementCursor, error) {
	return NewEnnoblementCursor(e.id, e.serverKey, e.createdAt)
}

func (e Ennoblement) Base() BaseEnnoblement {
	return BaseEnnoblement{
		villageID:  e.villageID,
		newOwnerID: e.newOwnerID,
		newTribeID: e.newTribeID,
		oldOwnerID: e.oldOwnerID,
		oldTribeID: e.oldTribeID,
		points:     e.points,
		createdAt:  e.createdAt,
	}
}

func (e Ennoblement) IsZero() bool {
	return e == Ennoblement{}
}

type Ennoblements []Ennoblement

type EnnoblementWithRelations struct {
	ennoblement Ennoblement
	village     VillageMeta
	newOwner    PlayerMeta
	newTribe    NullTribeMeta
	oldOwner    NullPlayerMeta
	oldTribe    NullTribeMeta
}

func (e EnnoblementWithRelations) Ennoblement() Ennoblement {
	return e.ennoblement
}

func (e EnnoblementWithRelations) Village() VillageMeta {
	return e.village
}

func (e EnnoblementWithRelations) NewOwner() PlayerMeta {
	return e.newOwner
}

func (e EnnoblementWithRelations) NewTribe() NullTribeMeta {
	return e.newTribe
}

func (e EnnoblementWithRelations) OldOwner() NullPlayerMeta {
	return e.oldOwner
}

func (e EnnoblementWithRelations) OldTribe() NullTribeMeta {
	return e.oldTribe
}

func (e EnnoblementWithRelations) IsZero() bool {
	return e.ennoblement.IsZero()
}

type EnnoblementsWithRelations []EnnoblementWithRelations

type CreateEnnoblementParams struct {
	base      BaseEnnoblement
	serverKey string
}

const createEnnoblementParamsModelName = "CreateEnnoblementParams"

func NewCreateEnnoblementParams(serverKey string, ennoblements BaseEnnoblements) ([]CreateEnnoblementParams, error) {
	if err := validateServerKey(serverKey); err != nil {
		return nil, ValidationError{
			Model: createEnnoblementParamsModelName,
			Field: "serverKey",
			Err:   err,
		}
	}

	params := make([]CreateEnnoblementParams, 0, len(ennoblements))

	for i, e := range ennoblements {
		if e.IsZero() {
			return nil, fmt.Errorf("ennoblements[%d] is an empty struct", i)
		}

		params = append(params, CreateEnnoblementParams{
			base:      e,
			serverKey: serverKey,
		})
	}

	return params, nil
}

func (params CreateEnnoblementParams) Base() BaseEnnoblement {
	return params.base
}

func (params CreateEnnoblementParams) ServerKey() string {
	return params.serverKey
}

type EnnoblementSort uint8

const (
	EnnoblementSortCreatedAtASC EnnoblementSort = iota + 1
	EnnoblementSortCreatedAtDESC
	EnnoblementSortIDASC
	EnnoblementSortIDDESC
	EnnoblementSortServerKeyASC
	EnnoblementSortServerKeyDESC
)

// IsInConflict returns true if two sorts can't be used together (e.g. EnnoblementSortIDASC and EnnoblementSortIDDESC).
func (s EnnoblementSort) IsInConflict(s2 EnnoblementSort) bool {
	return isSortInConflict(s, s2)
}

//nolint:gocyclo
func (s EnnoblementSort) String() string {
	switch s {
	case EnnoblementSortCreatedAtASC:
		return "createdAt:ASC"
	case EnnoblementSortCreatedAtDESC:
		return "createdAt:DESC"
	case EnnoblementSortIDASC:
		return "id:ASC"
	case EnnoblementSortIDDESC:
		return "id:DESC"
	case EnnoblementSortServerKeyASC:
		return "serverKey:ASC"
	case EnnoblementSortServerKeyDESC:
		return "serverKey:DESC"
	default:
		return "unknown ennoblement sort"
	}
}

type EnnoblementCursor struct {
	id        int
	serverKey string
	createdAt time.Time
}

const ennoblementCursorModelName = "EnnoblementCursor"

func NewEnnoblementCursor(id int, serverKey string, createdAt time.Time) (EnnoblementCursor, error) {
	if err := validateIntInRange(id, 1, math.MaxInt); err != nil {
		return EnnoblementCursor{}, ValidationError{
			Model: ennoblementCursorModelName,
			Field: "id",
			Err:   err,
		}
	}

	if err := validateServerKey(serverKey); err != nil {
		return EnnoblementCursor{}, ValidationError{
			Model: ennoblementCursorModelName,
			Field: "serverKey",
			Err:   err,
		}
	}

	return EnnoblementCursor{
		id:        id,
		serverKey: serverKey,
		createdAt: createdAt,
	}, nil
}

//nolint:gocyclo
func decodeEnnoblementCursor(encoded string) (EnnoblementCursor, error) {
	m, err := decodeCursor(encoded)
	if err != nil {
		return EnnoblementCursor{}, err
	}

	id, err := m.int("id")
	if err != nil {
		return EnnoblementCursor{}, ErrInvalidCursor
	}

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

	createdAt, err := m.time("createdAt")
	if err != nil {
		return EnnoblementCursor{}, ErrInvalidCursor
	}

	ec, err := NewEnnoblementCursor(
		id,
		serverKey,
		createdAt,
	)
	if err != nil {
		return EnnoblementCursor{}, ErrInvalidCursor
	}

	return ec, nil
}

func (ec EnnoblementCursor) ID() int {
	return ec.id
}

func (ec EnnoblementCursor) ServerKey() string {
	return ec.serverKey
}

func (ec EnnoblementCursor) CreatedAt() time.Time {
	return ec.createdAt
}

func (ec EnnoblementCursor) IsZero() bool {
	return ec == EnnoblementCursor{}
}

func (ec EnnoblementCursor) Encode() string {
	if ec.IsZero() {
		return ""
	}

	return encodeCursor([]keyValuePair{
		{"id", ec.id},
		{"serverKey", ec.serverKey},
		{"createdAt", ec.createdAt},
	})
}

type ListEnnoblementsParams struct {
	serverKeys []string
	villageIDs []int
	playerIDs  []int // Ennoblement.NewOwnerID or Ennoblement.OldOwnerID
	tribeIDs   []int // Ennoblement.NewTribeID or Ennoblement.OldTribeID
	since      NullTime
	before     NullTime
	sort       []EnnoblementSort
	cursor     EnnoblementCursor
	limit      int
}

const (
	EnnoblementListMaxLimit         = 500
	listEnnoblementsParamsModelName = "ListEnnoblementsParams"
)

func NewListEnnoblementsParams() ListEnnoblementsParams {
	return ListEnnoblementsParams{
		sort: []EnnoblementSort{
			EnnoblementSortServerKeyASC,
			EnnoblementSortCreatedAtASC,
			EnnoblementSortIDASC,
		},
		limit: EnnoblementListMaxLimit,
	}
}

func (params *ListEnnoblementsParams) ServerKeys() []string {
	return params.serverKeys
}

func (params *ListEnnoblementsParams) SetServerKeys(serverKeys []string) error {
	for i, sk := range serverKeys {
		if err := validateServerKey(sk); err != nil {
			return SliceElementValidationError{
				Model: listEnnoblementsParamsModelName,
				Field: "serverKeys",
				Index: i,
				Err:   err,
			}
		}
	}

	params.serverKeys = serverKeys

	return nil
}

func (params *ListEnnoblementsParams) VillageIDs() []int {
	return params.villageIDs
}

func (params *ListEnnoblementsParams) SetVillageIDs(villageIDs []int) error {
	for i, id := range villageIDs {
		if err := validateIntInRange(id, 1, math.MaxInt); err != nil {
			return SliceElementValidationError{
				Model: listEnnoblementsParamsModelName,
				Field: "villageIDs",
				Index: i,
				Err:   err,
			}
		}
	}

	params.villageIDs = villageIDs

	return nil
}

func (params *ListEnnoblementsParams) PlayerIDs() []int {
	return params.playerIDs
}

func (params *ListEnnoblementsParams) SetPlayerIDs(playerIDs []int) error {
	for i, id := range playerIDs {
		if err := validateIntInRange(id, 1, math.MaxInt); err != nil {
			return SliceElementValidationError{
				Model: listEnnoblementsParamsModelName,
				Field: "playerIDs",
				Index: i,
				Err:   err,
			}
		}
	}

	params.playerIDs = playerIDs

	return nil
}

func (params *ListEnnoblementsParams) TribeIDs() []int {
	return params.tribeIDs
}

func (params *ListEnnoblementsParams) SetTribeIDs(tribeIDs []int) error {
	for i, id := range tribeIDs {
		if err := validateIntInRange(id, 1, math.MaxInt); err != nil {
			return SliceElementValidationError{
				Model: listEnnoblementsParamsModelName,
				Field: "tribeIDs",
				Index: i,
				Err:   err,
			}
		}
	}

	params.tribeIDs = tribeIDs

	return nil
}

func (params *ListEnnoblementsParams) Since() NullTime {
	return params.since
}

func (params *ListEnnoblementsParams) SetSince(since NullTime) error {
	params.since = since
	return nil
}

func (params *ListEnnoblementsParams) Before() NullTime {
	return params.before
}

func (params *ListEnnoblementsParams) SetBefore(before NullTime) error {
	params.before = before
	return nil
}

func (params *ListEnnoblementsParams) Sort() []EnnoblementSort {
	return params.sort
}

const (
	ennoblementSortMinLength = 0
	ennoblementSortMaxLength = 3
)

func (params *ListEnnoblementsParams) SetSort(sort []EnnoblementSort) error {
	if err := validateSort(sort, ennoblementSortMinLength, ennoblementSortMaxLength); err != nil {
		return ValidationError{
			Model: listEnnoblementsParamsModelName,
			Field: "sort",
			Err:   err,
		}
	}

	params.sort = sort

	return nil
}

func (params *ListEnnoblementsParams) PrependSort(sort []EnnoblementSort) error {
	if len(sort) == 0 {
		return nil
	}

	if err := validateSliceLen(sort, 0, max(ennoblementSortMaxLength-len(params.sort), 0)); err != nil {
		return ValidationError{
			Model: listEnnoblementsParamsModelName,
			Field: "sort",
			Err:   err,
		}
	}

	return params.SetSort(append(sort, params.sort...))
}

func (params *ListEnnoblementsParams) PrependSortString(sort []string, allowed []EnnoblementSort, maxLength int) error {
	if len(sort) == 0 {
		return nil
	}

	if err := validateSliceLen(sort, 0, max(min(ennoblementSortMaxLength-len(params.sort), maxLength), 0)); err != nil {
		return ValidationError{
			Model: listEnnoblementsParamsModelName,
			Field: "sort",
			Err:   err,
		}
	}

	toPrepend := make([]EnnoblementSort, 0, len(sort))

	for i, s := range sort {
		converted, err := newSortFromString(s, allowed...)
		if err != nil {
			return SliceElementValidationError{
				Model: listEnnoblementsParamsModelName,
				Field: "sort",
				Index: i,
				Err:   err,
			}
		}
		toPrepend = append(toPrepend, converted)
	}

	return params.SetSort(append(toPrepend, params.sort...))
}

func (params *ListEnnoblementsParams) Cursor() EnnoblementCursor {
	return params.cursor
}

func (params *ListEnnoblementsParams) SetCursor(cursor EnnoblementCursor) error {
	params.cursor = cursor
	return nil
}

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

	params.cursor = decoded

	return nil
}

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

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

	params.limit = limit

	return nil
}

type ListEnnoblementsResult struct {
	ennoblements Ennoblements
	self         EnnoblementCursor
	next         EnnoblementCursor
}

const listEnnoblementsResultModelName = "ListEnnoblementsResult"

func NewListEnnoblementsResult(ennoblements Ennoblements, next Ennoblement) (ListEnnoblementsResult, error) {
	var err error
	res := ListEnnoblementsResult{
		ennoblements: ennoblements,
	}

	if len(ennoblements) > 0 {
		res.self, err = ennoblements[0].ToCursor()
		if err != nil {
			return ListEnnoblementsResult{}, ValidationError{
				Model: listEnnoblementsResultModelName,
				Field: "self",
				Err:   err,
			}
		}
	}

	if !next.IsZero() {
		res.next, err = next.ToCursor()
		if err != nil {
			return ListEnnoblementsResult{}, ValidationError{
				Model: listEnnoblementsResultModelName,
				Field: "next",
				Err:   err,
			}
		}
	}

	return res, nil
}

func (res ListEnnoblementsResult) Ennoblements() Ennoblements {
	return res.ennoblements
}

func (res ListEnnoblementsResult) Self() EnnoblementCursor {
	return res.self
}

func (res ListEnnoblementsResult) Next() EnnoblementCursor {
	return res.next
}

type ListEnnoblementsWithRelationsResult struct {
	ennoblements EnnoblementsWithRelations
	self         EnnoblementCursor
	next         EnnoblementCursor
}

const listEnnoblementsWithRelationsResultModelName = "ListEnnoblementsWithRelationsResult"

func NewListEnnoblementsWithRelationsResult(
	ennoblements EnnoblementsWithRelations,
	next EnnoblementWithRelations,
) (ListEnnoblementsWithRelationsResult, error) {
	var err error
	res := ListEnnoblementsWithRelationsResult{
		ennoblements: ennoblements,
	}

	if len(ennoblements) > 0 {
		res.self, err = ennoblements[0].Ennoblement().ToCursor()
		if err != nil {
			return ListEnnoblementsWithRelationsResult{}, ValidationError{
				Model: listEnnoblementsWithRelationsResultModelName,
				Field: "self",
				Err:   err,
			}
		}
	}

	if !next.IsZero() {
		res.next, err = next.Ennoblement().ToCursor()
		if err != nil {
			return ListEnnoblementsWithRelationsResult{}, ValidationError{
				Model: listEnnoblementsWithRelationsResultModelName,
				Field: "next",
				Err:   err,
			}
		}
	}

	return res, nil
}

func (res ListEnnoblementsWithRelationsResult) Ennoblements() EnnoblementsWithRelations {
	return res.ennoblements
}

func (res ListEnnoblementsWithRelationsResult) Self() EnnoblementCursor {
	return res.self
}

func (res ListEnnoblementsWithRelationsResult) Next() EnnoblementCursor {
	return res.next
}
