package grpc

import (
	"context"
	"errors"
	"strings"
	"testing"
	"time"

	providersv1 "github.com/codex-k8s/kodex/proto/gen/go/kodex/providers/v1"
	"github.com/codex-k8s/kodex/services/internal/provider-hub/internal/domain/errs"
	providerservice "github.com/codex-k8s/kodex/services/internal/provider-hub/internal/domain/service"
	"github.com/codex-k8s/kodex/services/internal/provider-hub/internal/domain/types/entity"
	"github.com/codex-k8s/kodex/services/internal/provider-hub/internal/domain/types/enum"
	"github.com/codex-k8s/kodex/services/internal/provider-hub/internal/domain/types/query"
	"github.com/google/uuid"
	grpcruntime "google.golang.org/grpc"
	"google.golang.org/grpc/codes"
	"google.golang.org/grpc/status"
)

func TestRegisterProviderArtifactSignalMapsRequestAndResponse(t *testing.T) {
	t.Parallel()

	commandID := uuid.NewString()
	externalAccountID := uuid.NewString()
	signalID := "slot-agent-signal-1"
	number := int64(688)
	kind := providersv1.WorkItemKind_WORK_ITEM_KIND_PULL_REQUEST
	observedAt := time.Date(2026, 5, 11, 9, 0, 0, 0, time.UTC)
	response, err := NewServer(fakeService{}).RegisterProviderArtifactSignal(context.Background(), &providersv1.RegisterProviderArtifactSignalRequest{
		SignalId:          &signalID,
		ExternalAccountId: externalAccountID,
		Target: &providersv1.ProviderTarget{
			ProviderSlug:       "github",
			RepositoryFullName: ptrString("codex-k8s/kodex"),
			WorkItemKind:       &kind,
			Number:             &number,
		},
		Source:     "slot_agent_after",
		ObservedAt: observedAt.Format(time.RFC3339Nano),
		Meta:       &providersv1.CommandMeta{CommandId: &commandID, RequestId: "req-1"},
	})
	if err != nil {
		t.Fatalf("RegisterProviderArtifactSignal(): %v", err)
	}
	if response.GetSignalId() != signalID || response.GetStatus() != "accepted" {
		t.Fatalf("signal response = %+v, want accepted signal", response)
	}
	if response.GetTarget().GetProviderSlug() != "github" ||
		response.GetTarget().GetRepositoryFullName() != "codex-k8s/kodex" ||
		response.GetTarget().GetWorkItemKind() != providersv1.WorkItemKind_WORK_ITEM_KIND_PULL_REQUEST ||
		response.GetTarget().GetNumber() != number {
		t.Fatalf("target = %+v, want PR target", response.GetTarget())
	}
}

func TestCreateBootstrapPullRequestMapsRequestAndResponse(t *testing.T) {
	t.Parallel()

	commandID := uuid.NewString()
	externalAccountID := uuid.NewString()
	projectID := uuid.NewString()
	repositoryID := uuid.NewString()
	response, err := NewServer(fakeService{}).CreateBootstrapPullRequest(context.Background(), &providersv1.CreateBootstrapPullRequestRequest{
		ProjectId:        projectID,
		RepositoryId:     repositoryID,
		ProviderSlug:     "github",
		RepositoryTarget: &providersv1.ProviderTarget{ProviderSlug: "github", RepositoryFullName: ptrString("codex-k8s/kodex")},
		BaseBranch:       "main",
		BootstrapBranch:  "kodex/bootstrap",
		CommitMessage:    "Bootstrap repository",
		Title:            "Bootstrap платформы",
		Body:             "Подготовленные файлы bootstrap.",
		Files: []*providersv1.BootstrapFile{{
			Path:    "services.yaml",
			Content: "version: 1\n",
		}},
		Meta:              &providersv1.CommandMeta{CommandId: &commandID, RequestId: "req-1"},
		ExternalAccountId: externalAccountID,
	})
	if err != nil {
		t.Fatalf("CreateBootstrapPullRequest(): %v", err)
	}
	if response.GetResult().GetResultRef() != "bootstrap_pull_request:create" {
		t.Fatalf("result ref = %q, want bootstrap create", response.GetResult().GetResultRef())
	}
}

func TestCreateAdoptionPullRequestMapsRequestAndResponse(t *testing.T) {
	t.Parallel()

	commandID := uuid.NewString()
	externalAccountID := uuid.NewString()
	projectID := uuid.NewString()
	repositoryID := uuid.NewString()
	response, err := NewServer(fakeService{}).CreateAdoptionPullRequest(context.Background(), &providersv1.CreateAdoptionPullRequestRequest{
		ProjectId:        projectID,
		RepositoryId:     repositoryID,
		ProviderSlug:     "github",
		RepositoryTarget: &providersv1.ProviderTarget{ProviderSlug: "github", RepositoryFullName: ptrString("codex-k8s/kodex")},
		BaseBranch:       "main",
		AdoptionBranch:   "kodex/adoption",
		CommitMessage:    "Prepare repository adoption",
		Title:            "Подключение существующего репозитория",
		Body:             "Подготовленные изменения adoption.",
		Files: []*providersv1.AdoptionFile{{
			Path:    "services.yaml",
			Content: "version: 1\n",
		}},
		Meta:              &providersv1.CommandMeta{CommandId: &commandID, RequestId: "req-1"},
		ExternalAccountId: externalAccountID,
	})
	if err != nil {
		t.Fatalf("CreateAdoptionPullRequest(): %v", err)
	}
	if response.GetResult().GetResultRef() != "adoption_pull_request:create" {
		t.Fatalf("result ref = %q, want adoption create", response.GetResult().GetResultRef())
	}
}

