package service

import (
	"baron-sso-backend/internal/domain"
	"context"
	"errors"
	"testing"
	"time"

	"github.com/stretchr/testify/require"
)

type fakeRPUsageOutboxRepo struct {
	created    []domain.RPUsageEvent
	ready      []domain.RPUsageEvent
	processing []string
	processed  []string
	failed     []string
	createErr  error
	projectErr error
}

func (f *fakeRPUsageOutboxRepo) Create(ctx context.Context, event *domain.RPUsageEvent) error {
	if f.createErr != nil {
		return f.createErr
	}
	f.created = append(f.created, *event)
	return nil
}

func (f *fakeRPUsageOutboxRepo) ListReady(ctx context.Context, limit int) ([]domain.RPUsageEvent, error) {
	return f.ready, nil
}

func (f *fakeRPUsageOutboxRepo) MarkProcessing(ctx context.Context, id string) error {
	f.processing = append(f.processing, id)
	return nil
}

func (f *fakeRPUsageOutboxRepo) MarkProcessed(ctx context.Context, id string) error {
	f.processed = append(f.processed, id)
	return nil
}

func (f *fakeRPUsageOutboxRepo) MarkFailed(ctx context.Context, id string, message string, nextAttemptAt time.Time) error {
	f.failed = append(f.failed, id)
	return nil
}

type fakeRPUsageProjectionRepo struct {
	created []domain.RPUsageEvent
	err     error
}

func (f *fakeRPUsageProjectionRepo) CreateRPUsageEvent(ctx context.Context, event domain.RPUsageEvent) error {
	if f.err != nil {
		return f.err
	}
	f.created = append(f.created, event)
	return nil
}

func TestRPUsageEventEmitterRequiresCanonicalFields(t *testing.T) {
	repo := &fakeRPUsageOutboxRepo{}
	emitter := NewRPUsageEventEmitter(repo)

	err := emitter.EmitRPUsageEvent(context.Background(), domain.RPUsageEvent{
		EventType: domain.RPUsageEventTypeAuthorizationGranted,
		ClientID:  "client-app",
	})

	require.Error(t, err)
	require.Empty(t, repo.created)
}

func TestRPUsageEventEmitterCreatesPendingOutboxEvent(t *testing.T) {
	repo := &fakeRPUsageOutboxRepo{}
	emitter := NewRPUsageEventEmitter(repo)

	err := emitter.EmitRPUsageEvent(context.Background(), domain.RPUsageEvent{
		EventType:     domain.RPUsageEventTypeAuthorizationGranted,
		Subject:       "user-123",
		ClientID:      "client-app",
		Source:        "hydra_consent",
		CorrelationID: "challenge-1",
	})

	require.NoError(t, err)
	require.Len(t, repo.created, 1)
	require.NotEmpty(t, repo.created[0].DedupeKey)
	require.Equal(t, domain.RPUsageEventTypeAuthorizationGranted, repo.created[0].EventType)
	require.Equal(t, "hydra_consent", repo.created[0].Source)
}

func TestRPUsageProjectorWorkerMarksProcessedAfterProjection(t *testing.T) {
	outbox := &fakeRPUsageOutboxRepo{
		ready: []domain.RPUsageEvent{{
			ID:        "event-1",
			EventType: domain.RPUsageEventTypeAuthorizationGranted,
			Subject:   "user-123",
			ClientID:  "client-app",
		}},
	}
	projection := &fakeRPUsageProjectionRepo{}
	worker := NewRPUsageProjectorWorker(outbox, projection)

	worker.processOnce(context.Background())

	require.Equal(t, []string{"event-1"}, outbox.processing)
	require.Equal(t, []string{"event-1"}, outbox.processed)
	require.Empty(t, outbox.failed)
	require.Len(t, projection.created, 1)
}

func TestRPUsageProjectorWorkerMarksFailedWhenProjectionFails(t *testing.T) {
	outbox := &fakeRPUsageOutboxRepo{
		ready: []domain.RPUsageEvent{{
			ID:        "event-1",
			EventType: domain.RPUsageEventTypeAuthorizationGranted,
			Subject:   "user-123",
			ClientID:  "client-app",
		}},
	}
	projection := &fakeRPUsageProjectionRepo{err: errors.New("clickhouse unavailable")}
	worker := NewRPUsageProjectorWorker(outbox, projection)

	worker.processOnce(context.Background())

	require.Equal(t, []string{"event-1"}, outbox.processing)
	require.Empty(t, outbox.processed)
	require.Equal(t, []string{"event-1"}, outbox.failed)
}
