package domain_test

import (
	"math"
	"slices"
	"testing"
	"time"

	"gitea.dwysokinski.me/twhelp/corev3/internal/domain"
	"gitea.dwysokinski.me/twhelp/corev3/internal/domain/domaintest"
	"github.com/brianvoe/gofakeit/v7"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
)

func TestNewSyncServersCmdPayload(t *testing.T) {
	t.Parallel()

	version := domaintest.NewVersion(t)

	payload, err := domain.NewSyncServersCmdPayload(version.Code(), version.URL())
	require.NoError(t, err)
	assert.Equal(t, version.Code(), payload.VersionCode())
	assert.Equal(t, version.URL(), payload.URL())
}

func TestNewServerSyncedEventPayload(t *testing.T) {
	t.Parallel()

	server := domaintest.NewServer(t)

	payload, err := domain.NewServerSyncedEventPayload(server.Key(), server.URL(), server.VersionCode())
	require.NoError(t, err)
	assert.Equal(t, server.Key(), payload.Key())
	assert.Equal(t, server.URL(), payload.URL())
	assert.Equal(t, server.VersionCode(), payload.VersionCode())
}

func TestNewServerSyncedEventPayloads(t *testing.T) {
	t.Parallel()

	versionCode := domaintest.RandVersionCode()
	servers := domain.BaseServers{
		domaintest.NewBaseServer(t),
		domaintest.NewBaseServer(t),
	}

	payloads, err := domain.NewServerSyncedEventPayloads(servers, versionCode)
	require.NoError(t, err)
	for _, s := range servers {
		assert.True(t, slices.ContainsFunc(payloads, func(payload domain.ServerSyncedEventPayload) bool {
			return payload.Key() == s.Key() && payload.VersionCode() == versionCode && payload.URL() == s.URL()
		}))
	}
}

func TestNewTribesSyncedEventPayload(t *testing.T) {
	t.Parallel()

	server := domaintest.NewServer(t)
	numTribes := gofakeit.IntRange(0, math.MaxInt)

	payload, err := domain.NewTribesSyncedEventPayload(
		server.Key(),
		server.URL(),
		server.VersionCode(),
		numTribes,
	)
	require.NoError(t, err)
	assert.Equal(t, server.Key(), payload.ServerKey())
	assert.Equal(t, server.URL(), payload.ServerURL())
	assert.Equal(t, server.VersionCode(), payload.VersionCode())
	assert.Equal(t, numTribes, payload.NumTribes())
}

func TestNewPlayersSyncedEventPayload(t *testing.T) {
	t.Parallel()

	server := domaintest.NewServer(t)
	numPlayers := gofakeit.IntRange(0, math.MaxInt)

	payload, err := domain.NewPlayersSyncedEventPayload(
		server.Key(),
		server.URL(),
		server.VersionCode(),
		numPlayers,
	)
	require.NoError(t, err)
	assert.Equal(t, server.Key(), payload.ServerKey())
	assert.Equal(t, server.URL(), payload.ServerURL())
	assert.Equal(t, server.VersionCode(), payload.VersionCode())
	assert.Equal(t, numPlayers, payload.NumPlayers())
}

func TestNewVillagesSyncedEventPayload(t *testing.T) {
	t.Parallel()

	server := domaintest.NewServer(t)
	numVillages := gofakeit.IntRange(0, math.MaxInt)
	numPlayerVillages := gofakeit.IntRange(0, math.MaxInt)
	numBarbarianVillages := gofakeit.IntRange(0, math.MaxInt)
	numBonusVillages := gofakeit.IntRange(0, math.MaxInt)

	payload, err := domain.NewVillagesSyncedEventPayload(
		server.Key(),
		server.URL(),
		server.VersionCode(),
		numVillages,
		numPlayerVillages,
		numBarbarianVillages,
		numBonusVillages,
	)
	require.NoError(t, err)
	assert.Equal(t, server.Key(), payload.ServerKey())
	assert.Equal(t, server.URL(), payload.ServerURL())
	assert.Equal(t, server.VersionCode(), payload.VersionCode())
	assert.Equal(t, numVillages, payload.NumVillages())
	assert.Equal(t, numPlayerVillages, payload.NumPlayerVillages())
	assert.Equal(t, numBarbarianVillages, payload.NumBarbarianVillages())
	assert.Equal(t, numBonusVillages, payload.NumBonusVillages())
}