func TestScanRepositoryForAdoptionMapsRequestAndResponse(t *testing.T) {
	t.Parallel()

	commandID := uuid.NewString()
	externalAccountID := uuid.NewString()
	response, err := NewServer(fakeService{}).ScanRepositoryForAdoption(context.Background(), &providersv1.ScanRepositoryForAdoptionRequest{
		ProviderSlug:     "github",
		RepositoryTarget: &providersv1.ProviderTarget{ProviderSlug: "github", RepositoryFullName: ptrString("codex-k8s/kodex")},
		Options: &providersv1.RepositoryAdoptionScanOptions{
			RequestedRef:       ptrString("main"),
			AllowedRefPrefixes: []string{"refs/heads/"},
			MaxTreeEntries:     500,
			MaxMarkerPaths:     10,
			MarkerPathHints:    []string{"docs/README.md"},
		},
		Meta:              &providersv1.CommandMeta{CommandId: &commandID, RequestId: "req-1"},
		ExternalAccountId: externalAccountID,
	})
	if err != nil {
		t.Fatalf("ScanRepositoryForAdoption(): %v", err)
	}
	snapshot := response.GetAdoptionScanSnapshot()
	if snapshot.GetRepositoryFullName() != "codex-k8s/kodex" ||
		snapshot.GetScannedRef() != "main" ||
		snapshot.GetStatus() != providersv1.RepositoryAdoptionScanStatus_REPOSITORY_ADOPTION_SCAN_STATUS_COMPLETED ||
		len(snapshot.GetMarkers()) != 1 {
		t.Fatalf("snapshot = %+v, want completed adoption scan", snapshot)
	}
}

func TestGetRepositoryMergeSignalMapsSafeReadResponse(t *testing.T) {
	t.Parallel()

	signalKey := "provider:github:repository_merge:bootstrap:github:codex-k8s/kodex:pull_request:88"
	response, err := NewServer(fakeService{}).GetRepositoryMergeSignal(context.Background(), &providersv1.GetRepositoryMergeSignalRequest{
		SignalKey: &signalKey,
		Meta:      &providersv1.QueryMeta{RequestId: "req-read-1"},
	})
	if err != nil {
		t.Fatalf("GetRepositoryMergeSignal(): %v", err)
	}
	signal := response.GetMergeSignal()
	if response.GetReadStatus() != providersv1.ProviderOwnedDataStatus_PROVIDER_OWNED_DATA_STATUS_READY ||
		signal.GetSignalKey() != signalKey ||
		signal.GetKind() != providersv1.RepositoryMergeSignalKind_REPOSITORY_MERGE_SIGNAL_KIND_BOOTSTRAP ||
		signal.GetMergeCommitSha() != "abc123" ||
		signal.GetWatermarkDigest() != "watermark-digest" ||
		signal.GetVersion() != 2 ||
		signal.GetEtag() == "" {
		t.Fatalf("response = %+v, want ready safe merge signal", response)
	}
}

func TestGetRepositoryMergeSignalMapsNotFoundStatus(t *testing.T) {
	t.Parallel()

	signalKey := "missing"
	response, err := NewServer(fakeService{}).GetRepositoryMergeSignal(context.Background(), &providersv1.GetRepositoryMergeSignalRequest{
		SignalKey: &signalKey,
		Meta:      &providersv1.QueryMeta{RequestId: "req-read-2"},
	})
	if err != nil {
		t.Fatalf("GetRepositoryMergeSignal(): %v", err)
	}
	if response.GetReadStatus() != providersv1.ProviderOwnedDataStatus_PROVIDER_OWNED_DATA_STATUS_NOT_FOUND || response.GetMergeSignal() != nil {
		t.Fatalf("response = %+v, want not found without merge signal", response)
	}
}

func TestGetRepositoryChangeSignalMapsSafeReadResponse(t *testing.T) {
	t.Parallel()

	signalKey := "provider:github:repository_change:push:codex-k8s/kodex:main:abc123"
	response, err := NewServer(fakeService{}).GetRepositoryChangeSignal(context.Background(), &providersv1.GetRepositoryChangeSignalRequest{
		SignalKey: &signalKey,
		Meta:      &providersv1.QueryMeta{RequestId: "req-read-change-1"},
	})
	if err != nil {
		t.Fatalf("GetRepositoryChangeSignal(): %v", err)
	}
	signal := response.GetChangeSignal()
	if response.GetReadStatus() != providersv1.ProviderOwnedDataStatus_PROVIDER_OWNED_DATA_STATUS_READY ||
		signal.GetSignalKey() != signalKey ||
		signal.GetKind() != providersv1.RepositoryChangeSignalKind_REPOSITORY_CHANGE_SIGNAL_KIND_PUSH ||
		signal.GetBaseBranch() != "main" ||
		signal.GetCommitSha() != "abc123" ||
		!signal.GetServicesPolicyChanged() ||
		!signal.GetDeployRelevantChanged() ||
		signal.GetPathSummaryStatus() != providersv1.RepositoryChangePathSummaryStatus_REPOSITORY_CHANGE_PATH_SUMMARY_STATUS_READY ||
		signal.GetEtag() == "" {
		t.Fatalf("response = %+v, want ready safe repository change signal", response)
	}
}

func TestGetRepositoryAdoptionScanSnapshotMapsSafeReadResponse(t *testing.T) {
	t.Parallel()

	snapshotKey := "provider:github:repository_adoption_scan:codex-k8s/kodex:main"
	response, err := NewServer(fakeService{}).GetRepositoryAdoptionScanSnapshot(context.Background(), &providersv1.GetRepositoryAdoptionScanSnapshotRequest{
		SnapshotKey: &snapshotKey,
		Meta:        &providersv1.QueryMeta{RequestId: "req-read-3"},
	})
	if err != nil {
		t.Fatalf("GetRepositoryAdoptionScanSnapshot(): %v", err)
	}
	snapshot := response.GetAdoptionScanSnapshot()
	if response.GetReadStatus() != providersv1.ProviderOwnedDataStatus_PROVIDER_OWNED_DATA_STATUS_READY ||
		snapshot.GetSnapshotDigest() != "safe-digest" ||
		snapshot.GetHeadSha() != "abc123" ||
		snapshot.GetVersion() != 2 ||
		snapshot.GetEtag() == "" ||
		len(snapshot.GetMarkers()) != 1 {
		t.Fatalf("response = %+v, want ready safe adoption scan snapshot", response)
	}
}

