package service

import (
	"context"
	"time"

	"github.com/pingcap/agent-git-service/internal/db"
	"github.com/pingcap/agent-git-service/internal/service/internal/repositorymutation"
)

// RepositoryPushOutboxEvent is a bounded pure value. Its opaque claim remains
// private to the Service wrapper and cannot be forged by handlers.
type RepositoryPushOutboxEvent struct {
	RepositoryID uint
	FullName     string
	OperationID  string
	EventKey     string
	OccurredAt   time.Time
	Actor        db.User
	RefUpdates   []RepositoryReceivePackRefUpdate
}

type RepositoryPushOutboxClaim struct {
	inner *repositorymutation.PushOutboxClaim
	event RepositoryPushOutboxEvent
}

func (claim *RepositoryPushOutboxClaim) Event() RepositoryPushOutboxEvent {
	if claim == nil {
		return RepositoryPushOutboxEvent{}
	}
	event := claim.event
	event.RefUpdates = append([]RepositoryReceivePackRefUpdate(nil), event.RefUpdates...)
	for index := range event.RefUpdates {
		event.RefUpdates[index].CommitSHAs = append([]string(nil), event.RefUpdates[index].CommitSHAs...)
	}
	return event
}

func (s *Service) ClaimRepositoryPushOutbox(ctx context.Context, repositoryID uint, limit int) ([]*RepositoryPushOutboxClaim, error) {
	if s == nil || s.repositoryMutations == nil {
		return nil, ErrRepositoryMigrationConflict
	}
	claims, err := s.repositoryMutations.ClaimPushOutbox(ctx, repositoryID, limit)
	result := make([]*RepositoryPushOutboxClaim, len(claims))
	for index, claim := range claims {
		event := claim.Event()
		actor := db.User{ID: event.Actor.ID, Login: event.Actor.Login, Name: event.Actor.Name, Email: event.Actor.Email, Bio: event.Actor.Bio,
			Type: event.Actor.Type, UserKind: event.Actor.UserKind, SiteAdmin: event.Actor.SiteAdmin,
			DefaultRepositoryPermission: event.Actor.DefaultRepositoryPermission, CreatedAt: event.Actor.CreatedAt, UpdatedAt: event.Actor.UpdatedAt}
		result[index] = &RepositoryPushOutboxClaim{inner: claim, event: RepositoryPushOutboxEvent{
			RepositoryID: event.RepositoryID, FullName: event.FullName, OperationID: event.OperationID,
			EventKey: event.EventKey, OccurredAt: event.OccurredAt, Actor: actor,
			RefUpdates: repositoryReceivePackRefUpdates(event.RefUpdates),
		}}
	}
	return result, repositoryMutationError(err)
}

func (s *Service) RecoverRepositoryPushMarkers(ctx context.Context, limit int) (int, int, error) {
	if s == nil || s.repositoryMutations == nil {
		return 0, 0, ErrRepositoryMigrationConflict
	}
	return s.repositoryMutations.RecoverPushMarkers(ctx, limit)
}

// HasPendingRepositoryPushWork is the bounded delayed-wake probe for a Git
// HTTP embedder that does not provide a lifecycle ticker.
func (s *Service) HasPendingRepositoryPushWork(ctx context.Context) (bool, error) {
	if s == nil || s.repositoryMutations == nil {
		return false, ErrRepositoryMigrationConflict
	}
	return s.repositoryMutations.HasPendingPushWork(ctx)
}

func (s *Service) CompleteRepositoryPushOutbox(ctx context.Context, claim *RepositoryPushOutboxClaim) error {
	if s == nil || s.repositoryMutations == nil || claim == nil || claim.inner == nil {
		return ErrRepositoryMigrationConflict
	}
	return repositoryMutationError(s.repositoryMutations.CompletePushOutbox(ctx, claim.inner))
}

func (s *Service) RetryRepositoryPushOutbox(ctx context.Context, claim *RepositoryPushOutboxClaim, cause error) error {
	if s == nil || s.repositoryMutations == nil || claim == nil || claim.inner == nil {
		return ErrRepositoryMigrationConflict
	}
	return repositoryMutationError(s.repositoryMutations.RetryPushOutbox(ctx, claim.inner, cause))
}
