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
}

type TribeChanges []TribeChange

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 {
			if res := cmp.Compare(a.ServerKey(), serverKey); res != 0 {
				return res
			}
			return cmp.Compare(a.ID(), b.ID())
		})
		if found {
			old = storedPlayers[idx]
		}

		if (old.ID() > 0 && old.TribeID() == player.TribeID()) || (old.ID() == 0 && 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
)

const TribeChangeListMaxLimit = 200

type ListTribeChangesParams struct {
	serverKeys []string
	sort       []TribeChangeSort
	limit      int
	offset     int
}

const 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 {
	params.serverKeys = serverKeys
	return nil
}

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

const (
	tribeChangeSortMinLength = 1
	tribeChangeSortMaxLength = 3
)

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

	params.sort = sort

	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) Offset() int {
	return params.offset
}

func (params *ListTribeChangesParams) SetOffset(offset int) error {
	if err := validateIntInRange(offset, 0, math.MaxInt); err != nil {
		return ValidationError{
			Model: listTribeChangesParamsModelName,
			Field: "offset",
			Err:   err,
		}
	}

	params.offset = offset

	return nil
}
