package domain

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

type SyncServersCmdPayload struct {
	versionCode string
	url         *url.URL
}

const syncServersCmdPayloadModelName = "SyncServersCmdPayload"

func NewSyncServersCmdPayload(versionCode string, u *url.URL) (SyncServersCmdPayload, error) {
	if versionCode == "" {
		return SyncServersCmdPayload{}, ValidationError{
			Model: syncServersCmdPayloadModelName,
			Field: "versionCode",
			Err:   ErrRequired,
		}
	}

	if u == nil {
		return SyncServersCmdPayload{}, ValidationError{
			Model: syncServersCmdPayloadModelName,
			Field: "url",
			Err:   ErrNil,
		}
	}

	return SyncServersCmdPayload{versionCode: versionCode, url: u}, nil
}

func (p SyncServersCmdPayload) VersionCode() string {
	return p.versionCode
}

func (p SyncServersCmdPayload) URL() *url.URL {
	return p.url
}

type ServerSyncedEventPayload struct {
	key         string
	url         *url.URL
	versionCode string
}

const serverSyncedEventPayloadModelName = "ServerSyncedEventPayload"

func NewServerSyncedEventPayload(key string, u *url.URL, versionCode string) (ServerSyncedEventPayload, error) {
	if key == "" {
		return ServerSyncedEventPayload{}, ValidationError{
			Model: serverSyncedEventPayloadModelName,
			Field: "key",
			Err:   ErrRequired,
		}
	}

	if versionCode == "" {
		return ServerSyncedEventPayload{}, ValidationError{
			Model: serverSyncedEventPayloadModelName,
			Field: "versionCode",
			Err:   ErrRequired,
		}
	}

	if u == nil {
		return ServerSyncedEventPayload{}, ValidationError{
			Model: serverSyncedEventPayloadModelName,
			Field: "url",
			Err:   ErrNil,
		}
	}

	return ServerSyncedEventPayload{
		key:         key,
		url:         u,
		versionCode: versionCode,
	}, nil
}

func NewServerSyncedEventPayloads(servers BaseServers, versionCode string) ([]ServerSyncedEventPayload, error) {
	res := make([]ServerSyncedEventPayload, 0, len(servers))

	for _, s := range servers {
		payload, err := NewServerSyncedEventPayload(s.Key(), s.URL(), versionCode)
		if err != nil {
			return nil, err
		}

		res = append(res, payload)
	}

	return res, nil
}

func (p ServerSyncedEventPayload) Key() string {
	return p.key
}

func (p ServerSyncedEventPayload) URL() *url.URL {
	return p.url
}

func (p ServerSyncedEventPayload) VersionCode() string {
	return p.versionCode
}

type TribesSyncedEventPayload struct {
	serverKey   string
	serverURL   *url.URL
	versionCode string
	numTribes   int
}

const tribesSyncedEventPayloadModelName = "TribesSyncedEventPayload"

func NewTribesSyncedEventPayload(
	serverKey string,
	serverURL *url.URL,
	versionCode string,
	numTribes int,
) (TribesSyncedEventPayload, error) {
	if serverKey == "" {
		return TribesSyncedEventPayload{}, ValidationError{
			Model: tribesSyncedEventPayloadModelName,
			Field: "serverKey",
			Err:   ErrRequired,
		}
	}

	if serverURL == nil {
		return TribesSyncedEventPayload{}, ValidationError{
			Model: tribesSyncedEventPayloadModelName,
			Field: "serverURL",
			Err:   ErrNil,
		}
	}

	if versionCode == "" {
		return TribesSyncedEventPayload{}, ValidationError{
			Model: tribesSyncedEventPayloadModelName,
			Field: "versionCode",
			Err:   ErrRequired,
		}
	}

	if err := validateIntInRange(numTribes, 0, math.MaxInt); err != nil {
		return TribesSyncedEventPayload{}, ValidationError{
			Model: tribesSyncedEventPayloadModelName,
			Field: "numTribes",
			Err:   err,
		}
	}

	return TribesSyncedEventPayload{
		serverKey:   serverKey,
		serverURL:   serverURL,
		versionCode: versionCode,
		numTribes:   numTribes,
	}, nil
}