func TestNewVillagesSyncedEventPayloadFromVillages(t *testing.T) {
	t.Parallel()

	server := domaintest.NewServer(t)
	villages := domain.BaseVillages{
		domaintest.NewBaseVillage(t, func(cfg *domaintest.BaseVillageConfig) {
			cfg.PlayerID = 0
			cfg.Bonus = 1
		}),
		domaintest.NewBaseVillage(t, func(cfg *domaintest.BaseVillageConfig) {
			cfg.PlayerID = 0
			cfg.Bonus = 0
		}),
		domaintest.NewBaseVillage(t, func(cfg *domaintest.BaseVillageConfig) {
			cfg.PlayerID = 0
			cfg.Bonus = 0
		}),
		domaintest.NewBaseVillage(t, func(cfg *domaintest.BaseVillageConfig) {
			cfg.PlayerID = domaintest.RandID()
			cfg.Bonus = 0
		}),
		domaintest.NewBaseVillage(t, func(cfg *domaintest.BaseVillageConfig) {
			cfg.PlayerID = domaintest.RandID()
			cfg.Bonus = 1
		}),
	}

	payload, err := domain.NewVillagesSyncedEventPayloadFromVillages(
		server.Key(),
		server.URL(),
		server.VersionCode(),
		villages,
	)
	require.NoError(t, err)
	assert.Equal(t, server.Key(), payload.ServerKey())
	assert.Equal(t, server.URL(), payload.ServerURL())
	assert.Equal(t, server.VersionCode(), payload.VersionCode())
	assert.Equal(t, len(villages), payload.NumVillages()) //nolint:testifylint
	assert.Equal(t, 2, payload.NumPlayerVillages())
	assert.Equal(t, 2, payload.NumBonusVillages())
	assert.Equal(t, 3, payload.NumBarbarianVillages())
}

func TestNewSyncEnnoblementsCmdPayload(t *testing.T) {
	t.Parallel()

	server := domaintest.NewServer(t)

	payload, err := domain.NewSyncEnnoblementsCmdPayload(
		server.Key(),
		server.URL(),
		server.VersionCode(),
	)
	require.NoError(t, err)
	assert.Equal(t, server.Key(), payload.ServerKey())
	assert.Equal(t, server.URL(), payload.ServerURL())
	assert.Equal(t, server.VersionCode(), payload.VersionCode())
}

func TestNewEnnoblementsSyncedEventPayload(t *testing.T) {
	t.Parallel()

	server := domaintest.NewServer(t)

	payload, err := domain.NewEnnoblementsSyncedEventPayload(
		server.Key(),
		server.URL(),
		server.VersionCode(),
	)
	require.NoError(t, err)
	assert.Equal(t, server.Key(), payload.ServerKey())
	assert.Equal(t, server.URL(), payload.ServerURL())
	assert.Equal(t, server.VersionCode(), payload.VersionCode())
}

func TestNewCreateSnapshotsCmdPayload(t *testing.T) {
	t.Parallel()

	version := domaintest.NewVersion(t)
	server := domaintest.NewServer(t, func(cfg *domaintest.ServerConfig) {
		cfg.VersionCode = version.Code()
	})
	date := time.Now()

	payload, err := domain.NewCreateSnapshotsCmdPayload(server.Key(), version.Code(), version.Timezone(), date)
	require.NoError(t, err)
	assert.Equal(t, server.Key(), payload.ServerKey())
	assert.Equal(t, version.Code(), payload.VersionCode())
	assert.Equal(t, version.Timezone(), payload.VersionTimezone())
	assert.Equal(t, date, payload.Date())
}

func TestNewSnapshotsCreatedEventPayload(t *testing.T) {
	t.Parallel()

	server := domaintest.NewServer(t)

	payload, err := domain.NewSnapshotsCreatedEventPayload(server.Key(), server.VersionCode())
	require.NoError(t, err)
	assert.Equal(t, server.Key(), payload.ServerKey())
	assert.Equal(t, server.VersionCode(), payload.VersionCode())
}

