package domain

import (
	"cmp"
	"fmt"
	"math"
	"slices"
	"time"
)

type TribeChange struct {
	id         int
	serverKey  string
	playerID   int
	oldTribeID int
	newTribeID int
	createdAt  time.Time
}

const tribeChangeModelName = "TribeChange"

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

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

	return TribeChange{
		id:         id,
		playerID:   playerID,
		oldTribeID: oldTribeID,
		newTribeID: newTribeID,
		serverKey:  serverKey,
		createdAt:  createdAt,
	}, nil
}

func (tc TribeChange) ID() int {
	return tc.id
}

func (tc TribeChange) PlayerID() int {
	return tc.playerID
}

func (tc TribeChange) OldTribeID() int {
	return tc.oldTribeID
}

func (tc TribeChange) NewTribeID() int {
	return tc.newTribeID
}

func (tc TribeChange) ServerKey() string {
	return tc.serverKey
}

func (tc TribeChange) CreatedAt() time.Time {
	return tc.createdAt
}

func (tc TribeChange) WithRelations(
	player PlayerMeta,
	oldTribe NullTribeMeta,
	newTribe NullTribeMeta,
) TribeChangeWithRelations {
	return TribeChangeWithRelations{
		tribeChange: tc,
		player:      player,
		oldTribe:    oldTribe,
		newTribe:    newTribe,
	}
}

func (tc TribeChange) ToCursor() (TribeChangeCursor, error) {
	return NewTribeChangeCursor(tc.id, tc.serverKey, tc.createdAt)
}

func (tc TribeChange) IsZero() bool {
	return tc == TribeChange{}
}

type TribeChanges []TribeChange

type TribeChangeWithRelations struct {
	tribeChange TribeChange
	player      PlayerMeta
	oldTribe    NullTribeMeta
	newTribe    NullTribeMeta
}

func (tc TribeChangeWithRelations) TribeChange() TribeChange {
	return tc.tribeChange
}

func (tc TribeChangeWithRelations) Player() PlayerMeta {
	return tc.player
}

func (tc TribeChangeWithRelations) OldTribe() NullTribeMeta {
	return tc.oldTribe
}

func (tc TribeChangeWithRelations) NewTribe() NullTribeMeta {
	return tc.newTribe
}

func (tc TribeChangeWithRelations) IsZero() bool {
	return tc.tribeChange.IsZero()
}

type TribeChangesWithRelations []TribeChangeWithRelations

type CreateTribeChangeParams struct {
	serverKey  string
	playerID   int
	newTribeID int
	oldTribeID int
}

const createTribeChangeParamsModelName = "CreateTribeChangeParams"

func NewCreateTribeChangeParams(
	serverKey string,
	playerID int,
	oldTribeID int,
	newTribeID int,
) (CreateTribeChangeParams, error) {
	if err := validateServerKey(serverKey); err != nil {
		return CreateTribeChangeParams{}, ValidationError{
			Model: createTribeChangeParamsModelName,
			Field: "serverKey",
			Err:   err,
		}
	}

	if err := validateIntInRange(playerID, 1, math.MaxInt); err != nil {
		return CreateTribeChangeParams{}, ValidationError{
			Model: createTribeChangeParamsModelName,
			Field: "playerID",
			Err:   err,
		}
	}

	if err := validateIntInRange(oldTribeID, 0, math.MaxInt); err != nil {
		return CreateTribeChangeParams{}, ValidationError{
			Model: createTribeChangeParamsModelName,
			Field: "oldTribeID",
			Err:   err,
		}
	}

	if err := validateIntInRange(newTribeID, 0, math.MaxInt); err != nil {
		return CreateTribeChangeParams{}, ValidationError{
			Model: createTribeChangeParamsModelName,
			Field: "newTribeID",
			Err:   err,
		}
	}

	return CreateTribeChangeParams{
		serverKey:  serverKey,
		playerID:   playerID,
		oldTribeID: oldTribeID,
		newTribeID: newTribeID,
	}, nil
}

// NewCreateTribeChangeParamsFromPlayers constructs a slice of CreatePlayerParams based on the given parameters.
// Both slices must be sorted in ascending order by ID
// + if storedPlayers contains players from different servers. they must be sorted in ascending order by server key.
func NewCreateTribeChangeParamsFromPlayers(
	serverKey string,
	players BasePlayers,
	storedPlayers Players,
) ([]CreateTribeChangeParams, error) {
	// tribe changes happens now and then, there is no point in prereallocating this slice
	//nolint:prealloc
	var params []CreateTribeChangeParams

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

		var old Player
		idx, found := slices.BinarySearchFunc(storedPlayers, player, func(a Player, b BasePlayer) int {
			return cmp.Or(
				cmp.Compare(a.ServerKey(), serverKey),
				cmp.Compare(a.ID(), b.ID()),
			)
		})
		if found {
			old = storedPlayers[idx]
		}

		if (old.ID() > 0 && old.TribeID() == player.TribeID()) || (old.IsZero() && player.TribeID() == 0) {
			continue
		}

		p, err := NewCreateTribeChangeParams(
			serverKey,
			player.ID(),
			old.TribeID(),
			player.TribeID(),
		)
		if err != nil {
			return nil, err
		}

		params = append(params, p)
	}

	return params, nil
}

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

func (params CreateTribeChangeParams) PlayerID() int {
	return params.playerID
}

func (params CreateTribeChangeParams) OldTribeID() int {
	return params.oldTribeID
}

func (params CreateTribeChangeParams) NewTribeID() int {
	return params.newTribeID
}