func TestCreateRepositoryMapsRequestAndResponse(t *testing.T) {
	t.Parallel()

	commandID := uuid.NewString()
	externalAccountID := uuid.NewString()
	projectID := uuid.NewString()
	repositoryID := uuid.NewString()
	owner := "codex-k8s"
	description := "Тестовый сервис"
	response, err := NewServer(fakeService{}).CreateRepository(context.Background(), &providersv1.CreateRepositoryRequest{
		ProjectId:         projectID,
		RepositoryId:      repositoryID,
		ProviderSlug:      "github",
		OwnerKind:         providersv1.RepositoryOwnerKind_REPOSITORY_OWNER_KIND_ORGANIZATION,
		ProviderOwner:     &owner,
		RepositoryName:    "new-service",
		Visibility:        providersv1.RepositoryVisibility_REPOSITORY_VISIBILITY_PRIVATE,
		Description:       &description,
		Meta:              &providersv1.CommandMeta{CommandId: &commandID, RequestId: "req-1"},
		ExternalAccountId: externalAccountID,
	})
	if err != nil {
		t.Fatalf("CreateRepository(): %v", err)
	}
	if response.GetResult().GetResultRef() != "repository:create" ||
		response.GetResult().GetBaseBranch() != "main" ||
		response.GetResult().GetTarget().GetRepositoryFullName() != "codex-k8s/new-service" {
		t.Fatalf("result = %+v, want repository create result", response.GetResult())
	}
}

func TestRecordProviderLimitSnapshotMapsRequestAndResponse(t *testing.T) {
	t.Parallel()

	accountID := uuid.New()
	commandID := uuid.NewString()
	capturedAt := time.Date(2026, 5, 7, 11, 0, 0, 0, time.UTC)
	remaining := int64(42)
	response, err := NewServer(fakeService{}).RecordProviderLimitSnapshot(context.Background(), &providersv1.RecordProviderLimitSnapshotRequest{
		ExternalAccountId: accountID.String(),
		ProviderSlug:      "github",
		LimitClass:        "core",
		Remaining:         &remaining,
		CapturedAt:        capturedAt.Format(time.RFC3339Nano),
		Source:            "provider_hub",
		Meta:              &providersv1.CommandMeta{CommandId: &commandID, RequestId: "req-1"},
	})
	if err != nil {
		t.Fatalf("RecordProviderLimitSnapshot(): %v", err)
	}
	if response.GetLimitSnapshot().GetExternalAccountId() != accountID.String() {
		t.Fatalf("external account = %s, want %s", response.GetLimitSnapshot().GetExternalAccountId(), accountID)
	}
	if response.GetLimitSnapshot().GetRemaining() != remaining {
		t.Fatalf("remaining = %d, want %d", response.GetLimitSnapshot().GetRemaining(), remaining)
	}
}

func TestIngestWebhookEventMapsRequestAndResponse(t *testing.T) {
	t.Parallel()

	commandID := uuid.NewString()
	receivedAt := time.Date(2026, 5, 7, 12, 0, 0, 0, time.UTC)
	response, err := NewServer(fakeService{}).IngestWebhookEvent(context.Background(), &providersv1.IngestWebhookEventRequest{
		ProviderSlug: "github",
		DeliveryId:   "delivery-1",
		EventName:    "issues",
		PayloadJson:  `{"issue":{"id":1}}`,
		ReceivedAt:   receivedAt.Format(time.RFC3339Nano),
		Meta:         &providersv1.CommandMeta{CommandId: &commandID, RequestId: "req-1"},
	})
	if err != nil {
		t.Fatalf("IngestWebhookEvent(): %v", err)
	}
	if response.GetWebhookEvent().GetDeliveryId() != "delivery-1" {
		t.Fatalf("delivery id = %s, want delivery-1", response.GetWebhookEvent().GetDeliveryId())
	}
	if response.GetWebhookEvent().GetProcessingStatus() != providersv1.WebhookProcessingStatus_WEBHOOK_PROCESSING_STATUS_PROCESSED {
		t.Fatalf("status = %s, want processed", response.GetWebhookEvent().GetProcessingStatus())
	}
	payload := response.GetWebhookEvent().GetPayloadJson()
	if strings.Contains(payload, "\"issue\"") {
		t.Fatalf("webhook response payload = %s, want safe envelope without raw provider body", payload)
	}
	if !strings.Contains(payload, "payload_storage") || response.GetWebhookEvent().GetPayloadSha256() == "" {
		t.Fatalf("webhook response = %+v, want safe payload envelope and digest", response.GetWebhookEvent())
	}
}

func TestCleanupExpiredWebhookPayloadsMapsRequestAndSafeResponse(t *testing.T) {
	t.Parallel()

	commandID := uuid.NewString()
	cleanedAt := time.Date(2026, 5, 28, 12, 0, 0, 0, time.UTC)
	limit := int32(25)
	response, err := NewServer(fakeService{}).CleanupExpiredWebhookPayloads(context.Background(), &providersv1.CleanupExpiredWebhookPayloadsRequest{
		Limit: &limit,
		Now:   ptrString(cleanedAt.Format(time.RFC3339Nano)),
		Meta:  &providersv1.CommandMeta{CommandId: &commandID, RequestId: "req-1"},
	})
	if err != nil {
		t.Fatalf("CleanupExpiredWebhookPayloads(): %v", err)
	}
	if response.GetCleanedCount() != 1 || response.GetCleanedAt() != cleanedAt.Format(time.RFC3339Nano) {
		t.Fatalf("cleanup response = %+v, want one cleaned event at %s", response, cleanedAt.Format(time.RFC3339Nano))
	}
	payload := response.GetWebhookEvents()[0].GetPayloadJson()
	if strings.Contains(payload, "do-not-leak") || !strings.Contains(payload, "payload_expired") {
		t.Fatalf("cleanup payload = %s, want safe expired envelope", payload)
	}
}

