package domain

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

type TribeSnapshot struct {
	id          int
	tribeID     int
	serverKey   string
	numMembers  int
	numVillages int
	points      int
	allPoints   int
	rank        int
	od          OpponentsDefeated
	dominance   float64
	date        time.Time
	createdAt   time.Time
}

const tribeSnapshotModelName = "TribeSnapshot"

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

	if err := validateIntInRange(tribeID, 1, math.MaxInt); err != nil {
		return TribeSnapshot{}, ValidationError{
			Model: tribeSnapshotModelName,
			Field: "tribeID",
			Err:   err,
		}
	}

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

	return TribeSnapshot{
		id:          id,
		tribeID:     tribeID,
		serverKey:   serverKey,
		numMembers:  numMembers,
		numVillages: numVillages,
		points:      points,
		allPoints:   allPoints,
		rank:        rank,
		od:          od,
		dominance:   dominance,
		date:        date,
		createdAt:   createdAt,
	}, nil
}

func (ts TribeSnapshot) ID() int {
	return ts.id
}

func (ts TribeSnapshot) TribeID() int {
	return ts.tribeID
}

func (ts TribeSnapshot) ServerKey() string {
	return ts.serverKey
}

func (ts TribeSnapshot) NumMembers() int {
	return ts.numMembers
}

func (ts TribeSnapshot) NumVillages() int {
	return ts.numVillages
}

func (ts TribeSnapshot) Points() int {
	return ts.points
}

func (ts TribeSnapshot) AllPoints() int {
	return ts.allPoints
}

func (ts TribeSnapshot) Rank() int {
	return ts.rank
}

func (ts TribeSnapshot) OD() OpponentsDefeated {
	return ts.od
}

func (ts TribeSnapshot) Dominance() float64 {
	return ts.dominance
}

func (ts TribeSnapshot) Date() time.Time {
	return ts.date
}

func (ts TribeSnapshot) CreatedAt() time.Time {
	return ts.createdAt
}

type TribeSnapshots []TribeSnapshot

type CreateTribeSnapshotParams struct {
	tribeID     int
	serverKey   string
	numMembers  int
	numVillages int
	points      int
	allPoints   int
	rank        int
	od          OpponentsDefeated
	dominance   float64
	date        time.Time
}

func NewCreateTribeSnapshotParams(tribes Tribes, date time.Time) ([]CreateTribeSnapshotParams, error) {
	params := make([]CreateTribeSnapshotParams, 0, len(tribes))

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

		if t.IsDeleted() {
			continue
		}

		params = append(params, CreateTribeSnapshotParams{
			tribeID:     t.ID(),
			serverKey:   t.ServerKey(),
			numMembers:  t.NumMembers(),
			numVillages: t.NumVillages(),
			points:      t.Points(),
			allPoints:   t.AllPoints(),
			rank:        t.Rank(),
			od:          t.OD(),
			dominance:   t.Dominance(),
			date:        date,
		})
	}

	return params, nil
}

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

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

func (params CreateTribeSnapshotParams) NumMembers() int {
	return params.numMembers
}

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

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

func (params CreateTribeSnapshotParams) AllPoints() int {
	return params.allPoints
}

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

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

func (params CreateTribeSnapshotParams) Dominance() float64 {
	return params.dominance
}

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

type TribeSnapshotSort uint8

const (
	TribeSnapshotSortDateASC TribeSnapshotSort = iota + 1
	TribeSnapshotSortDateDESC
	TribeSnapshotSortIDASC
	TribeSnapshotSortIDDESC
	TribeSnapshotSortServerKeyASC
	TribeSnapshotSortServerKeyDESC
)

// IsInConflict returns true if two sorts can't be used together
// (e.g. TribeSnapshotSortIDASC and TribeSnapshotSortIDDESC).
func (s TribeSnapshotSort) IsInConflict(s2 TribeSnapshotSort) bool {
	ss := []TribeSnapshotSort{s, s2}
	slices.Sort(ss)
	// ASC is always an odd number, DESC is always an even number
	return (ss[0]%2 == 1 && ss[0] == ss[1]-1) || ss[0] == ss[1]
}

//nolint:gocyclo
func (s TribeSnapshotSort) String() string {
	switch s {
	case TribeSnapshotSortDateASC:
		return "date:ASC"
	case TribeSnapshotSortDateDESC:
		return "date:DESC"
	case TribeSnapshotSortIDASC:
		return "id:ASC"
	case TribeSnapshotSortIDDESC:
		return "id:DESC"
	case TribeSnapshotSortServerKeyASC:
		return "serverKey:ASC"
	case TribeSnapshotSortServerKeyDESC:
		return "serverKey:DESC"
	default:
		return "unknown tribe snapshot sort"
	}
}

type ListTribeSnapshotsParams struct {
	serverKeys []string
	sort       []TribeSnapshotSort
	limit      int
	offset     int
}

const (
	TribeSnapshotListMaxLimit         = 200
	listTribeSnapshotsParamsModelName = "ListTribeSnapshotsParams"
)

func NewListTribeSnapshotsParams() ListTribeSnapshotsParams {
	return ListTribeSnapshotsParams{
		sort: []TribeSnapshotSort{
			TribeSnapshotSortServerKeyASC,
			TribeSnapshotSortDateASC,
			TribeSnapshotSortIDASC,
		},
		limit: TribeSnapshotListMaxLimit,
	}
}

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

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

	params.serverKeys = serverKeys

	return nil
}

func (params *ListTribeSnapshotsParams) Sort() []TribeSnapshotSort {
	return params.sort
}

const (
	tribeSnapshotSortMinLength = 1
	tribeSnapshotSortMaxLength = 3
)

func (params *ListTribeSnapshotsParams) SetSort(sort []TribeSnapshotSort) error {
	if err := validateSort(sort, tribeSnapshotSortMinLength, tribeSnapshotSortMaxLength); err != nil {
		return ValidationError{
			Model: listTribeSnapshotsParamsModelName,
			Field: "sort",
			Err:   err,
		}
	}

	params.sort = sort

	return nil
}

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

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

	params.limit = limit

	return nil
}

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

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

	params.offset = offset

	return nil
}
