package app

import (
	"context"
	"fmt"

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

type TribeRepository interface {
	CreateOrUpdate(ctx context.Context, params ...domain.CreateTribeParams) error
	UpdateDominance(ctx context.Context, serverKey string, numPlayerVillages int) error
	List(ctx context.Context, params domain.ListTribesParams) (domain.Tribes, error)
	// Delete marks players with the given serverKey and ids as deleted (sets deleted at to now).
	//
	// https://en.wiktionary.org/wiki/soft_deletion
	Delete(ctx context.Context, serverKey string, ids ...int) error
}

type TribeService struct {
	repo  TribeRepository
	twSvc TWService
	pub   TribePublisher
}

func NewTribeService(repo TribeRepository, twSvc TWService, pub TribePublisher) *TribeService {
	return &TribeService{repo: repo, twSvc: twSvc, pub: pub}
}

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

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

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

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

	tribesSyncedPayload, err := domain.NewTribesSyncedEventPayload(
		serverKey,
		serverURL,
		serverSyncedPayload.VersionCode(),
		len(tribes),
	)
	if err != nil {
		return fmt.Errorf("%s: couldn't construct domain.TribesSyncedEventPayload: %w", serverKey, err)
	}

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

	return nil
}

const tribeCreateOrUpdateChunkSize = domain.TribeListMaxLimit

func (svc *TribeService) createOrUpdate(ctx context.Context, serverKey string, tribes domain.BaseTribes) error {
	for i := 0; i < len(tribes); i += tribeCreateOrUpdateChunkSize {
		end := i + tribeCreateOrUpdateChunkSize
		if end > len(tribes) {
			end = len(tribes)
		}

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

	return nil
}

func (svc *TribeService) createOrUpdateChunk(ctx context.Context, serverKey string, tribes domain.BaseTribes) error {
	ids := make([]int, 0, len(tribes))
	for _, t := range tribes {
		ids = append(ids, t.ID())
	}

	listParams := domain.NewListTribesParams()
	if err := listParams.SetServerKeys([]string{serverKey}); err != nil {
		return err
	}
	if err := listParams.SetIDs(ids); err != nil {
		return err
	}
	if err := listParams.SetSort([]domain.TribeSort{domain.TribeSortIDASC}); err != nil {
		return err
	}
	if err := listParams.SetLimit(domain.TribeListMaxLimit); err != nil {
		return err
	}

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

	createParams, err := domain.NewCreateTribeParams(serverKey, tribes, storedTribes)
	if err != nil {
		return err
	}

	return svc.repo.CreateOrUpdate(ctx, createParams...)
}

func (svc *TribeService) delete(ctx context.Context, serverKey string, tribes domain.BaseTribes) error {
	listParams := domain.NewListTribesParams()
	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.TribeSort{domain.TribeSortIDASC}); err != nil {
		return err
	}
	if err := listParams.SetLimit(domain.TribeListMaxLimit); err != nil {
		return err
	}

	var toDelete []int

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

		if len(storedTribes) == 0 {
			break
		}

		toDelete = append(toDelete, storedTribes.Delete(serverKey, tribes)...)

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

	return svc.repo.Delete(ctx, serverKey, toDelete...)
}

func (svc *TribeService) UpdateDominance(ctx context.Context, payload domain.VillagesSyncedEventPayload) error {
	return svc.repo.UpdateDominance(ctx, payload.ServerKey(), payload.NumPlayerVillages())
}
