package app

import (
	"context"
	"fmt"

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

type PlayerRepository interface {
	CreateOrUpdate(ctx context.Context, params ...domain.CreatePlayerParams) error
	List(ctx context.Context, params domain.ListPlayersParams) (domain.Players, error)
	// Delete marks players with the given serverKey and ids as deleted (sets deleted at to now).
	// In addition, Delete sets TribeID to null.
	//
	// https://en.wiktionary.org/wiki/soft_deletion
	Delete(ctx context.Context, serverKey string, ids ...int) error
}

type PlayerService struct {
	repo           PlayerRepository
	tribeChangeSvc *TribeChangeService
	twSvc          TWService
	pub            PlayerPublisher
}

func NewPlayerService(
	repo PlayerRepository,
	tribeChangeSvc *TribeChangeService,
	twSvc TWService,
	pub PlayerPublisher,
) *PlayerService {
	return &PlayerService{repo: repo, tribeChangeSvc: tribeChangeSvc, twSvc: twSvc, pub: pub}
}

func (svc *PlayerService) Sync(ctx context.Context, serverSyncedPayload domain.ServerSyncedEventPayload) error {
	serverKey := serverSyncedPayload.Key()
	serverURL := serverSyncedPayload.URL()

	players, err := svc.twSvc.GetPlayers(ctx, serverURL)
	if err != nil {
		return fmt.Errorf("%s: couldn't get players: %w", serverKey, err)
	}

	if err = svc.createOrUpdate(ctx, serverKey, players); err != nil {
		return fmt.Errorf("%s: couldn't create/update players: %w", serverKey, err)
	}

	if err = svc.delete(ctx, serverKey, players); err != nil {
		return fmt.Errorf("%s: couldn't delete players: %w", serverKey, err)
	}

	playersSyncedPayload, err := domain.NewPlayersSyncedEventPayload(
		serverKey,
		serverURL,
		serverSyncedPayload.VersionCode(),
		len(players),
	)
	if err != nil {
		return fmt.Errorf("%s: couldn't construct domain.PlayersSyncedEventPayload: %w", serverKey, err)
	}

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

	return nil
}

const playerCreateOrUpdateChunkSize = domain.PlayerListMaxLimit

func (svc *PlayerService) createOrUpdate(ctx context.Context, serverKey string, players domain.BasePlayers) error {
	for i := 0; i < len(players); i += playerCreateOrUpdateChunkSize {
		end := i + playerCreateOrUpdateChunkSize
		if end > len(players) {
			end = len(players)
		}

		if err := svc.createOrUpdateChunk(ctx, serverKey, players[i:end]); err != nil {
			return err
		}
	}

	return nil
}

func (svc *PlayerService) createOrUpdateChunk(ctx context.Context, serverKey string, players domain.BasePlayers) error {
	ids := make([]int, 0, len(players))
	for _, p := range players {
		ids = append(ids, p.ID())
	}

	listParams := domain.NewListPlayersParams()
	if err := listParams.SetServerKeys([]string{serverKey}); err != nil {
		return err
	}
	if err := listParams.SetIDs(ids); err != nil {
		return err
	}
	if err := listParams.SetSort([]domain.PlayerSort{domain.PlayerSortIDASC}); err != nil {
		return err
	}
	if err := listParams.SetLimit(domain.PlayerListMaxLimit); err != nil {
		return err
	}

	storedPlayers, err := svc.repo.List(ctx, listParams)
	if err != nil {
		return err
	}

	createParams, err := domain.NewCreatePlayerParams(serverKey, players, storedPlayers)
	if err != nil {
		return err
	}

	tribeChangesParams, err := domain.NewCreateTribeChangeParamsFromPlayers(serverKey, players, storedPlayers)
	if err != nil {
		return err
	}

	if err = svc.repo.CreateOrUpdate(ctx, createParams...); err != nil {
		return err
	}

	return svc.tribeChangeSvc.Create(ctx, tribeChangesParams...)
}

//nolint:gocyclo
func (svc *PlayerService) delete(ctx context.Context, serverKey string, players domain.BasePlayers) error {
	listParams := domain.NewListPlayersParams()
	if err := listParams.SetServerKeys([]string{serverKey}); err != nil {
		return err
	}
	if err := listParams.SetDeleted(domain.NullBool{
		Value: false,
		Valid: true,
	}); err != nil {
		return err
	}
	if err := listParams.SetSort([]domain.PlayerSort{domain.PlayerSortIDASC}); err != nil {
		return err
	}
	if err := listParams.SetLimit(domain.PlayerListMaxLimit); err != nil {
		return err
	}

	var toDelete []int
	var tribeChangesParams []domain.CreateTribeChangeParams

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

		if len(storedPlayers) == 0 {
			break
		}

		ids, params, err := storedPlayers.Delete(serverKey, players)
		if err != nil {
			return err
		}

		toDelete = append(toDelete, ids...)
		tribeChangesParams = append(tribeChangesParams, params...)

		if err = listParams.SetIDGT(domain.NullInt{
			Value: storedPlayers[len(storedPlayers)-1].ID(),
			Valid: true,
		}); err != nil {
			return err
		}
	}

	if err := svc.repo.Delete(ctx, serverKey, toDelete...); err != nil {
		return err
	}

	return svc.tribeChangeSvc.Create(ctx, tribeChangesParams...)
}