func TestEnqueueReconciliationMapsRequestAndResponse(t *testing.T) {
	t.Parallel()

	commandID := uuid.NewString()
	externalAccountID := uuid.NewString()
	response, err := NewServer(fakeService{}).EnqueueReconciliation(context.Background(), &providersv1.EnqueueReconciliationRequest{
		ProviderSlug:      "github",
		ExternalAccountId: externalAccountID,
		ScopeType:         providersv1.SyncCursorScopeType_SYNC_CURSOR_SCOPE_TYPE_REPOSITORY,
		ScopeRef:          "codex-k8s/kodex",
		ArtifactKinds:     []providersv1.SyncArtifactKind{providersv1.SyncArtifactKind_SYNC_ARTIFACT_KIND_ISSUE},
		Priority:          providersv1.SyncCursorPriority_SYNC_CURSOR_PRIORITY_HOT,
		Meta:              &providersv1.CommandMeta{CommandId: &commandID, RequestId: "req-1"},
	})
	if err != nil {
		t.Fatalf("EnqueueReconciliation(): %v", err)
	}
	if len(response.GetSyncCursors()) != 1 {
		t.Fatalf("sync cursors = %d, want 1", len(response.GetSyncCursors()))
	}
	cursor := response.GetSyncCursors()[0]
	if cursor.GetExternalAccountId() != externalAccountID ||
		cursor.GetScopeRef() != "codex-k8s/kodex" ||
		cursor.GetArtifactKind() != providersv1.SyncArtifactKind_SYNC_ARTIFACT_KIND_ISSUE {
		t.Fatalf("cursor = %+v, want issue cursor", cursor)
	}
}

func TestGetWorkItemProjectionMapsRequestAndResponse(t *testing.T) {
	t.Parallel()

	projectionID := uuid.New()
	response, err := NewServer(fakeService{}).GetWorkItemProjection(context.Background(), &providersv1.GetWorkItemProjectionRequest{
		WorkItemProjectionId: projectionID.String(),
		Meta:                 &providersv1.QueryMeta{Actor: &providersv1.Actor{Type: "user", Id: uuid.NewString()}},
	})
	if err != nil {
		t.Fatalf("GetWorkItemProjection(): %v", err)
	}
	projection := response.GetWorkItemProjection()
	if projection.GetWorkItemProjectionId() != projectionID.String() {
		t.Fatalf("projection id = %s, want %s", projection.GetWorkItemProjectionId(), projectionID)
	}
	if projection.GetKind() != providersv1.WorkItemKind_WORK_ITEM_KIND_ISSUE {
		t.Fatalf("kind = %s, want issue", projection.GetKind())
	}
	if len(projection.GetLabels()) != 1 || projection.GetLabels()[0] != "bug" {
		t.Fatalf("labels = %+v, want bug", projection.GetLabels())
	}
}

func TestNewServerPanicsWithoutService(t *testing.T) {
	t.Parallel()

	defer func() {
		if recover() == nil {
			t.Fatal("NewServer() did not panic")
		}
	}()
	_ = NewServer(nil)
}

func TestUnaryErrorInterceptorMapsDomainErrors(t *testing.T) {
	t.Parallel()

	cases := []struct {
		name string
		err  error
		code codes.Code
	}{
		{name: "invalid argument", err: errs.ErrInvalidArgument, code: codes.InvalidArgument},
		{name: "forbidden", err: errs.ErrForbidden, code: codes.PermissionDenied},
		{name: "not found", err: errs.ErrNotFound, code: codes.NotFound},
		{name: "already exists", err: errs.ErrAlreadyExists, code: codes.AlreadyExists},
		{name: "conflict", err: errs.ErrConflict, code: codes.Aborted},
		{name: "precondition", err: errs.ErrPreconditionFailed, code: codes.FailedPrecondition},
		{name: "dependency", err: errs.ErrDependencyUnavailable, code: codes.Unavailable},
		{name: "unknown", err: errors.New("boom"), code: codes.Internal},
	}
	for _, tc := range cases {
		t.Run(tc.name, func(t *testing.T) {
			t.Parallel()

			interceptor := UnaryErrorInterceptor(nil)
			info := &grpcruntime.UnaryServerInfo{FullMethod: "/kodex.providers.v1.ProviderHubService/Test"}
			_, err := interceptor(context.Background(), nil, info, func(context.Context, any) (any, error) {
				return nil, tc.err
			})
			if status.Code(err) != tc.code {
				t.Fatalf("status code = %s, want %s", status.Code(err), tc.code)
			}
		})
	}
}

type fakeService struct{}

func (fakeService) IngestWebhookEvent(_ context.Context, input providerservice.IngestWebhookEventInput) (entity.WebhookEvent, error) {
	return entity.WebhookEvent{
		ID:               uuid.New(),
		ProviderSlug:     input.ProviderSlug,
		DeliveryID:       input.DeliveryID,
		EventName:        input.EventName,
		ReceivedAt:       input.ReceivedAt,
		ProcessingStatus: enum.WebhookProcessingStatusProcessed,
		PayloadJSON:      input.PayloadJSON,
		PayloadDigest:    "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef",
		RetainUntil:      input.ReceivedAt.Add(24 * time.Hour),
	}, nil
}

func (fakeService) GetWebhookEvent(context.Context, providerservice.GetWebhookEventInput) (entity.WebhookEvent, error) {
	now := time.Date(2026, 5, 7, 12, 0, 0, 0, time.UTC)
	return entity.WebhookEvent{
		ID:               uuid.New(),
		ProviderSlug:     enum.ProviderSlugGitHub,
		DeliveryID:       "delivery-1",
		EventName:        "issues",
		ReceivedAt:       now,
		ProcessingStatus: enum.WebhookProcessingStatusProcessed,
		PayloadJSON:      []byte(`{}`),
		PayloadDigest:    "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef",
		RetainUntil:      now.Add(24 * time.Hour),
	}, nil
}

func (fakeService) GetWorkItemProjection(_ context.Context, input providerservice.GetWorkItemProjectionInput) (entity.ProviderWorkItemProjection, error) {
	now := time.Date(2026, 5, 7, 12, 0, 0, 0, time.UTC)
	return entity.ProviderWorkItemProjection{
		Base:               entity.Base{ID: input.WorkItemProjectionID, Version: 1, CreatedAt: now, UpdatedAt: now},
		ProviderSlug:       enum.ProviderSlugGitHub,
		ProviderWorkItemID: "55",
		RepositoryFullName: "codex-k8s/kodex",
		Kind:               enum.WorkItemKindIssue,
		Number:             7,
		URL:                "https://github.com/codex-k8s/kodex/issues/7",
		Title:              "Issue",
		State:              "open",
		LabelsJSON:         []byte(`["bug"]`),
		AssigneesJSON:      []byte(`["kodex-agent"]`),
		WatermarkStatus:    enum.WorkItemWatermarkStatusValid,
		ProviderUpdatedAt:  &now,
		SyncedAt:           now,
		DriftStatus:        enum.WorkItemDriftStatusFresh,
	}, nil
}

