package domain

import (
	"cmp"
	"fmt"
	"math"
	"net/url"
	"slices"
	"time"
)

const (
	villageNameMinLength      = 1
	villageNameMaxLength      = 150
	villageContinentMinLength = 1
	villageContinentMaxLength = 5
)

type Village struct {
	id         int
	serverKey  string
	name       string
	points     int
	x          int
	y          int
	continent  string
	bonus      int
	playerID   int
	profileURL *url.URL
	createdAt  time.Time
}

const villageModelName = "Village"

// UnmarshalVillageFromDatabase unmarshals Village from the database.
//
// It should be used only for unmarshalling from the database!
// You can't use UnmarshalVillageFromDatabase as constructor - It may put domain into the invalid state!
func UnmarshalVillageFromDatabase(
	id int,
	serverKey string,
	name string,
	points int,
	x int,
	y int,
	continent string,
	bonus int,
	playerID int,
	rawProfileURL string,
	createdAt time.Time,
) (Village, error) {
	if err := validateIntInRange(id, 1, math.MaxInt); err != nil {
		return Village{}, ValidationError{
			Model: villageModelName,
			Field: "id",
			Err:   err,
		}
	}

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

	profileURL, err := parseURL(rawProfileURL)
	if err != nil {
		return Village{}, ValidationError{
			Model: villageModelName,
			Field: "profileURL",
			Err:   err,
		}
	}

	return Village{
		id:         id,
		serverKey:  serverKey,
		name:       name,
		points:     points,
		x:          x,
		y:          y,
		continent:  continent,
		bonus:      bonus,
		playerID:   playerID,
		profileURL: profileURL,
		createdAt:  createdAt,
	}, nil
}

func (v Village) ID() int {
	return v.id
}

func (v Village) ServerKey() string {
	return v.serverKey
}

func (v Village) Name() string {
	return v.name
}

func (v Village) Points() int {
	return v.points
}

func (v Village) X() int {
	return v.x
}

func (v Village) Y() int {
	return v.y
}

func (v Village) Continent() string {
	return v.continent
}

func (v Village) Bonus() int {
	return v.bonus
}

func (v Village) PlayerID() int {
	return v.playerID
}

func (v Village) ProfileURL() *url.URL {
	return v.profileURL
}

func (v Village) CreatedAt() time.Time {
	return v.createdAt
}

func (v Village) Base() BaseVillage {
	return BaseVillage{
		id:         v.id,
		name:       v.name,
		points:     v.points,
		x:          v.x,
		y:          v.y,
		continent:  v.continent,
		bonus:      v.bonus,
		playerID:   v.playerID,
		profileURL: v.profileURL,
	}
}

type Villages []Village

// Delete finds all villages with the given serverKey that are not in the given slice with active villages
// and returns their ids. Both slices must be sorted in ascending order by ID
// + if vs contains villages from different servers. they must be sorted in ascending order by server key.
func (vs Villages) Delete(serverKey string, active BaseVillages) []int {
	// villages are deleted now and then, there is no point in prereallocating these slices
	//nolint:prealloc
	var toDelete []int

	for _, v := range vs {
		if v.ServerKey() != serverKey {
			continue
		}

		_, found := slices.BinarySearchFunc(active, v, func(a BaseVillage, b Village) int {
			return cmp.Compare(a.ID(), b.ID())
		})
		if found {
			continue
		}

		toDelete = append(toDelete, v.ID())
	}

	return toDelete
}

type CreateVillageParams struct {
	base      BaseVillage
	serverKey string
}

const createVillageParamsModelName = "CreateVillageParams"

func NewCreateVillageParams(serverKey string, villages BaseVillages) ([]CreateVillageParams, error) {
	if err := validateServerKey(serverKey); err != nil {
		return nil, ValidationError{
			Model: createVillageParamsModelName,
			Field: "serverKey",
			Err:   err,
		}
	}

	params := make([]CreateVillageParams, 0, len(villages))

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

		params = append(params, CreateVillageParams{
			base:      v,
			serverKey: serverKey,
		})
	}

	return params, nil
}

func (params CreateVillageParams) Base() BaseVillage {
	return params.base
}

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

type VillageSort uint8

const (
	VillageSortIDASC VillageSort = iota + 1
	VillageSortIDDESC
	VillageSortServerKeyASC
	VillageSortServerKeyDESC
)

const VillageListMaxLimit = 500

type ListVillagesParams struct {
	ids        []int
	idGT       NullInt
	serverKeys []string
	sort       []VillageSort
	limit      int
	offset     int
}

const listVillagesParamsModelName = "ListVillagesParams"

func NewListVillagesParams() ListVillagesParams {
	return ListVillagesParams{
		sort: []VillageSort{
			VillageSortServerKeyASC,
			VillageSortIDASC,
		},
		limit: VillageListMaxLimit,
	}
}

func (params *ListVillagesParams) IDs() []int {
	return params.ids
}

func (params *ListVillagesParams) SetIDs(ids []int) error {
	for i, id := range ids {
		if err := validateIntInRange(id, 0, math.MaxInt); err != nil {
			return SliceElementValidationError{
				Model: listVillagesParamsModelName,
				Field: "ids",
				Index: i,
				Err:   err,
			}
		}
	}

	params.ids = ids

	return nil
}

func (params *ListVillagesParams) IDGT() NullInt {
	return params.idGT
}

func (params *ListVillagesParams) SetIDGT(idGT NullInt) error {
	if idGT.Valid {
		if err := validateIntInRange(idGT.Value, 0, math.MaxInt); err != nil {
			return ValidationError{
				Model: listVillagesParamsModelName,
				Field: "idGT",
				Err:   err,
			}
		}
	}

	params.idGT = idGT

	return nil
}

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

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

func (params *ListVillagesParams) Sort() []VillageSort {
	return params.sort
}

const (
	villageSortMinLength = 1
	villageSortMaxLength = 2
)

func (params *ListVillagesParams) SetSort(sort []VillageSort) error {
	if err := validateSliceLen(sort, villageSortMinLength, villageSortMaxLength); err != nil {
		return ValidationError{
			Model: listVillagesParamsModelName,
			Field: "sort",
			Err:   err,
		}
	}

	params.sort = sort

	return nil
}

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

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

	params.limit = limit

	return nil
}

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

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

	params.offset = offset

	return nil
}