func TestNewCleanUpDataCmdPayloadServer(t *testing.T) {
	t.Parallel()

	server := domaintest.NewServer(t)

	payloadServer, err := domain.NewCleanUpDataCmdPayloadServer(
		server.Key(),
		server.VersionCode(),
		server.Open(),
		server.Special(),
		server.PlayerDataSyncedAt(),
		server.PlayerSnapshotsCreatedAt(),
		server.TribeDataSyncedAt(),
		server.TribeSnapshotsCreatedAt(),
		server.VillageDataSyncedAt(),
		server.EnnoblementDataSyncedAt(),
	)
	require.NoError(t, err)
	assert.Equal(t, server.Key(), payloadServer.Key())
	assert.Equal(t, server.VersionCode(), payloadServer.VersionCode())
	assert.Equal(t, server.Open(), payloadServer.Open())
	assert.Equal(t, server.Special(), payloadServer.Special())
	assert.Equal(t, server.PlayerDataSyncedAt(), payloadServer.PlayerDataSyncedAt())
	assert.Equal(t, server.PlayerSnapshotsCreatedAt(), payloadServer.PlayerSnapshotsCreatedAt())
	assert.Equal(t, server.TribeDataSyncedAt(), payloadServer.TribeDataSyncedAt())
	assert.Equal(t, server.TribeSnapshotsCreatedAt(), payloadServer.TribeSnapshotsCreatedAt())
	assert.Equal(t, server.VillageDataSyncedAt(), payloadServer.VillageDataSyncedAt())
	assert.Equal(t, server.EnnoblementDataSyncedAt(), payloadServer.EnnoblementDataSyncedAt())
}

func TestNewCleanUpDataCmdPayload(t *testing.T) {
	t.Parallel()

	server := domaintest.NewCleanUpDataCmdPayload(t).Server()

	payload, err := domain.NewCleanUpDataCmdPayload(server)
	require.NoError(t, err)
	assert.Equal(t, server, payload.Server())
}

func TestCleanUpDataCmdPayload_CanCleanUpPlayerSnapshots(t *testing.T) {
	t.Parallel()

	validServerOpt := func(cfg *domaintest.ServerConfig) {
		cfg.Special = false
		cfg.Open = false
		cfg.PlayerSnapshotsCreatedAt = time.Now().Add(-30*24*time.Hour - time.Minute)
	}

	assert.True(t, domaintest.NewCleanUpDataCmdPayload(t, func(cfg *domaintest.CleanUpDataCmdPayloadConfig) {
		cfg.ServerOptions = append(cfg.ServerOptions, validServerOpt)
	}).CanCleanUpPlayerSnapshots(), "special=false open=false playerSnapshotsCreatedAt<now-30 days")

	assert.False(t, domaintest.NewCleanUpDataCmdPayload(t, func(cfg *domaintest.CleanUpDataCmdPayloadConfig) {
		cfg.ServerOptions = append(cfg.ServerOptions, validServerOpt, func(cfg *domaintest.ServerConfig) {
			cfg.Special = true
		})
	}).CanCleanUpPlayerSnapshots(), "special=true open=false playerSnapshotsCreatedAt<now-30 days")

	assert.False(t, domaintest.NewCleanUpDataCmdPayload(t, func(cfg *domaintest.CleanUpDataCmdPayloadConfig) {
		cfg.ServerOptions = append(cfg.ServerOptions, validServerOpt, func(cfg *domaintest.ServerConfig) {
			cfg.Open = true
		})
	}).CanCleanUpPlayerSnapshots(), "special=false open=true playerSnapshotsCreatedAt<now-30 days")

	assert.False(t, domaintest.NewCleanUpDataCmdPayload(t, func(cfg *domaintest.CleanUpDataCmdPayloadConfig) {
		cfg.ServerOptions = append(cfg.ServerOptions, validServerOpt, func(cfg *domaintest.ServerConfig) {
			cfg.PlayerSnapshotsCreatedAt = time.Now().Add(-30*24*time.Hour + time.Minute)
		})
	}).CanCleanUpPlayerSnapshots(), "special=false open=false playerSnapshotsCreatedAt>now-30 days")
}