func (fakeService) FindWorkItemByProviderRef(context.Context, providerservice.FindWorkItemByProviderRefInput) (entity.ProviderWorkItemProjection, error) {
	return fakeService{}.GetWorkItemProjection(context.Background(), providerservice.GetWorkItemProjectionInput{WorkItemProjectionID: uuid.New()})
}

func (fakeService) ListWorkItemProjections(context.Context, providerservice.ListWorkItemProjectionsInput) (providerservice.ListWorkItemProjectionsResult, error) {
	item, err := fakeService{}.GetWorkItemProjection(context.Background(), providerservice.GetWorkItemProjectionInput{WorkItemProjectionID: uuid.New()})
	if err != nil {
		return providerservice.ListWorkItemProjectionsResult{}, err
	}
	return providerservice.ListWorkItemProjectionsResult{
		WorkItemProjections: []entity.ProviderWorkItemProjection{item},
		Page:                query.PageResult{},
	}, nil
}

func (fakeService) ListComments(context.Context, providerservice.ListCommentsInput) (providerservice.ListCommentsResult, error) {
	now := time.Date(2026, 5, 7, 12, 0, 0, 0, time.UTC)
	return providerservice.ListCommentsResult{
		Comments: []entity.ProviderCommentProjection{{
			Base:                entity.Base{ID: uuid.New(), Version: 1, CreatedAt: now, UpdatedAt: now},
			ProviderCommentID:   "900",
			Kind:                enum.CommentKindComment,
			AuthorProviderLogin: "kodex-agent",
			BodyDigest:          "digest",
			Summary:             "comment",
			ProviderCreatedAt:   &now,
			ProviderUpdatedAt:   &now,
		}},
		Page: query.PageResult{},
	}, nil
}

func (fakeService) ListRelationships(context.Context, providerservice.ListRelationshipsInput) (providerservice.ListRelationshipsResult, error) {
	return providerservice.ListRelationshipsResult{
		Relationships: []entity.ProviderRelationship{{
			ID:                uuid.New(),
			SourceWorkItemID:  uuid.New(),
			TargetProviderRef: "https://github.com/codex-k8s/kodex/pull/8",
			RelationshipType:  "source",
			Source:            enum.RelationshipSourceWatermark,
			Confidence:        enum.RelationshipConfidenceConfirmed,
			CreatedAt:         time.Date(2026, 5, 7, 12, 0, 0, 0, time.UTC),
		}},
		Page: query.PageResult{},
	}, nil
}

func (fakeService) GetRepositoryMergeSignal(_ context.Context, input providerservice.GetRepositoryMergeSignalInput) (providerservice.RepositoryMergeSignalResult, error) {
	if input.SignalKey == "missing" {
		return providerservice.RepositoryMergeSignalResult{Status: enum.ProviderOwnedDataStatusNotFound}, nil
	}
	signal := fakeRepositoryMergeSignal(input.SignalKey)
	return providerservice.RepositoryMergeSignalResult{Status: enum.ProviderOwnedDataStatusReady, MergeSignal: &signal}, nil
}

func (fakeService) ListRepositoryMergeSignals(context.Context, providerservice.ListRepositoryMergeSignalsInput) (providerservice.ListRepositoryMergeSignalsResult, error) {
	return providerservice.ListRepositoryMergeSignalsResult{
		MergeSignals: []entity.RepositoryMergeSignal{fakeRepositoryMergeSignal("provider:github:repository_merge:bootstrap:github:codex-k8s/kodex:pull_request:88")},
		Page:         query.PageResult{},
	}, nil
}

func (fakeService) GetRepositoryChangeSignal(_ context.Context, input providerservice.GetRepositoryChangeSignalInput) (providerservice.RepositoryChangeSignalResult, error) {
	if input.SignalKey == "missing" {
		return providerservice.RepositoryChangeSignalResult{Status: enum.ProviderOwnedDataStatusNotFound}, nil
	}
	signal := fakeRepositoryChangeSignal(input.SignalKey)
	return providerservice.RepositoryChangeSignalResult{Status: enum.ProviderOwnedDataStatusReady, ChangeSignal: &signal}, nil
}

func (fakeService) ListRepositoryChangeSignals(context.Context, providerservice.ListRepositoryChangeSignalsInput) (providerservice.ListRepositoryChangeSignalsResult, error) {
	return providerservice.ListRepositoryChangeSignalsResult{
		ChangeSignals: []entity.RepositoryChangeSignal{fakeRepositoryChangeSignal("provider:github:repository_change:push:codex-k8s/kodex:main:abc123")},
		Page:          query.PageResult{},
	}, nil
}

func (fakeService) GetRepositoryAdoptionScanSnapshot(_ context.Context, input providerservice.GetRepositoryAdoptionScanSnapshotInput) (providerservice.RepositoryAdoptionScanSnapshotResult, error) {
	if input.SnapshotKey == "missing" {
		return providerservice.RepositoryAdoptionScanSnapshotResult{Status: enum.ProviderOwnedDataStatusNotFound}, nil
	}
	snapshot := fakeRepositoryAdoptionScanSnapshot(input.SnapshotKey)
	return providerservice.RepositoryAdoptionScanSnapshotResult{Status: enum.ProviderOwnedDataStatusReady, Snapshot: &snapshot}, nil
}

func (fakeService) ListRepositoryAdoptionScanSnapshots(context.Context, providerservice.ListRepositoryAdoptionScanSnapshotsInput) (providerservice.ListRepositoryAdoptionScanSnapshotsResult, error) {
	return providerservice.ListRepositoryAdoptionScanSnapshotsResult{
		Snapshots: []entity.RepositoryAdoptionScanSnapshot{fakeRepositoryAdoptionScanSnapshot("provider:github:repository_adoption_scan:codex-k8s/kodex:main")},
		Page:      query.PageResult{},
	}, nil
}

