package domain

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

type PlayerSnapshot struct {
	id          int
	playerID    int
	serverKey   string
	numVillages int
	points      int
	rank        int
	tribeID     int
	od          OpponentsDefeated
	date        time.Time
	createdAt   time.Time
}

const playerSnapshotModelName = "PlayerSnapshot"

// UnmarshalPlayerSnapshotFromDatabase unmarshals PlayerSnapshot from the database.
//
// It should be used only for unmarshalling from the database!
// You can't use UnmarshalPlayerSnapshotFromDatabase as constructor - It may put domain into the invalid state!
func UnmarshalPlayerSnapshotFromDatabase(
	id int,
	playerID int,
	serverKey string,
	numVillages int,
	points int,
	rank int,
	tribeID int,
	od OpponentsDefeated,
	date time.Time,
	createdAt time.Time,
) (PlayerSnapshot, error) {
	if err := validateIntInRange(id, 1, math.MaxInt); err != nil {
		return PlayerSnapshot{}, ValidationError{
			Model: playerSnapshotModelName,
			Field: "id",
			Err:   err,
		}
	}

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

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

	return PlayerSnapshot{
		id:          id,
		playerID:    playerID,
		serverKey:   serverKey,
		numVillages: numVillages,
		points:      points,
		rank:        rank,
		tribeID:     tribeID,
		od:          od,
		date:        date,
		createdAt:   createdAt,
	}, nil
}

func (ps PlayerSnapshot) ID() int {
	return ps.id
}

func (ps PlayerSnapshot) PlayerID() int {
	return ps.playerID
}

func (ps PlayerSnapshot) ServerKey() string {
	return ps.serverKey
}

func (ps PlayerSnapshot) NumVillages() int {
	return ps.numVillages
}

func (ps PlayerSnapshot) Points() int {
	return ps.points
}

func (ps PlayerSnapshot) Rank() int {
	return ps.rank
}

func (ps PlayerSnapshot) TribeID() int {
	return ps.tribeID
}

func (ps PlayerSnapshot) OD() OpponentsDefeated {
	return ps.od
}

func (ps PlayerSnapshot) Date() time.Time {
	return ps.date
}

func (ps PlayerSnapshot) CreatedAt() time.Time {
	return ps.createdAt
}

type PlayerSnapshots []PlayerSnapshot

type CreatePlayerSnapshotParams struct {
	playerID    int
	serverKey   string
	numVillages int
	points      int
	rank        int
	tribeID     int
	od          OpponentsDefeated
	date        time.Time
}

func NewCreatePlayerSnapshotParams(players Players, date time.Time) ([]CreatePlayerSnapshotParams, error) {
	params := make([]CreatePlayerSnapshotParams, 0, len(players))

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

		if p.IsDeleted() {
			continue
		}

		params = append(params, CreatePlayerSnapshotParams{
			playerID:    p.ID(),
			serverKey:   p.ServerKey(),
			numVillages: p.NumVillages(),
			points:      p.Points(),
			rank:        p.Rank(),
			tribeID:     p.TribeID(),
			od:          p.OD(),
			date:        date,
		})
	}

	return params, nil
}

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

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

func (params CreatePlayerSnapshotParams) NumVillages() int {
	return params.numVillages
}

func (params CreatePlayerSnapshotParams) Points() int {
	return params.points
}

func (params CreatePlayerSnapshotParams) Rank() int {
	return params.rank
}

func (params CreatePlayerSnapshotParams) TribeID() int {
	return params.tribeID
}

func (params CreatePlayerSnapshotParams) OD() OpponentsDefeated {
	return params.od
}

func (params CreatePlayerSnapshotParams) Date() time.Time {
	return params.date
}

type PlayerSnapshotSort uint8

const (
	PlayerSnapshotSortDateASC PlayerSnapshotSort = iota + 1
	PlayerSnapshotSortDateDESC
	PlayerSnapshotSortIDASC
	PlayerSnapshotSortIDDESC
	PlayerSnapshotSortServerKeyASC
	PlayerSnapshotSortServerKeyDESC
)

const PlayerSnapshotListMaxLimit = 200

type ListPlayerSnapshotsParams struct {
	serverKeys []string
	sort       []PlayerSnapshotSort
	limit      int
	offset     int
}

const listPlayerSnapshotsParamsModelName = "ListPlayerSnapshotsParams"

func NewListPlayerSnapshotsParams() ListPlayerSnapshotsParams {
	return ListPlayerSnapshotsParams{
		sort: []PlayerSnapshotSort{
			PlayerSnapshotSortServerKeyASC,
			PlayerSnapshotSortDateASC,
			PlayerSnapshotSortIDASC,
		},
		limit: PlayerSnapshotListMaxLimit,
	}
}

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

func (params *ListPlayerSnapshotsParams) SetServerKeys(serverKeys []string) error {
	params.serverKeys = serverKeys
	return nil
}

func (params *ListPlayerSnapshotsParams) Sort() []PlayerSnapshotSort {
	return params.sort
}

const (
	playerSnapshotSortMinLength = 1
	playerSnapshotSortMaxLength = 3
)

func (params *ListPlayerSnapshotsParams) SetSort(sort []PlayerSnapshotSort) error {
	if err := validateSliceLen(sort, playerSnapshotSortMinLength, playerSnapshotSortMaxLength); err != nil {
		return ValidationError{
			Model: listPlayerSnapshotsParamsModelName,
			Field: "sort",
			Err:   err,
		}
	}

	params.sort = sort

	return nil
}

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

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

	params.limit = limit

	return nil
}

func (params *ListPlayerSnapshotsParams) Offset() int {
	return params.offset
}

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

	params.offset = offset

	return nil
}
