package domain

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

const (
	serverKeyMinLength = 1
	serverKeyMaxLength = 10
)

type Server struct {
	key                      string
	versionCode              string
	url                      *url.URL
	open                     bool
	special                  bool
	numPlayers               int
	numTribes                int
	numVillages              int
	numPlayerVillages        int
	numBarbarianVillages     int
	numBonusVillages         int
	config                   ServerConfig
	buildingInfo             BuildingInfo
	unitInfo                 UnitInfo
	createdAt                time.Time
	playerDataSyncedAt       time.Time
	playerSnapshotsCreatedAt time.Time
	tribeDataSyncedAt        time.Time
	tribeSnapshotsCreatedAt  time.Time
	villageDataSyncedAt      time.Time
	ennoblementDataSyncedAt  time.Time
}

const serverModelName = "Server"

// UnmarshalServerFromDatabase unmarshals Server from the database.
//
// It should be used only for unmarshalling from the database!
// You can't use UnmarshalServerFromDatabase as constructor - It may put domain into the invalid state!
func UnmarshalServerFromDatabase(
	key string,
	versionCode string,
	rawURL string,
	open bool,
	special bool,
	numPlayers int,
	numTribes int,
	numVillages int,
	numPlayerVillages int,
	numBarbarianVillages int,
	numBonusVillages int,
	config ServerConfig,
	buildingInfo BuildingInfo,
	unitInfo UnitInfo,
	createdAt time.Time,
	playerDataSyncedAt time.Time,
	playerSnapshotsCreatedAt time.Time,
	tribeDataSyncedAt time.Time,
	tribeSnapshotsCreatedAt time.Time,
	villageDataSyncedAt time.Time,
	ennoblementDataSyncedAt time.Time,
) (Server, error) {
	if key == "" {
		return Server{}, ValidationError{
			Model: serverModelName,
			Field: "key",
			Err:   ErrRequired,
		}
	}

	if versionCode == "" {
		return Server{}, ValidationError{
			Model: serverModelName,
			Field: "versionCode",
			Err:   ErrRequired,
		}
	}

	u, err := parseURL(rawURL)
	if err != nil {
		return Server{}, ValidationError{
			Model: serverModelName,
			Field: "url",
			Err:   err,
		}
	}

	return Server{
		key:                      key,
		url:                      u,
		open:                     open,
		special:                  special,
		numPlayers:               numPlayers,
		numTribes:                numTribes,
		numVillages:              numVillages,
		numPlayerVillages:        numPlayerVillages,
		numBarbarianVillages:     numBarbarianVillages,
		numBonusVillages:         numBonusVillages,
		config:                   config,
		buildingInfo:             buildingInfo,
		unitInfo:                 unitInfo,
		createdAt:                createdAt,
		playerDataSyncedAt:       playerDataSyncedAt,
		playerSnapshotsCreatedAt: playerSnapshotsCreatedAt,
		tribeDataSyncedAt:        tribeDataSyncedAt,
		tribeSnapshotsCreatedAt:  tribeSnapshotsCreatedAt,
		villageDataSyncedAt:      villageDataSyncedAt,
		ennoblementDataSyncedAt:  ennoblementDataSyncedAt,
		versionCode:              versionCode,
	}, nil
}

func (s Server) Key() string {
	return s.key
}

func (s Server) VersionCode() string {
	return s.versionCode
}

func (s Server) URL() *url.URL {
	return s.url
}

func (s Server) Open() bool {
	return s.open
}

func (s Server) Special() bool {
	return s.special
}

func (s Server) NumPlayers() int {
	return s.numPlayers
}

func (s Server) NumTribes() int {
	return s.numTribes
}

func (s Server) NumVillages() int {
	return s.numVillages
}

func (s Server) NumPlayerVillages() int {
	return s.numPlayerVillages
}

func (s Server) NumBarbarianVillages() int {
	return s.numBarbarianVillages
}

func (s Server) NumBonusVillages() int {
	return s.numBonusVillages
}

func (s Server) Config() ServerConfig {
	return s.config
}

func (s Server) BuildingInfo() BuildingInfo {
	return s.buildingInfo
}

func (s Server) UnitInfo() UnitInfo {
	return s.unitInfo
}

func (s Server) CreatedAt() time.Time {
	return s.createdAt
}

func (s Server) PlayerDataSyncedAt() time.Time {
	return s.playerDataSyncedAt
}

func (s Server) PlayerSnapshotsCreatedAt() time.Time {
	return s.playerSnapshotsCreatedAt
}

func (s Server) TribeDataSyncedAt() time.Time {
	return s.tribeDataSyncedAt
}

func (s Server) TribeSnapshotsCreatedAt() time.Time {
	return s.tribeSnapshotsCreatedAt
}