func fakeRepositoryMergeSignal(signalKey string) entity.RepositoryMergeSignal {
	now := time.Date(2026, 5, 27, 10, 0, 0, 0, time.UTC)
	projectID := uuid.New()
	repositoryID := uuid.New()
	return entity.RepositoryMergeSignal{
		Base: entity.Base{
			ID:        uuid.New(),
			Version:   2,
			CreatedAt: now.Add(-time.Minute),
			UpdatedAt: now,
		},
		SignalKey:                   signalKey,
		Kind:                        enum.RepositoryMergeSignalKindBootstrap,
		ProviderSlug:                enum.ProviderSlugGitHub,
		ProjectID:                   &projectID,
		RepositoryID:                &repositoryID,
		RepositoryFullName:          "codex-k8s/kodex",
		ProviderRepositoryID:        "101",
		WorkItemProjectionID:        uuid.New(),
		ProviderWorkItemID:          "github:codex-k8s/kodex:pull_request:88",
		PullRequestNumber:           88,
		PullRequestProviderID:       "8801",
		PullRequestURL:              "https://github.com/codex-k8s/kodex/pull/88",
		BaseBranch:                  "main",
		HeadBranch:                  "kodex/bootstrap",
		MergeCommitSHA:              "abc123",
		SourceRef:                   "kodex/bootstrap",
		RelatedProviderOperationRef: "provider-hub:create_bootstrap_pull_request:github:codex-k8s/kodex:pull_request:88",
		WatermarkDigest:             "watermark-digest",
		ObservedAt:                  now,
		MergedAt:                    now.Add(-time.Minute),
		Status:                      enum.RepositoryMergeSignalStatusMerged,
	}
}

func fakeRepositoryChangeSignal(signalKey string) entity.RepositoryChangeSignal {
	now := time.Date(2026, 5, 29, 10, 0, 0, 0, time.UTC)
	return entity.RepositoryChangeSignal{
		Base:                 entity.Base{ID: uuid.New(), Version: 2, CreatedAt: now.Add(-time.Minute), UpdatedAt: now},
		SignalKey:            signalKey,
		Kind:                 enum.RepositoryChangeSignalKindPush,
		ProviderSlug:         enum.ProviderSlugGitHub,
		RepositoryFullName:   "codex-k8s/kodex",
		ProviderRepositoryID: "101",
		Ref:                  "refs/heads/main",
		BaseBranch:           "main",
		CommitSHA:            "abc123",
		BeforeSHA:            "def456",
		PathSummaryStatus:    enum.RepositoryChangePathSummaryStatusReady,
		ChangedPathCount:     2,
		PathDigest:           "sha256:path-digest",
		PathCategories: []entity.RepositoryChangePathCategoryCount{
			{Category: enum.RepositoryChangePathCategoryServicesPolicy, Count: 1},
			{Category: enum.RepositoryChangePathCategoryDeployManifest, Count: 1},
		},
		ServicesPolicyChanged: true,
		DeployRelevantChanged: true,
		ChangeFingerprint:     "sha256:fingerprint",
		ObservedAt:            now,
		Status:                enum.RepositoryChangeSignalStatusObserved,
	}
}

func fakeRepositoryAdoptionScanSnapshot(snapshotKey string) entity.RepositoryAdoptionScanSnapshot {
	now := time.Date(2026, 5, 27, 10, 0, 0, 0, time.UTC)
	return entity.RepositoryAdoptionScanSnapshot{
		Base:                entity.Base{ID: uuid.New(), Version: 2, CreatedAt: now.Add(-time.Minute), UpdatedAt: now},
		SnapshotKey:         snapshotKey,
		ProviderOperationID: uuid.New(),
		ExternalAccountID:   uuid.New(),
		ProviderSlug:        enum.ProviderSlugGitHub,
		RepositoryFullName:  "codex-k8s/kodex",
		DefaultBranch:       "main",
		RequestedRef:        "main",
		ScannedRef:          "main",
		HeadSHA:             "abc123",
		Status:              enum.RepositoryAdoptionScanStatusCompleted,
		Markers: []entity.RepositoryAdoptionScanMarker{{
			Path:         "services.yaml",
			Kind:         enum.RepositoryAdoptionMarkerServiceDescriptor,
			ObjectDigest: "blob-sha",
			SizeBytes:    42,
		}},
		FileCount:        8,
		VisibleFileCount: 8,
		SnapshotDigest:   "safe-digest",
		ObservedAt:       now,
	}
}

func (fakeService) RegisterProviderArtifactSignal(_ context.Context, input providerservice.RegisterProviderArtifactSignalInput) (providerservice.ProviderArtifactSignalResult, error) {
	return providerservice.ProviderArtifactSignalResult{
		SignalID: input.SignalID,
		Status:   "accepted",
		Target:   input.Target,
	}, nil
}

func (fakeService) EnqueueReconciliation(_ context.Context, input providerservice.EnqueueReconciliationInput) (providerservice.EnqueueReconciliationResult, error) {
	now := time.Date(2026, 5, 7, 12, 0, 0, 0, time.UTC)
	cursors := make([]entity.SyncCursor, 0, len(input.ArtifactKinds))
	for _, artifactKind := range input.ArtifactKinds {
		cursors = append(cursors, entity.SyncCursor{
			Base:                entity.Base{ID: uuid.New(), Version: 1, CreatedAt: now, UpdatedAt: now},
			ProviderSlug:        input.ProviderSlug,
			ExternalAccountID:   input.ExternalAccountID,
			ScopeType:           input.ScopeType,
			ScopeRef:            input.ScopeRef,
			ArtifactKind:        artifactKind,
			Priority:            input.Priority,
			RateBudgetStateJSON: []byte(`{}`),
		})
	}
	return providerservice.EnqueueReconciliationResult{SyncCursors: cursors}, nil
}