func (p TribesSyncedEventPayload) ServerKey() string {
	return p.serverKey
}

func (p TribesSyncedEventPayload) ServerURL() *url.URL {
	return p.serverURL
}

func (p TribesSyncedEventPayload) VersionCode() string {
	return p.versionCode
}

func (p TribesSyncedEventPayload) NumTribes() int {
	return p.numTribes
}

type PlayersSyncedEventPayload struct {
	serverKey   string
	serverURL   *url.URL
	versionCode string
	numPlayers  int
}

const playersSyncedEventPayloadModelName = "PlayersSyncedEventPayload"

func NewPlayersSyncedEventPayload(
	serverKey string,
	serverURL *url.URL,
	versionCode string,
	numPlayers int,
) (PlayersSyncedEventPayload, error) {
	if serverKey == "" {
		return PlayersSyncedEventPayload{}, ValidationError{
			Model: playersSyncedEventPayloadModelName,
			Field: "serverKey",
			Err:   ErrRequired,
		}
	}

	if serverURL == nil {
		return PlayersSyncedEventPayload{}, ValidationError{
			Model: playersSyncedEventPayloadModelName,
			Field: "serverURL",
			Err:   ErrNil,
		}
	}

	if versionCode == "" {
		return PlayersSyncedEventPayload{}, ValidationError{
			Model: playersSyncedEventPayloadModelName,
			Field: "versionCode",
			Err:   ErrRequired,
		}
	}

	if err := validateIntInRange(numPlayers, 0, math.MaxInt); err != nil {
		return PlayersSyncedEventPayload{}, ValidationError{
			Model: playersSyncedEventPayloadModelName,
			Field: "numPlayers",
			Err:   err,
		}
	}

	return PlayersSyncedEventPayload{
		serverKey:   serverKey,
		serverURL:   serverURL,
		versionCode: versionCode,
		numPlayers:  numPlayers,
	}, nil
}

func (p PlayersSyncedEventPayload) ServerKey() string {
	return p.serverKey
}

func (p PlayersSyncedEventPayload) ServerURL() *url.URL {
	return p.serverURL
}

func (p PlayersSyncedEventPayload) VersionCode() string {
	return p.versionCode
}

func (p PlayersSyncedEventPayload) NumPlayers() int {
	return p.numPlayers
}

type VillagesSyncedEventPayload struct {
	serverKey            string
	serverURL            *url.URL
	versionCode          string
	numVillages          int
	numPlayerVillages    int
	numBarbarianVillages int
	numBonusVillages     int
}

const villagesSyncedEventPayloadModelName = "VillagesSyncedEventPayload"

func NewVillagesSyncedEventPayload(
	serverKey string,
	serverURL *url.URL,
	versionCode string,
	numVillages int,
	numPlayerVillages int,
	numBarbarianVillages int,
	numBonusVillages int,
) (VillagesSyncedEventPayload, error) {
	if serverKey == "" {
		return VillagesSyncedEventPayload{}, ValidationError{
			Model: villagesSyncedEventPayloadModelName,
			Field: "serverKey",
			Err:   ErrRequired,
		}
	}

	if serverURL == nil {
		return VillagesSyncedEventPayload{}, ValidationError{
			Model: villagesSyncedEventPayloadModelName,
			Field: "serverURL",
			Err:   ErrNil,
		}
	}

	if versionCode == "" {
		return VillagesSyncedEventPayload{}, ValidationError{
			Model: villagesSyncedEventPayloadModelName,
			Field: "versionCode",
			Err:   ErrRequired,
		}
	}

	if err := validateIntInRange(numVillages, 0, math.MaxInt); err != nil {
		return VillagesSyncedEventPayload{}, ValidationError{
			Model: villagesSyncedEventPayloadModelName,
			Field: "numVillages",
			Err:   err,
		}
	}

	if err := validateIntInRange(numPlayerVillages, 0, math.MaxInt); err != nil {
		return VillagesSyncedEventPayload{}, ValidationError{
			Model: villagesSyncedEventPayloadModelName,
			Field: "numPlayerVillages",
			Err:   err,
		}
	}

	if err := validateIntInRange(numBarbarianVillages, 0, math.MaxInt); err != nil {
		return VillagesSyncedEventPayload{}, ValidationError{
			Model: villagesSyncedEventPayloadModelName,
			Field: "numBarbarianVillages",
			Err:   err,
		}
	}

	if err := validateIntInRange(numBonusVillages, 0, math.MaxInt); err != nil {
		return VillagesSyncedEventPayload{}, ValidationError{
			Model: villagesSyncedEventPayloadModelName,
			Field: "numBonusVillages",
			Err:   err,
		}
	}

	return VillagesSyncedEventPayload{
		serverKey:            serverKey,
		serverURL:            serverURL,
		versionCode:          versionCode,
		numVillages:          numVillages,
		numPlayerVillages:    numPlayerVillages,
		numBarbarianVillages: numBarbarianVillages,
		numBonusVillages:     numBonusVillages,
	}, nil
}

