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) FullName() string {
	return fmt.Sprintf("%s (%d|%d) %s", v.name, v.x, v.y, v.continent)
}

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) Coords() Coords {
	return Coords{
		x: v.x,
		y: 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) WithRelations(player NullPlayerMetaWithRelations) VillageWithRelations {
	return VillageWithRelations{
		village: v,
		player:  player,
	}
}

func (v Village) ToCursor() (VillageCursor, error) {
	return NewVillageCursor(v.id, v.serverKey)
}

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

func (v Village) IsZero() bool {
	return v == Village{}
}

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 VillageWithRelations struct {
	village Village
	player  NullPlayerMetaWithRelations
}

func (v VillageWithRelations) Village() Village {
	return v.village
}

func (v VillageWithRelations) Player() NullPlayerMetaWithRelations {
	return v.player
}

func (v VillageWithRelations) IsZero() bool {
	return v.village.IsZero()
}

type VillagesWithRelations []VillageWithRelations

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
)

// IsInConflict returns true if two sorts can't be used together (e.g. VillageSortIDASC and VillageSortIDDESC).
func (s VillageSort) IsInConflict(s2 VillageSort) bool {
	ss := []VillageSort{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 VillageSort) String() string {
	switch s {
	case VillageSortIDASC:
		return "id:ASC"
	case VillageSortIDDESC:
		return "id:DESC"
	case VillageSortServerKeyASC:
		return "serverKey:ASC"
	case VillageSortServerKeyDESC:
		return "serverKey:DESC"
	default:
		return "unknown village sort"
	}
}

type VillageCursor struct {
	id        int
	serverKey string
}

const villageCursorModelName = "VillageCursor"

func NewVillageCursor(id int, serverKey string) (VillageCursor, error) {
	if err := validateIntInRange(id, 1, math.MaxInt); err != nil {
		return VillageCursor{}, ValidationError{
			Model: villageCursorModelName,
			Field: "id",
			Err:   err,
		}
	}

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

	return VillageCursor{
		id:        id,
		serverKey: serverKey,
	}, nil
}

//nolint:gocyclo
func decodeVillageCursor(encoded string) (VillageCursor, error) {
	m, err := decodeCursor(encoded)
	if err != nil {
		return VillageCursor{}, err
	}

	id, err := m.int("id")
	if err != nil {
		return VillageCursor{}, ErrInvalidCursor
	}

	serverKey, err := m.string("serverKey")
	if err != nil {
		return VillageCursor{}, ErrInvalidCursor
	}

	vc, err := NewVillageCursor(
		id,
		serverKey,
	)
	if err != nil {
		return VillageCursor{}, ErrInvalidCursor
	}

	return vc, nil
}

func (vc VillageCursor) ID() int {
	return vc.id
}

func (vc VillageCursor) ServerKey() string {
	return vc.serverKey
}

func (vc VillageCursor) IsZero() bool {
	return vc == VillageCursor{}
}

func (vc VillageCursor) Encode() string {
	if vc.IsZero() {
		return ""
	}

	return encodeCursor([]keyValuePair{
		{"id", vc.id},
		{"serverKey", vc.serverKey},
	})
}

type ListVillagesParams struct {
	ids        []int
	serverKeys []string
	coords     []Coords
	sort       []VillageSort
	cursor     VillageCursor
	limit      int
}

const (
	VillageListMaxLimit         = 500
	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, 1, math.MaxInt); err != nil {
			return SliceElementValidationError{
				Model: listVillagesParamsModelName,
				Field: "ids",
				Index: i,
				Err:   err,
			}
		}
	}

	params.ids = ids

	return nil
}

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

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

	params.serverKeys = serverKeys

	return nil
}

func (params *ListVillagesParams) Coords() []Coords {
	return params.coords
}

const (
	villageCoordsMinLength = 0
	villageCoordsMaxLength = 200
)

