package app

import (
	"context"
	"fmt"

	"gitea.dwysokinski.me/twhelp/corev3/internal/domain"
	"golang.org/x/sync/errgroup"
)

type VillageRepository interface {
	CreateOrUpdate(ctx context.Context, params ...domain.CreateVillageParams) error
	List(ctx context.Context, params domain.ListVillagesParams) (domain.ListVillagesResult, error)
	ListWithRelations(
		ctx context.Context,
		params domain.ListVillagesParams,
	) (domain.ListVillagesWithRelationsResult, error)
	Delete(ctx context.Context, serverKey string, ids ...int) error
}

type VillageService struct {
	repo  VillageRepository
	twSvc TWService
	pub   VillagePublisher
}

func NewVillageService(repo VillageRepository, twSvc TWService, pub VillagePublisher) *VillageService {
	return &VillageService{repo: repo, twSvc: twSvc, pub: pub}
}

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

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

	g, ctx := errgroup.WithContext(ctx)

	g.Go(func() error {
		if createOrUpdateErr := svc.createOrUpdate(ctx, serverKey, villages); createOrUpdateErr != nil {
			return fmt.Errorf("couldn't create/update villages: %w", createOrUpdateErr)
		}
		return nil
	})

	g.Go(func() error {
		if deleteErr := svc.delete(ctx, serverKey, villages); deleteErr != nil {
			return fmt.Errorf("couldn't delete villages: %w", deleteErr)
		}
		return nil
	})

	if err = g.Wait(); err != nil {
		return fmt.Errorf("%s: %w", serverKey, err)
	}

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

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

	return nil
}

const villageCreateOrUpdateChunkSize = domain.VillageListMaxLimit

func (svc *VillageService) createOrUpdate(ctx context.Context, serverKey string, villages domain.BaseVillages) error {
	for i := 0; i < len(villages); i += villageCreateOrUpdateChunkSize {
		end := i + villageCreateOrUpdateChunkSize
		if end > len(villages) {
			end = len(villages)
		}

		createParams, err := domain.NewCreateVillageParams(serverKey, villages[i:end])
		if err != nil {
			return err
		}

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

	return nil
}

func (svc *VillageService) delete(ctx context.Context, serverKey string, villages domain.BaseVillages) error {
	listParams := domain.NewListVillagesParams()
	if err := listParams.SetServerKeys([]string{serverKey}); err != nil {
		return err
	}
	if err := listParams.SetSort([]domain.VillageSort{domain.VillageSortIDASC}); err != nil {
		return err
	}
	if err := listParams.SetLimit(domain.VillageListMaxLimit); err != nil {
		return err
	}

	var toDelete []int

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

		toDelete = append(toDelete, res.Villages().Delete(serverKey, villages)...)

		if res.Next().IsZero() {
			break
		}

		if err = listParams.SetCursor(res.Next()); err != nil {
			return err
		}
	}

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

func (svc *VillageService) List(
	ctx context.Context,
	params domain.ListVillagesParams,
) (domain.ListVillagesResult, error) {
	return svc.repo.List(ctx, params)
}

func (svc *VillageService) ListWithRelations(
	ctx context.Context,
	params domain.ListVillagesParams,
) (domain.ListVillagesWithRelationsResult, error) {
	return svc.repo.ListWithRelations(ctx, params)
}

func (svc *VillageService) GetWithRelations(
	ctx context.Context,
	id int,
	serverKey string,
) (domain.VillageWithRelations, error) {
	params := domain.NewListVillagesParams()
	if err := params.SetIDs([]int{id}); err != nil {
		return domain.VillageWithRelations{}, err
	}
	if err := params.SetServerKeys([]string{serverKey}); err != nil {
		return domain.VillageWithRelations{}, err
	}

	res, err := svc.repo.ListWithRelations(ctx, params)
	if err != nil {
		return domain.VillageWithRelations{}, err
	}

	villages := res.Villages()
	if len(villages) == 0 {
		return domain.VillageWithRelations{}, domain.VillageNotFoundError{
			ID:        id,
			ServerKey: serverKey,
		}
	}

	return villages[0], nil
}