func (s Server) VillageDataSyncedAt() time.Time {
	return s.villageDataSyncedAt
}

func (s Server) EnnoblementDataSyncedAt() time.Time {
	return s.ennoblementDataSyncedAt
}

func (s Server) Base() BaseServer {
	return BaseServer{
		key:  s.key,
		url:  s.url,
		open: s.open,
	}
}

type Servers []Server

// Close finds all servers with Server.Open returning true that are not in the given slice with open servers
// and then converts them to BaseServers with the corrected open value.
func (ss Servers) Close(open BaseServers) (BaseServers, error) {
	res := make(BaseServers, 0, len(ss))

	for _, s := range ss {
		if !s.Open() || slices.ContainsFunc(open, func(openServer BaseServer) bool {
			return openServer.Key() == s.Key() && openServer.Open() == s.Open()
		}) {
			continue
		}

		base, err := NewBaseServer(s.Key(), s.URL(), false)
		if err != nil {
			return nil, fmt.Errorf("couldn't construct BaseServer for server with key '%s': %w", s.Key(), err)
		}

		res = append(res, base)
	}

	return res, nil
}

type CreateServerParams struct {
	base        BaseServer
	versionCode string
}

const createServerParamsModelName = "CreateServerParams"

func NewCreateServerParams(servers BaseServers, versionCode string) ([]CreateServerParams, error) {
	if err := validateVersionCode(versionCode); err != nil {
		return nil, ValidationError{
			Model: createServerParamsModelName,
			Field: "versionCode",
			Err:   err,
		}
	}

	res := make([]CreateServerParams, 0, len(servers))

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

		res = append(res, CreateServerParams{
			base:        s,
			versionCode: versionCode,
		})
	}

	return res, nil
}

func (params CreateServerParams) Base() BaseServer {
	return params.base
}

func (params CreateServerParams) VersionCode() string {
	return params.versionCode
}

type UpdateServerParams struct {
	config                  NullServerConfig
	buildingInfo            NullBuildingInfo
	unitInfo                NullUnitInfo
	numTribes               NullInt
	tribeDataSyncedAt       NullTime
	numPlayers              NullInt
	playerDataSyncedAt      NullTime
	numVillages             NullInt
	numPlayerVillages       NullInt
	numBarbarianVillages    NullInt
	numBonusVillages        NullInt
	villageDataSyncedAt     NullTime
	ennoblementDataSyncedAt NullTime
}

const updateServerParamsModelName = "UpdateServerParams"

func (params *UpdateServerParams) Config() NullServerConfig {
	return params.config
}

func (params *UpdateServerParams) SetConfig(config NullServerConfig) error {
	params.config = config
	return nil
}

func (params *UpdateServerParams) BuildingInfo() NullBuildingInfo {
	return params.buildingInfo
}

func (params *UpdateServerParams) SetBuildingInfo(buildingInfo NullBuildingInfo) error {
	params.buildingInfo = buildingInfo
	return nil
}

func (params *UpdateServerParams) UnitInfo() NullUnitInfo {
	return params.unitInfo
}

func (params *UpdateServerParams) SetUnitInfo(unitInfo NullUnitInfo) error {
	params.unitInfo = unitInfo
	return nil
}

func (params *UpdateServerParams) NumTribes() NullInt {
	return params.numTribes
}

func (params *UpdateServerParams) SetNumTribes(numTribes NullInt) error {
	if numTribes.Valid {
		if err := validateIntInRange(numTribes.Value, 0, math.MaxInt); err != nil {
			return ValidationError{
				Model: updateServerParamsModelName,
				Field: "numTribes",
				Err:   err,
			}
		}
	}

	params.numTribes = numTribes

	return nil
}

func (params *UpdateServerParams) TribeDataSyncedAt() NullTime {
	return params.tribeDataSyncedAt
}

func (params *UpdateServerParams) SetTribeDataSyncedAt(tribeDataSyncedAt NullTime) error {
	params.tribeDataSyncedAt = tribeDataSyncedAt
	return nil
}

func (params *UpdateServerParams) NumPlayers() NullInt {
	return params.numPlayers
}

func (params *UpdateServerParams) SetNumPlayers(numPlayers NullInt) error {
	params.numPlayers = numPlayers
	return nil
}

func (params *UpdateServerParams) PlayerDataSyncedAt() NullTime {
	return params.playerDataSyncedAt
}

func (params *UpdateServerParams) SetPlayerDataSyncedAt(playerDataSyncedAt NullTime) error {
	params.playerDataSyncedAt = playerDataSyncedAt
	return nil
}

func (params *UpdateServerParams) NumVillages() NullInt {
	return params.numVillages
}

func (params *UpdateServerParams) SetNumVillages(numVillages NullInt) error {
	params.numVillages = numVillages
	return nil
}

