package app

import (
	"context"
	"fmt"
	"time"

	"gitea.dwysokinski.me/twhelp/corev3/internal/domain"
)

type ServerRepository interface {
	CreateOrUpdate(ctx context.Context, params ...domain.CreateServerParams) error
	List(ctx context.Context, params domain.ListServersParams) (domain.Servers, error)
	Update(ctx context.Context, key string, params domain.UpdateServerParams) error
}

type ServerService struct {
	repo  ServerRepository
	twSvc TWService
	pub   ServerPublisher
}

func NewServerService(repo ServerRepository, twSvc TWService, pub ServerPublisher) *ServerService {
	return &ServerService{repo: repo, twSvc: twSvc, pub: pub}
}

func (svc *ServerService) Sync(ctx context.Context, payload domain.SyncServersCmdPayload) error {
	versionCode := payload.VersionCode()

	openServers, err := svc.twSvc.GetOpenServers(ctx, payload.URL())
	if err != nil {
		return fmt.Errorf("%s: couldn't get open servers: %w", versionCode, err)
	}

	specialServers, err := svc.listAllSpecial(ctx, versionCode)
	if err != nil {
		return fmt.Errorf("%s: couldn't list special servers: %w", versionCode, err)
	}

	currentlyStoredOpenServers, err := svc.ListAllOpen(ctx, versionCode)
	if err != nil {
		return fmt.Errorf("%s: couldn't list open servers: %w", versionCode, err)
	}

	openServersWithoutSpecial := openServers.FilterOutSpecial(specialServers)

	serversToBeClosed, err := currentlyStoredOpenServers.Close(openServersWithoutSpecial)
	if err != nil {
		return fmt.Errorf("%s: couldn't close servers: %w", versionCode, err)
	}

	params, err := domain.NewCreateServerParams(append(openServersWithoutSpecial, serversToBeClosed...), versionCode)
	if err != nil {
		return fmt.Errorf("%s: %w", versionCode, err)
	}

	if err = svc.repo.CreateOrUpdate(ctx, params...); err != nil {
		return fmt.Errorf("%s: couldn't create/update servers: %w", versionCode, err)
	}

	payloads, err := domain.NewServerSyncedEventPayloads(openServersWithoutSpecial, versionCode)
	if err != nil {
		return fmt.Errorf("%s: couldn't construct domain.ServerSyncedEventPayload: %w", versionCode, err)
	}

	if err = svc.pub.EventSynced(ctx, payloads...); err != nil {
		return fmt.Errorf("%s: %w", versionCode, err)
	}

	return nil
}

func (svc *ServerService) listAllSpecial(ctx context.Context, versionCode string) (domain.Servers, error) {
	params := domain.NewListServersParams()
	if err := params.SetVersionCodes([]string{versionCode}); err != nil {
		return nil, err
	}
	if err := params.SetSort([]domain.ServerSort{domain.ServerSortKeyASC}); err != nil {
		return nil, err
	}
	if err := params.SetSpecial(domain.NullBool{
		Value: true,
		Valid: true,
	}); err != nil {
		return nil, err
	}

	return svc.ListAll(ctx, params)
}

func (svc *ServerService) ListAllOpen(ctx context.Context, versionCode string) (domain.Servers, error) {
	params := domain.NewListServersParams()
	if err := params.SetVersionCodes([]string{versionCode}); err != nil {
		return nil, err
	}
	if err := params.SetSort([]domain.ServerSort{domain.ServerSortKeyASC}); err != nil {
		return nil, err
	}
	if err := params.SetOpen(domain.NullBool{
		Value: true,
		Valid: true,
	}); err != nil {
		return nil, err
	}

	return svc.ListAll(ctx, params)
}

// ListAll retrieves all servers from the database based on the given params in an optimal way.
// You can't specify a custom limit/offset/sort/keyGT for this operation.
func (svc *ServerService) ListAll(ctx context.Context, params domain.ListServersParams) (domain.Servers, error) {
	if err := params.SetOffset(0); err != nil {
		return nil, err
	}
	if err := params.SetLimit(domain.ServerListMaxLimit); err != nil {
		return nil, err
	}
	if err := params.SetSort([]domain.ServerSort{domain.ServerSortKeyASC}); err != nil {
		return nil, err
	}

	var servers domain.Servers

	for {
		ss, err := svc.repo.List(ctx, params)
		if err != nil {
			return nil, err
		}

		if len(ss) == 0 {
			return servers, nil
		}

		servers = append(servers, ss...)

		if err = params.SetKeyGT(domain.NullString{
			Value: ss[len(ss)-1].Key(),
			Valid: true,
		}); err != nil {
			return nil, err
		}
	}
}