func TestCleanUpDataCmdPayload_CanCleanUpTribeSnapshots(t *testing.T) {
	t.Parallel()

	validServerOpt := func(cfg *domaintest.ServerConfig) {
		cfg.Special = false
		cfg.Open = false
		cfg.TribeSnapshotsCreatedAt = time.Now().Add(-30*24*time.Hour - time.Minute)
	}

	assert.True(t, domaintest.NewCleanUpDataCmdPayload(t, func(cfg *domaintest.CleanUpDataCmdPayloadConfig) {
		cfg.ServerOptions = append(cfg.ServerOptions, validServerOpt)
	}).CanCleanUpTribeSnapshots(), "special=false open=false tribeSnapshotsCreatedAt<now-30 days")

	assert.False(t, domaintest.NewCleanUpDataCmdPayload(t, func(cfg *domaintest.CleanUpDataCmdPayloadConfig) {
		cfg.ServerOptions = append(cfg.ServerOptions, validServerOpt, func(cfg *domaintest.ServerConfig) {
			cfg.Special = true
		})
	}).CanCleanUpTribeSnapshots(), "special=true open=false tribeSnapshotsCreatedAt<now-30 days")

	assert.False(t, domaintest.NewCleanUpDataCmdPayload(t, func(cfg *domaintest.CleanUpDataCmdPayloadConfig) {
		cfg.ServerOptions = append(cfg.ServerOptions, validServerOpt, func(cfg *domaintest.ServerConfig) {
			cfg.Open = true
		})
	}).CanCleanUpTribeSnapshots(), "special=false open=true tribeSnapshotsCreatedAt<now-30 days")

	assert.False(t, domaintest.NewCleanUpDataCmdPayload(t, func(cfg *domaintest.CleanUpDataCmdPayloadConfig) {
		cfg.ServerOptions = append(cfg.ServerOptions, validServerOpt, func(cfg *domaintest.ServerConfig) {
			cfg.TribeSnapshotsCreatedAt = time.Now().Add(-30*24*time.Hour + time.Minute)
		})
	}).CanCleanUpTribeSnapshots(), "special=false open=false tribeSnapshotsCreatedAt>now-30 days")
}

func TestCleanUpDataCmdPayload_CanCleanUpEnnoblements(t *testing.T) {
	t.Parallel()

	validServerOpt := func(cfg *domaintest.ServerConfig) {
		cfg.Special = false
		cfg.Open = false
		cfg.EnnoblementDataSyncedAt = time.Now().Add(-30*24*time.Hour - time.Minute)
	}

	assert.True(t, domaintest.NewCleanUpDataCmdPayload(t, func(cfg *domaintest.CleanUpDataCmdPayloadConfig) {
		cfg.ServerOptions = append(cfg.ServerOptions, validServerOpt)
	}).CanCleanUpEnnoblements(), "special=false open=false ennoblementDataSyncedAt<now-30 days")

	assert.False(t, domaintest.NewCleanUpDataCmdPayload(t, func(cfg *domaintest.CleanUpDataCmdPayloadConfig) {
		cfg.ServerOptions = append(cfg.ServerOptions, validServerOpt, func(cfg *domaintest.ServerConfig) {
			cfg.Special = true
		})
	}).CanCleanUpEnnoblements(), "special=true open=false ennoblementDataSyncedAt<now-30 days")

	assert.False(t, domaintest.NewCleanUpDataCmdPayload(t, func(cfg *domaintest.CleanUpDataCmdPayloadConfig) {
		cfg.ServerOptions = append(cfg.ServerOptions, validServerOpt, func(cfg *domaintest.ServerConfig) {
			cfg.Open = true
		})
	}).CanCleanUpEnnoblements(), "special=false open=true ennoblementDataSyncedAt<now-30 days")

	assert.False(t, domaintest.NewCleanUpDataCmdPayload(t, func(cfg *domaintest.CleanUpDataCmdPayloadConfig) {
		cfg.ServerOptions = append(cfg.ServerOptions, validServerOpt, func(cfg *domaintest.ServerConfig) {
			cfg.EnnoblementDataSyncedAt = time.Now().Add(-30*24*time.Hour + time.Minute)
		})
	}).CanCleanUpEnnoblements(), "special=false open=false ennoblementDataSyncedAt>now-30 days")
}