func (params *UpdateServerParams) NumPlayerVillages() NullInt {
	return params.numPlayerVillages
}

func (params *UpdateServerParams) SetNumPlayerVillages(numPlayerVillages NullInt) error {
	params.numPlayerVillages = numPlayerVillages
	return nil
}

func (params *UpdateServerParams) NumBarbarianVillages() NullInt {
	return params.numBarbarianVillages
}

func (params *UpdateServerParams) SetNumBarbarianVillages(numBarbarianVillages NullInt) error {
	params.numBarbarianVillages = numBarbarianVillages
	return nil
}

func (params *UpdateServerParams) NumBonusVillages() NullInt {
	return params.numBonusVillages
}

func (params *UpdateServerParams) SetNumBonusVillages(numBonusVillages NullInt) error {
	params.numBonusVillages = numBonusVillages
	return nil
}

func (params *UpdateServerParams) VillageDataSyncedAt() NullTime {
	return params.villageDataSyncedAt
}

func (params *UpdateServerParams) SetVillageDataSyncedAt(villageDataSyncedAt NullTime) error {
	params.villageDataSyncedAt = villageDataSyncedAt
	return nil
}

func (params *UpdateServerParams) EnnoblementDataSyncedAt() NullTime {
	return params.ennoblementDataSyncedAt
}

func (params *UpdateServerParams) SetEnnoblementDataSyncedAt(ennoblementDataSyncedAt NullTime) error {
	params.ennoblementDataSyncedAt = ennoblementDataSyncedAt
	return nil
}

func (params *UpdateServerParams) IsZero() bool {
	return !params.config.Valid &&
		!params.buildingInfo.Valid &&
		!params.unitInfo.Valid &&
		!params.numTribes.Valid &&
		!params.tribeDataSyncedAt.Valid &&
		!params.numPlayers.Valid &&
		!params.playerDataSyncedAt.Valid &&
		!params.numVillages.Valid &&
		!params.villageDataSyncedAt.Valid &&
		!params.ennoblementDataSyncedAt.Valid
}

type ServerSort uint8

const (
	ServerSortKeyASC ServerSort = iota + 1
	ServerSortKeyDESC
	ServerSortOpenASC
	ServerSortOpenDESC
)

const ServerListMaxLimit = 500

type ListServersParams struct {
	keys         []string
	keyGT        NullString
	versionCodes []string
	open         NullBool
	special      NullBool
	sort         []ServerSort
	limit        int
	offset       int
}

const listServersParamsModelName = "ListServersParams"

func NewListServersParams() ListServersParams {
	return ListServersParams{
		sort:  []ServerSort{ServerSortKeyASC},
		limit: ServerListMaxLimit,
		special: NullBool{
			Value: false,
			Valid: true,
		},
	}
}

func (params *ListServersParams) Keys() []string {
	return params.keys
}

func (params *ListServersParams) SetKeys(keys []string) error {
	params.keys = keys
	return nil
}

func (params *ListServersParams) KeyGT() NullString {
	return params.keyGT
}

func (params *ListServersParams) SetKeyGT(keyGT NullString) error {
	params.keyGT = keyGT
	return nil
}

func (params *ListServersParams) VersionCodes() []string {
	return params.versionCodes
}

func (params *ListServersParams) SetVersionCodes(versionCodes []string) error {
	params.versionCodes = versionCodes
	return nil
}

func (params *ListServersParams) Open() NullBool {
	return params.open
}

func (params *ListServersParams) SetOpen(open NullBool) error {
	params.open = open
	return nil
}

func (params *ListServersParams) Special() NullBool {
	return params.special
}

func (params *ListServersParams) SetSpecial(special NullBool) error {
	params.special = special
	return nil
}

func (params *ListServersParams) Sort() []ServerSort {
	return params.sort
}

const (
	serverSortMinLength = 1
	serverSortMaxLength = 2
)

func (params *ListServersParams) SetSort(sort []ServerSort) error {
	if err := validateSliceLen(sort, serverSortMinLength, serverSortMaxLength); err != nil {
		return ValidationError{
			Model: listServersParamsModelName,
			Field: "sort",
			Err:   err,
		}
	}

	params.sort = sort

	return nil
}

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

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

	params.limit = limit

	return nil
}

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

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

	params.offset = offset

	return nil
}

type ServerNotFoundError struct {
	Key string
}

var _ ErrorWithParams = ServerNotFoundError{}

func (e ServerNotFoundError) Error() string {
	return fmt.Sprintf("server with key %s not found", e.Key)
}

func (e ServerNotFoundError) Code() ErrorCode {
	return ErrorCodeNotFound
}

func (e ServerNotFoundError) Slug() string {
	return "server-not-found"
}

func (e ServerNotFoundError) Params() map[string]any {
	return map[string]any{
		"Key": e.Key,
	}
}