func (params *ListVillagesParams) SetCoords(coords []Coords) error {
	if err := validateSliceLen(coords, villageCoordsMinLength, villageCoordsMaxLength); err != nil {
		return ValidationError{
			Model: listVillagesParamsModelName,
			Field: "coords",
			Err:   err,
		}
	}

	params.coords = coords

	return nil
}

func (params *ListVillagesParams) SetCoordsString(rawCoords []string) error {
	if err := validateSliceLen(rawCoords, villageCoordsMinLength, villageCoordsMaxLength); err != nil {
		return ValidationError{
			Model: listVillagesParamsModelName,
			Field: "coords",
			Err:   err,
		}
	}

	coords := make([]Coords, 0, len(rawCoords))

	for i, s := range rawCoords {
		c, err := newCoords(s)
		if err != nil {
			return SliceElementValidationError{
				Model: listVillagesParamsModelName,
				Field: "coords",
				Index: i,
				Err:   err,
			}
		}

		coords = append(coords, c)
	}

	params.coords = coords

	return nil
}

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

const (
	villageSortMinLength = 1
	villageSortMaxLength = 2
)

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

	params.sort = sort

	return nil
}

func (params *ListVillagesParams) Cursor() VillageCursor {
	return params.cursor
}

func (params *ListVillagesParams) SetCursor(cursor VillageCursor) error {
	params.cursor = cursor
	return nil
}

func (params *ListVillagesParams) SetEncodedCursor(encoded string) error {
	decoded, err := decodeVillageCursor(encoded)
	if err != nil {
		return ValidationError{
			Model: listVillagesParamsModelName,
			Field: "cursor",
			Err:   err,
		}
	}

	params.cursor = decoded

	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
}

type ListVillagesResult struct {
	villages Villages
	self     VillageCursor
	next     VillageCursor
}

const listVillagesResultModelName = "ListVillagesResult"

func NewListVillagesResult(villages Villages, next Village) (ListVillagesResult, error) {
	var err error
	res := ListVillagesResult{
		villages: villages,
	}

	if len(villages) > 0 {
		res.self, err = villages[0].ToCursor()
		if err != nil {
			return ListVillagesResult{}, ValidationError{
				Model: listVillagesResultModelName,
				Field: "self",
				Err:   err,
			}
		}
	}

	if !next.IsZero() {
		res.next, err = next.ToCursor()
		if err != nil {
			return ListVillagesResult{}, ValidationError{
				Model: listVillagesResultModelName,
				Field: "next",
				Err:   err,
			}
		}
	}

	return res, nil
}

func (res ListVillagesResult) Villages() Villages {
	return res.villages
}

func (res ListVillagesResult) Self() VillageCursor {
	return res.self
}

func (res ListVillagesResult) Next() VillageCursor {
	return res.next
}

type ListVillagesWithRelationsResult struct {
	villages VillagesWithRelations
	self     VillageCursor
	next     VillageCursor
}

const listVillagesWithRelationsResultModelName = "ListVillagesWithRelationsResult"

func NewListVillagesWithRelationsResult(
	villages VillagesWithRelations,
	next VillageWithRelations,
) (ListVillagesWithRelationsResult, error) {
	var err error
	res := ListVillagesWithRelationsResult{
		villages: villages,
	}

	if len(villages) > 0 {
		res.self, err = villages[0].Village().ToCursor()
		if err != nil {
			return ListVillagesWithRelationsResult{}, ValidationError{
				Model: listVillagesWithRelationsResultModelName,
				Field: "self",
				Err:   err,
			}
		}
	}

	if !next.IsZero() {
		res.next, err = next.Village().ToCursor()
		if err != nil {
			return ListVillagesWithRelationsResult{}, ValidationError{
				Model: listVillagesWithRelationsResultModelName,
				Field: "next",
				Err:   err,
			}
		}
	}

	return res, nil
}

func (res ListVillagesWithRelationsResult) Villages() VillagesWithRelations {
	return res.villages
}

func (res ListVillagesWithRelationsResult) Self() VillageCursor {
	return res.self
}

func (res ListVillagesWithRelationsResult) Next() VillageCursor {
	return res.next
}