func (fakeService) RunReconciliationBatch(_ context.Context, input providerservice.RunReconciliationBatchInput) (providerservice.RunReconciliationBatchResult, error) {
	now := time.Date(2026, 5, 7, 12, 0, 0, 0, time.UTC)
	cursorID := uuid.New()
	if input.SyncCursorID != nil {
		cursorID = *input.SyncCursorID
	}
	externalAccountID := uuid.New()
	if input.ExternalAccountID != nil {
		externalAccountID = *input.ExternalAccountID
	}
	leaseUntil := now.Add(30 * time.Second)
	return providerservice.RunReconciliationBatchResult{
		SyncCursor: entity.SyncCursor{
			Base:                entity.Base{ID: cursorID, Version: 2, CreatedAt: now, UpdatedAt: now},
			ProviderSlug:        enum.ProviderSlugGitHub,
			ExternalAccountID:   externalAccountID,
			ScopeType:           enum.SyncCursorScopeRepository,
			ScopeRef:            "codex-k8s/kodex",
			ArtifactKind:        enum.SyncArtifactIssue,
			Priority:            enum.SyncCursorPriorityHot,
			LastCheckedAt:       &now,
			RateBudgetStateJSON: []byte(`{}`),
			LeaseOwner:          input.LeaseOwner,
			LeaseUntil:          &leaseUntil,
		},
	}, nil
}

func (fakeService) GetSyncCursor(_ context.Context, input providerservice.GetSyncCursorInput) (entity.SyncCursor, error) {
	now := time.Date(2026, 5, 7, 12, 0, 0, 0, time.UTC)
	return entity.SyncCursor{
		Base:                entity.Base{ID: input.SyncCursorID, Version: 1, CreatedAt: now, UpdatedAt: now},
		ProviderSlug:        enum.ProviderSlugGitHub,
		ExternalAccountID:   uuid.New(),
		ScopeType:           enum.SyncCursorScopeRepository,
		ScopeRef:            "codex-k8s/kodex",
		ArtifactKind:        enum.SyncArtifactIssue,
		Priority:            enum.SyncCursorPriorityHot,
		RateBudgetStateJSON: []byte(`{}`),
	}, nil
}

func (fakeService) ListSyncCursors(context.Context, providerservice.ListSyncCursorsInput) (providerservice.ListSyncCursorsResult, error) {
	cursor, err := fakeService{}.GetSyncCursor(context.Background(), providerservice.GetSyncCursorInput{SyncCursorID: uuid.New()})
	if err != nil {
		return providerservice.ListSyncCursorsResult{}, err
	}
	return providerservice.ListSyncCursorsResult{SyncCursors: []entity.SyncCursor{cursor}, Page: query.PageResult{}}, nil
}

func (fakeService) ListWebhookEvents(context.Context, providerservice.ListWebhookEventsInput) (providerservice.ListWebhookEventsResult, error) {
	return providerservice.ListWebhookEventsResult{Page: query.PageResult{}}, nil
}

func (fakeService) RetryWebhookEventProcessing(ctx context.Context, input providerservice.RetryWebhookEventProcessingInput) (entity.WebhookEvent, error) {
	return fakeService{}.GetWebhookEvent(ctx, providerservice.GetWebhookEventInput{WebhookEventID: input.WebhookEventID})
}

func (fakeService) CleanupExpiredWebhookPayloads(_ context.Context, input providerservice.CleanupExpiredWebhookPayloadsInput) (providerservice.CleanupExpiredWebhookPayloadsResult, error) {
	cleanedAt := input.Now
	if cleanedAt.IsZero() {
		cleanedAt = time.Date(2026, 5, 28, 12, 0, 0, 0, time.UTC)
	}
	webhook := entity.WebhookEvent{
		ID:               uuid.New(),
		ProviderSlug:     enum.ProviderSlugGitHub,
		DeliveryID:       "delivery-expired",
		EventName:        "issues",
		ReceivedAt:       cleanedAt.Add(-48 * time.Hour),
		ProcessingStatus: enum.WebhookProcessingStatusFailed,
		PayloadJSON:      []byte(`{"payload_storage":"expired_after_retention","payload_cleanup_reason":"payload_expired","payload_expired_at":"` + cleanedAt.Format(time.RFC3339Nano) + `","secret":"do-not-leak"}`),
		PayloadDigest:    "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef",
		LastError:        "payload_expired",
		RetainUntil:      cleanedAt.Add(-24 * time.Hour),
	}
	return providerservice.CleanupExpiredWebhookPayloadsResult{
		CleanedAt:     cleanedAt,
		CleanedCount:  1,
		WebhookEvents: []entity.WebhookEvent{webhook},
	}, nil
}

func (fakeService) GetProviderAccountRuntimeState(context.Context, providerservice.GetProviderAccountRuntimeStateInput) (entity.ProviderAccountRuntimeState, error) {
	return entity.ProviderAccountRuntimeState{
		Base:              entity.Base{ID: uuid.New(), Version: 1},
		ExternalAccountID: uuid.New(),
		ProviderSlug:      enum.ProviderSlugGitHub,
		Status:            enum.ProviderAccountRuntimeStatusActive,
	}, nil
}

func (fakeService) ListProviderAccountRuntimeStates(context.Context, providerservice.ListProviderAccountRuntimeStatesInput) (providerservice.ListProviderAccountRuntimeStatesResult, error) {
	return providerservice.ListProviderAccountRuntimeStatesResult{
		RuntimeStates: []entity.ProviderAccountRuntimeState{{
			Base:              entity.Base{ID: uuid.New(), Version: 1},
			ExternalAccountID: uuid.New(),
			ProviderSlug:      enum.ProviderSlugGitHub,
			Status:            enum.ProviderAccountRuntimeStatusActive,
		}},
		Page: query.PageResult{},
	}, nil
}

func (fakeService) RecordProviderLimitSnapshot(_ context.Context, input providerservice.RecordProviderLimitSnapshotInput) (entity.ProviderLimitSnapshot, error) {
	return entity.ProviderLimitSnapshot{
		ID:                uuid.New(),
		ExternalAccountID: input.ExternalAccountID,
		ProviderSlug:      input.ProviderSlug,
		LimitClass:        input.LimitClass,
		Remaining:         input.Remaining,
		LimitValue:        input.LimitValue,
		ResetAt:           input.ResetAt,
		CapturedAt:        input.CapturedAt,
		Source:            input.Source,
	}, nil
}

func (fakeService) ListProviderLimitSnapshots(context.Context, providerservice.ListProviderLimitSnapshotsInput) (providerservice.ListProviderLimitSnapshotsResult, error) {
	return providerservice.ListProviderLimitSnapshotsResult{Page: query.PageResult{}}, nil
}

func (fakeService) ListProviderOperations(context.Context, providerservice.ListProviderOperationsInput) (providerservice.ListProviderOperationsResult, error) {
	return providerservice.ListProviderOperationsResult{Page: query.PageResult{}}, nil
}

