package app

import (
	"context"
	"fmt"

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

type VillageRepository interface {
	CreateOrUpdate(ctx context.Context, params ...domain.CreateVillageParams) error
	List(ctx context.Context, params domain.ListVillagesParams) (domain.Villages, 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)
	}

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

	if err = svc.delete(ctx, serverKey, villages); err != nil {
		return fmt.Errorf("%s: couldn't delete villages: %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 {
		storedVillages, err := svc.repo.List(ctx, listParams)
		if err != nil {
			return err
		}

		if len(storedVillages) == 0 {
			break
		}

		toDelete = append(toDelete, storedVillages.Delete(serverKey, villages)...)

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

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