package domain

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

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) 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,
	}
}

type Ennoblements []Ennoblement

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
)

const EnnoblementListMaxLimit = 200

type ListEnnoblementsParams struct {
	serverKeys []string
	sort       []EnnoblementSort
	limit      int
	offset     int
}

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

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

const (
	ennoblementSortMinLength = 1
	ennoblementSortMaxLength = 3
)

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

	params.sort = sort

	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
}

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

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

	params.offset = offset

	return nil
}