func (svc *ServerService) SyncConfigAndInfo(ctx context.Context, payload domain.ServerSyncedEventPayload) error {
	key := payload.Key()
	u := payload.URL()

	cfg, err := svc.twSvc.GetServerConfig(ctx, u)
	if err != nil {
		return fmt.Errorf("%s: couldn't get server config: %w", key, err)
	}

	buildingInfo, err := svc.twSvc.GetBuildingInfo(ctx, u)
	if err != nil {
		return fmt.Errorf("%s: couldn't get building info: %w", key, err)
	}

	unitInfo, err := svc.twSvc.GetUnitInfo(ctx, u)
	if err != nil {
		return fmt.Errorf("%s: couldn't get unit info: %w", key, err)
	}

	var updateParams domain.UpdateServerParams
	if err = updateParams.SetConfig(domain.NullServerConfig{
		Value: cfg,
		Valid: true,
	}); err != nil {
		return fmt.Errorf("%s: %w", key, err)
	}
	if err = updateParams.SetBuildingInfo(domain.NullBuildingInfo{
		Value: buildingInfo,
		Valid: true,
	}); err != nil {
		return fmt.Errorf("%s: %w", key, err)
	}
	if err = updateParams.SetUnitInfo(domain.NullUnitInfo{
		Value: unitInfo,
		Valid: true,
	}); err != nil {
		return fmt.Errorf("%s: %w", key, err)
	}

	return svc.repo.Update(ctx, key, updateParams)
}

func (svc *ServerService) UpdateNumTribes(ctx context.Context, payload domain.TribesSyncedEventPayload) error {
	key := payload.ServerKey()

	var updateParams domain.UpdateServerParams
	if err := updateParams.SetNumTribes(domain.NullInt{
		Value: payload.NumTribes(),
		Valid: true,
	}); err != nil {
		return fmt.Errorf("%s: %w", key, err)
	}
	if err := updateParams.SetTribeDataSyncedAt(domain.NullTime{
		Value: time.Now(),
		Valid: true,
	}); err != nil {
		return fmt.Errorf("%s: %w", key, err)
	}

	return svc.repo.Update(ctx, key, updateParams)
}

func (svc *ServerService) UpdateNumPlayers(ctx context.Context, payload domain.PlayersSyncedEventPayload) error {
	key := payload.ServerKey()

	var updateParams domain.UpdateServerParams
	if err := updateParams.SetNumPlayers(domain.NullInt{
		Value: payload.NumPlayers(),
		Valid: true,
	}); err != nil {
		return fmt.Errorf("%s: %w", key, err)
	}
	if err := updateParams.SetPlayerDataSyncedAt(domain.NullTime{
		Value: time.Now(),
		Valid: true,
	}); err != nil {
		return fmt.Errorf("%s: %w", key, err)
	}

	return svc.repo.Update(ctx, key, updateParams)
}

func (svc *ServerService) UpdateNumVillages(ctx context.Context, payload domain.VillagesSyncedEventPayload) error {
	key := payload.ServerKey()

	var updateParams domain.UpdateServerParams
	if err := updateParams.SetNumVillages(domain.NullInt{
		Value: payload.NumVillages(),
		Valid: true,
	}); err != nil {
		return fmt.Errorf("%s: %w", key, err)
	}
	if err := updateParams.SetNumPlayerVillages(domain.NullInt{
		Value: payload.NumPlayerVillages(),
		Valid: true,
	}); err != nil {
		return fmt.Errorf("%s: %w", key, err)
	}
	if err := updateParams.SetNumBarbarianVillages(domain.NullInt{
		Value: payload.NumBarbarianVillages(),
		Valid: true,
	}); err != nil {
		return fmt.Errorf("%s: %w", key, err)
	}
	if err := updateParams.SetNumBonusVillages(domain.NullInt{
		Value: payload.NumBonusVillages(),
		Valid: true,
	}); err != nil {
		return fmt.Errorf("%s: %w", key, err)
	}
	if err := updateParams.SetVillageDataSyncedAt(domain.NullTime{
		Value: time.Now(),
		Valid: true,
	}); err != nil {
		return fmt.Errorf("%s: %w", key, err)
	}

	return svc.repo.Update(ctx, key, updateParams)
}

func (svc *ServerService) UpdateEnnoblementDataSyncedAt(
	ctx context.Context,
	payload domain.EnnoblementsSyncedEventPayload,
) error {
	key := payload.ServerKey()

	var updateParams domain.UpdateServerParams
	if err := updateParams.SetEnnoblementDataSyncedAt(domain.NullTime{
		Value: time.Now(),
		Valid: true,
	}); err != nil {
		return fmt.Errorf("%s: %w", key, err)
	}

	return svc.repo.Update(ctx, key, updateParams)
}