func NewVillagesSyncedEventPayloadFromVillages(
	serverKey string,
	serverURL *url.URL,
	versionCode string,
	villages BaseVillages,
) (VillagesSyncedEventPayload, error) {
	numPlayerVillages := 0
	numBarbarianVillages := 0
	numBonusVillages := 0

	for _, v := range villages {
		if v.PlayerID() > 0 {
			numPlayerVillages++
		} else {
			numBarbarianVillages++
		}

		if v.Bonus() > 0 {
			numBonusVillages++
		}
	}

	return NewVillagesSyncedEventPayload(
		serverKey,
		serverURL,
		versionCode,
		len(villages),
		numPlayerVillages,
		numBarbarianVillages,
		numBonusVillages,
	)
}

func (p VillagesSyncedEventPayload) ServerKey() string {
	return p.serverKey
}

func (p VillagesSyncedEventPayload) ServerURL() *url.URL {
	return p.serverURL
}

func (p VillagesSyncedEventPayload) VersionCode() string {
	return p.versionCode
}

func (p VillagesSyncedEventPayload) NumVillages() int {
	return p.numVillages
}

func (p VillagesSyncedEventPayload) NumPlayerVillages() int {
	return p.numPlayerVillages
}

func (p VillagesSyncedEventPayload) NumBarbarianVillages() int {
	return p.numBarbarianVillages
}

func (p VillagesSyncedEventPayload) NumBonusVillages() int {
	return p.numBonusVillages
}

type SyncEnnoblementsCmdPayload struct {
	serverKey   string
	serverURL   *url.URL
	versionCode string
}

const syncEnnoblementsCmdPayloadModelName = "SyncEnnoblementsCmdPayload"

func NewSyncEnnoblementsCmdPayload(
	serverKey string,
	serverURL *url.URL,
	versionCode string,
) (SyncEnnoblementsCmdPayload, error) {
	if serverKey == "" {
		return SyncEnnoblementsCmdPayload{}, ValidationError{
			Model: syncEnnoblementsCmdPayloadModelName,
			Field: "serverKey",
			Err:   ErrRequired,
		}
	}

	if serverURL == nil {
		return SyncEnnoblementsCmdPayload{}, ValidationError{
			Model: syncEnnoblementsCmdPayloadModelName,
			Field: "serverURL",
			Err:   ErrNil,
		}
	}

	if versionCode == "" {
		return SyncEnnoblementsCmdPayload{}, ValidationError{
			Model: syncEnnoblementsCmdPayloadModelName,
			Field: "versionCode",
			Err:   ErrRequired,
		}
	}

	return SyncEnnoblementsCmdPayload{
		serverKey:   serverKey,
		serverURL:   serverURL,
		versionCode: versionCode,
	}, nil
}

func (p SyncEnnoblementsCmdPayload) ServerKey() string {
	return p.serverKey
}

func (p SyncEnnoblementsCmdPayload) ServerURL() *url.URL {
	return p.serverURL
}

func (p SyncEnnoblementsCmdPayload) VersionCode() string {
	return p.versionCode
}