type TribeChangeSort uint8

const (
	TribeChangeSortCreatedAtASC TribeChangeSort = iota + 1
	TribeChangeSortCreatedAtDESC
	TribeChangeSortIDASC
	TribeChangeSortIDDESC
	TribeChangeSortServerKeyASC
	TribeChangeSortServerKeyDESC
)

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

//nolint:gocyclo
func (s TribeChangeSort) String() string {
	switch s {
	case TribeChangeSortCreatedAtASC:
		return "createdAt:ASC"
	case TribeChangeSortCreatedAtDESC:
		return "createdAt:DESC"
	case TribeChangeSortIDASC:
		return "id:ASC"
	case TribeChangeSortIDDESC:
		return "id:DESC"
	case TribeChangeSortServerKeyASC:
		return "serverKey:ASC"
	case TribeChangeSortServerKeyDESC:
		return "serverKey:DESC"
	default:
		return "unknown tribe change sort"
	}
}

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

const tribeChangeCursorModelName = "TribeChangeCursor"

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

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

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

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

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

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

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

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

	return ec, nil
}

func (tcc TribeChangeCursor) ID() int {
	return tcc.id
}

func (tcc TribeChangeCursor) ServerKey() string {
	return tcc.serverKey
}

func (tcc TribeChangeCursor) CreatedAt() time.Time {
	return tcc.createdAt
}

func (tcc TribeChangeCursor) IsZero() bool {
	return tcc == TribeChangeCursor{}
}

func (tcc TribeChangeCursor) Encode() string {
	if tcc.IsZero() {
		return ""
	}

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

type ListTribeChangesParams struct {
	serverKeys []string
	playerIDs  []int
	tribeIDs   []int // TribeChange.NewTribeID or TribeChange.OldTribeID
	since      NullTime
	before     NullTime
	sort       []TribeChangeSort
	cursor     TribeChangeCursor
	limit      int
}

const (
	TribeChangeListMaxLimit         = 500
	listTribeChangesParamsModelName = "ListTribeChangesParams"
)

func NewListTribeChangesParams() ListTribeChangesParams {
	return ListTribeChangesParams{
		sort: []TribeChangeSort{
			TribeChangeSortServerKeyASC,
			TribeChangeSortCreatedAtASC,
			TribeChangeSortIDASC,
		},
		limit: TribeChangeListMaxLimit,
	}
}

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

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

	params.serverKeys = serverKeys

	return nil
}

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

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

	params.playerIDs = playerIDs

	return nil
}

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

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

	params.tribeIDs = tribeIDs

	return nil
}

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

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

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

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

func (params *ListTribeChangesParams) Sort() []TribeChangeSort {
	return params.sort
}

const (
	tribeChangeSortMinLength = 0
	tribeChangeSortMaxLength = 3
)

func (params *ListTribeChangesParams) SetSort(sort []TribeChangeSort) error {
	if err := validateSort(sort, tribeChangeSortMinLength, tribeChangeSortMaxLength); err != nil {
		return ValidationError{
			Model: listTribeChangesParamsModelName,
			Field: "sort",
			Err:   err,
		}
	}

	params.sort = sort

	return nil
}

func (params *ListTribeChangesParams) Cursor() TribeChangeCursor {
	return params.cursor
}

func (params *ListTribeChangesParams) SetCursor(cursor TribeChangeCursor) error {
	params.cursor = cursor
	return nil
}

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

	params.cursor = decoded

	return nil
}

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

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

	params.limit = limit

	return nil
}

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

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

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

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

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

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

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

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

type ListTribeChangesResult struct {
	tribeChanges TribeChanges
	self         TribeChangeCursor
	next         TribeChangeCursor
}

const listTribeChangesResultModelName = "ListTribeChangesResult"

func NewListTribeChangesResult(tribeChanges TribeChanges, next TribeChange) (ListTribeChangesResult, error) {
	var err error
	res := ListTribeChangesResult{
		tribeChanges: tribeChanges,
	}

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

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

	return res, nil
}

func (res ListTribeChangesResult) TribeChanges() TribeChanges {
	return res.tribeChanges
}

func (res ListTribeChangesResult) Self() TribeChangeCursor {
	return res.self
}

func (res ListTribeChangesResult) Next() TribeChangeCursor {
	return res.next
}

type ListTribeChangesWithRelationsResult struct {
	tribeChanges TribeChangesWithRelations
	self         TribeChangeCursor
	next         TribeChangeCursor
}

const listTribeChangesWithRelationsResultModelName = "ListTribeChangesWithRelationsResult"

func NewListTribeChangesWithRelationsResult(
	tribeChanges TribeChangesWithRelations,
	next TribeChangeWithRelations,
) (ListTribeChangesWithRelationsResult, error) {
	var err error
	res := ListTribeChangesWithRelationsResult{
		tribeChanges: tribeChanges,
	}

	if len(tribeChanges) > 0 {
		res.self, err = tribeChanges[0].TribeChange().ToCursor()
		if err != nil {
			return ListTribeChangesWithRelationsResult{}, ValidationError{
				Model: listTribeChangesWithRelationsResultModelName,
				Field: "self",
				Err:   err,
			}
		}
	}

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

	return res, nil
}

func (res ListTribeChangesWithRelationsResult) TribeChanges() TribeChangesWithRelations {
	return res.tribeChanges
}

func (res ListTribeChangesWithRelationsResult) Self() TribeChangeCursor {
	return res.self
}

func (res ListTribeChangesWithRelationsResult) Next() TribeChangeCursor {
	return res.next
}
