package app

import (
	"context"
	"errors"
	"fmt"
	"time"

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

type EnnoblementRepository interface {
	Create(ctx context.Context, params ...domain.CreateEnnoblementParams) error
	List(ctx context.Context, params domain.ListEnnoblementsParams) (domain.Ennoblements, error)
}

type EnnoblementService struct {
	repo  EnnoblementRepository
	twSvc TWService
	pub   EnnoblementPublisher
}

func NewEnnoblementService(repo EnnoblementRepository, twSvc TWService, pub EnnoblementPublisher) *EnnoblementService {
	return &EnnoblementService{repo: repo, twSvc: twSvc, pub: pub}
}

func (svc *EnnoblementService) Sync(
	ctx context.Context,
	syncEnnoblementsCmdPayload domain.SyncEnnoblementsCmdPayload,
) error {
	serverURL := syncEnnoblementsCmdPayload.ServerURL()
	serverKey := syncEnnoblementsCmdPayload.ServerKey()

	latestEnnoblement, err := svc.getLatest(ctx, serverKey)
	if err != nil && !errors.Is(err, errEnnoblementNotFound) {
		return fmt.Errorf("%s: %w", serverKey, err)
	}

	var since time.Time
	if err == nil {
		since = latestEnnoblement.CreatedAt()
	}

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

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

	payload, err := domain.NewEnnoblementsSyncedEventPayload(
		serverKey,
		serverURL,
		syncEnnoblementsCmdPayload.VersionCode(),
	)
	if err != nil {
		return fmt.Errorf("%s: couldn't construct domain.EnnoblementsSyncedEventPayload %w", serverKey, err)
	}

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

	return nil
}

var errEnnoblementNotFound = errors.New("ennoblement not found")

func (svc *EnnoblementService) getLatest(ctx context.Context, serverKey string) (domain.Ennoblement, error) {
	params := domain.NewListEnnoblementsParams()
	if err := params.SetLimit(1); err != nil {
		return domain.Ennoblement{}, err
	}
	if err := params.SetServerKeys([]string{serverKey}); err != nil {
		return domain.Ennoblement{}, err
	}
	if err := params.SetSort([]domain.EnnoblementSort{domain.EnnoblementSortCreatedAtDESC}); err != nil {
		return domain.Ennoblement{}, err
	}

	ennoblements, err := svc.repo.List(ctx, params)
	if err != nil {
		return domain.Ennoblement{}, err
	}
	if len(ennoblements) == 0 {
		return domain.Ennoblement{}, errEnnoblementNotFound
	}

	return ennoblements[0], nil
}

const ennoblementCreateChunkSize = 500

func (svc *EnnoblementService) create(
	ctx context.Context,
	serverKey string,
	ennoblements domain.BaseEnnoblements,
) error {
	for i := 0; i < len(ennoblements); i += ennoblementCreateChunkSize {
		end := i + ennoblementCreateChunkSize
		if end > len(ennoblements) {
			end = len(ennoblements)
		}

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

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

	return nil
}