type EnnoblementsSyncedEventPayload struct {
	serverKey   string
	serverURL   *url.URL
	versionCode string
}

const ennoblementsSyncedEventPayloadModelName = "EnnoblementsSyncedEventPayload"

func NewEnnoblementsSyncedEventPayload(
	serverKey string,
	serverURL *url.URL,
	versionCode string,
) (EnnoblementsSyncedEventPayload, error) {
	if serverKey == "" {
		return EnnoblementsSyncedEventPayload{}, ValidationError{
			Model: ennoblementsSyncedEventPayloadModelName,
			Field: "serverKey",
			Err:   ErrRequired,
		}
	}

	if serverURL == nil {
		return EnnoblementsSyncedEventPayload{}, ValidationError{
			Model: ennoblementsSyncedEventPayloadModelName,
			Field: "serverURL",
			Err:   ErrNil,
		}
	}

	if versionCode == "" {
		return EnnoblementsSyncedEventPayload{}, ValidationError{
			Model: ennoblementsSyncedEventPayloadModelName,
			Field: "versionCode",
			Err:   ErrRequired,
		}
	}

	return EnnoblementsSyncedEventPayload{
		serverKey:   serverKey,
		serverURL:   serverURL,
		versionCode: versionCode,
	}, nil
}

func (p EnnoblementsSyncedEventPayload) ServerKey() string {
	return p.serverKey
}

func (p EnnoblementsSyncedEventPayload) ServerURL() *url.URL {
	return p.serverURL
}

func (p EnnoblementsSyncedEventPayload) VersionCode() string {
	return p.versionCode
}

type CreateSnapshotsCmdPayload struct {
	serverKey       string
	versionCode     string
	versionTimezone string
	date            time.Time
}

const createSnapshotsCmdPayloadModelName = "CreateSnapshotsCmdPayload"

func NewCreateSnapshotsCmdPayload(
	serverKey string,
	versionCode string,
	versionTimezone string,
	date time.Time,
) (CreateSnapshotsCmdPayload, error) {
	if serverKey == "" {
		return CreateSnapshotsCmdPayload{}, ValidationError{
			Model: createSnapshotsCmdPayloadModelName,
			Field: "serverKey",
			Err:   ErrRequired,
		}
	}

	if versionCode == "" {
		return CreateSnapshotsCmdPayload{}, ValidationError{
			Model: createSnapshotsCmdPayloadModelName,
			Field: "versionCode",
			Err:   ErrRequired,
		}
	}

	if versionTimezone == "" {
		return CreateSnapshotsCmdPayload{}, ValidationError{
			Model: createSnapshotsCmdPayloadModelName,
			Field: "versionTimezone",
			Err:   ErrRequired,
		}
	}

	return CreateSnapshotsCmdPayload{
		serverKey:       serverKey,
		versionCode:     versionCode,
		versionTimezone: versionTimezone,
		date:            date,
	}, nil
}

func (p CreateSnapshotsCmdPayload) ServerKey() string {
	return p.serverKey
}

func (p CreateSnapshotsCmdPayload) VersionCode() string {
	return p.versionCode
}

func (p CreateSnapshotsCmdPayload) VersionTimezone() string {
	return p.versionTimezone
}

func (p CreateSnapshotsCmdPayload) Date() time.Time {
	return p.date
}

type SnapshotsCreatedEventPayload struct {
	serverKey   string
	versionCode string
}

const snapshotsCreatedEventPayloadModelName = "SnapshotsCreatedEventPayload"

func NewSnapshotsCreatedEventPayload(serverKey string, versionCode string) (SnapshotsCreatedEventPayload, error) {
	if serverKey == "" {
		return SnapshotsCreatedEventPayload{}, ValidationError{
			Model: snapshotsCreatedEventPayloadModelName,
			Field: "serverKey",
			Err:   ErrRequired,
		}
	}

	if versionCode == "" {
		return SnapshotsCreatedEventPayload{}, ValidationError{
			Model: snapshotsCreatedEventPayloadModelName,
			Field: "versionCode",
			Err:   ErrRequired,
		}
	}

	return SnapshotsCreatedEventPayload{serverKey: serverKey, versionCode: versionCode}, nil
}