func (fakeService) CreateIssue(_ context.Context, input providerservice.CreateIssueInput) (providerservice.ProviderOperationResult, error) {
	return providerservice.ProviderOperationResult{
		Result: providerservice.ProviderOperationCommandResult{
			ResultRef: "issue:create",
			Target: &providerservice.ProviderTarget{
				ProviderSlug: enum.ProviderSlugGitHub,
			},
		},
	}, nil
}

func (fakeService) UpdateIssue(_ context.Context, input providerservice.UpdateIssueInput) (providerservice.ProviderOperationResult, error) {
	return providerservice.ProviderOperationResult{
		Result: providerservice.ProviderOperationCommandResult{
			ResultRef: "issue:update",
			Target:    &input.Target,
		},
	}, nil
}

func (fakeService) CreateComment(_ context.Context, input providerservice.CreateCommentInput) (providerservice.ProviderOperationResult, error) {
	return providerservice.ProviderOperationResult{
		Result: providerservice.ProviderOperationCommandResult{
			ResultRef: "comment:create",
			Target:    &input.Target,
		},
	}, nil
}

func (fakeService) UpdateComment(_ context.Context, input providerservice.UpdateCommentInput) (providerservice.ProviderOperationResult, error) {
	return providerservice.ProviderOperationResult{
		Result: providerservice.ProviderOperationCommandResult{
			ResultRef: "comment:update",
			Target:    &input.Target,
		},
	}, nil
}

func (fakeService) CreatePullRequest(_ context.Context, input providerservice.CreatePullRequestInput) (providerservice.ProviderOperationResult, error) {
	return providerservice.ProviderOperationResult{
		Result: providerservice.ProviderOperationCommandResult{
			ResultRef: "pull_request:create",
			Target: &providerservice.ProviderTarget{
				ProviderSlug: input.ProviderSlug,
			},
		},
	}, nil
}

func (fakeService) CreateRepository(_ context.Context, input providerservice.CreateRepositoryInput) (providerservice.ProviderOperationResult, error) {
	owner := ""
	if input.ProviderOwner != nil {
		owner = *input.ProviderOwner
	}
	return providerservice.ProviderOperationResult{
		Result: providerservice.ProviderOperationCommandResult{
			ResultRef:  "repository:create",
			BaseBranch: "main",
			Target: &providerservice.ProviderTarget{
				ProviderSlug:       input.ProviderSlug,
				RepositoryFullName: owner + "/" + input.RepositoryName,
			},
		},
	}, nil
}

func (fakeService) CreateBootstrapPullRequest(_ context.Context, input providerservice.CreateBootstrapPullRequestInput) (providerservice.ProviderOperationResult, error) {
	return providerservice.ProviderOperationResult{
		Result: providerservice.ProviderOperationCommandResult{
			ResultRef: "bootstrap_pull_request:create",
			Target: &providerservice.ProviderTarget{
				ProviderSlug: input.ProviderSlug,
			},
		},
	}, nil
}

func (fakeService) CreateAdoptionPullRequest(_ context.Context, input providerservice.CreateAdoptionPullRequestInput) (providerservice.ProviderOperationResult, error) {
	return providerservice.ProviderOperationResult{
		Result: providerservice.ProviderOperationCommandResult{
			ResultRef: "adoption_pull_request:create",
			Target: &providerservice.ProviderTarget{
				ProviderSlug: input.ProviderSlug,
			},
		},
	}, nil
}

func (fakeService) ScanRepositoryForAdoption(_ context.Context, input providerservice.ScanRepositoryForAdoptionInput) (providerservice.ProviderOperationResult, error) {
	snapshotID := uuid.New()
	operationID := uuid.New()
	observedAt := time.Date(2026, 5, 27, 10, 0, 0, 0, time.UTC)
	return providerservice.ProviderOperationResult{
		AdoptionScan: &entity.RepositoryAdoptionScanSnapshot{
			Base:                entity.Base{ID: snapshotID, Version: 1, CreatedAt: observedAt, UpdatedAt: observedAt},
			ProviderOperationID: operationID,
			ExternalAccountID:   input.ExternalAccountID,
			ProviderSlug:        input.ProviderSlug,
			RepositoryFullName:  input.RepositoryTarget.RepositoryFullName,
			DefaultBranch:       "main",
			RequestedRef:        input.Options.RequestedRef,
			ScannedRef:          "main",
			HeadSHA:             "abc123",
			Status:              enum.RepositoryAdoptionScanStatusCompleted,
			Markers: []entity.RepositoryAdoptionScanMarker{{
				Path:         "services.yaml",
				Kind:         enum.RepositoryAdoptionMarkerServiceDescriptor,
				ObjectDigest: "blob-sha",
				SizeBytes:    42,
			}},
			FileCount:        8,
			VisibleFileCount: 8,
			SnapshotDigest:   "safe-digest",
			ObservedAt:       observedAt,
		},
		Result: providerservice.ProviderOperationCommandResult{
			ResultRef: "adoption_scan:completed",
			Target:    &input.RepositoryTarget,
		},
	}, nil
}

func (fakeService) UpdatePullRequest(_ context.Context, input providerservice.UpdatePullRequestInput) (providerservice.ProviderOperationResult, error) {
	return providerservice.ProviderOperationResult{
		Result: providerservice.ProviderOperationCommandResult{
			ResultRef: "pull_request:update",
			Target:    &input.Target,
		},
	}, nil
}

func (fakeService) CreateReviewSignal(_ context.Context, input providerservice.CreateReviewSignalInput) (providerservice.ProviderOperationResult, error) {
	return providerservice.ProviderOperationResult{
		Result: providerservice.ProviderOperationCommandResult{
			ResultRef: "review_signal:create",
			Target:    &input.Target,
		},
	}, nil
}

func (fakeService) UpdateRelationship(_ context.Context, input providerservice.UpdateRelationshipInput) (providerservice.ProviderOperationResult, error) {
	return providerservice.ProviderOperationResult{
		Result: providerservice.ProviderOperationCommandResult{
			ResultRef: "relationship:update",
			Target:    input.Target,
		},
	}, nil
}

func ptrString(value string) *string {
	return &value
}