func (p SnapshotsCreatedEventPayload) ServerKey() string {
	return p.serverKey
}

func (p SnapshotsCreatedEventPayload) VersionCode() string {
	return p.versionCode
}

type CleanUpDataCmdPayloadServer struct {
	key                      string
	versionCode              string
	open                     bool
	special                  bool
	playerDataSyncedAt       time.Time
	playerSnapshotsCreatedAt time.Time
	tribeDataSyncedAt        time.Time
	tribeSnapshotsCreatedAt  time.Time
	villageDataSyncedAt      time.Time
	ennoblementDataSyncedAt  time.Time
}

const cleanUpDataCmdPayloadServerModelName = "CleanUpDataCmdPayloadServer"

func NewCleanUpDataCmdPayloadServer(
	key string,
	versionCode string,
	open bool,
	special bool,
	playerDataSyncedAt time.Time,
	playerSnapshotsCreatedAt time.Time,
	tribeDataSyncedAt time.Time,
	tribeSnapshotsCreatedAt time.Time,
	villageDataSyncedAt time.Time,
	ennoblementDataSyncedAt time.Time,
) (CleanUpDataCmdPayloadServer, error) {
	if key == "" {
		return CleanUpDataCmdPayloadServer{}, ValidationError{
			Model: cleanUpDataCmdPayloadServerModelName,
			Field: "key",
			Err:   ErrRequired,
		}
	}

	if versionCode == "" {
		return CleanUpDataCmdPayloadServer{}, ValidationError{
			Model: cleanUpDataCmdPayloadServerModelName,
			Field: "versionCode",
			Err:   ErrRequired,
		}
	}

	return CleanUpDataCmdPayloadServer{
		key:                      key,
		versionCode:              versionCode,
		open:                     open,
		special:                  special,
		playerDataSyncedAt:       playerDataSyncedAt,
		playerSnapshotsCreatedAt: playerSnapshotsCreatedAt,
		tribeDataSyncedAt:        tribeDataSyncedAt,
		tribeSnapshotsCreatedAt:  tribeSnapshotsCreatedAt,
		villageDataSyncedAt:      villageDataSyncedAt,
		ennoblementDataSyncedAt:  ennoblementDataSyncedAt,
	}, nil
}

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

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

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

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

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

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

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

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

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

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

func (s CleanUpDataCmdPayloadServer) IsZero() bool {
	return s == CleanUpDataCmdPayloadServer{}
}

type CleanUpDataCmdPayload struct {
	server CleanUpDataCmdPayloadServer
}

const cleanUpDataCmdPayloadModelName = "CleanUpDataCmdPayload"

func NewCleanUpDataCmdPayload(server CleanUpDataCmdPayloadServer) (CleanUpDataCmdPayload, error) {
	if server.IsZero() {
		return CleanUpDataCmdPayload{}, ValidationError{
			Model: cleanUpDataCmdPayloadModelName,
			Field: "server",
			Err:   ErrRequired,
		}
	}

	return CleanUpDataCmdPayload{server: server}, nil
}

func (p CleanUpDataCmdPayload) Server() CleanUpDataCmdPayloadServer {
	return p.server
}

func (p CleanUpDataCmdPayload) CanCleanUpPlayerSnapshots() bool {
	return !p.server.Open() &&
		!p.server.Special() &&
		p.server.PlayerSnapshotsCreatedAt().Before(time.Now().Add(-30*24*time.Hour) /* 30 days */)
}

func (p CleanUpDataCmdPayload) CanCleanUpTribeSnapshots() bool {
	return !p.server.Open() &&
		!p.server.Special() &&
		p.server.TribeSnapshotsCreatedAt().Before(time.Now().Add(-30*24*time.Hour) /* 30 days */)
}

func (p CleanUpDataCmdPayload) CanCleanUpEnnoblements() bool {
	return !p.server.Open() &&
		!p.server.Special() &&
		p.server.EnnoblementDataSyncedAt().Before(time.Now().Add(-30*24*time.Hour) /* 30 days */)
}
