package kb

import (
	"bytes"
	"context"
	"errors"
	"fmt"
	"strings"

	"github.com/tidbcloud/AgentTeam/internal/kbgovernance"
	storekb "github.com/tidbcloud/AgentTeam/internal/store/kb"
	"gorm.io/gorm"
)

type KBPublicationReview struct {
	KnowledgeBaseID string
	Name            string
	Description     string
	GroupID         string
	TagIDs          []string
	Sources         []KBPublicationReviewSource
	Request         storekb.KnowledgeBasePublicationRequest
	Approvers       []kbgovernance.PublicationApprover
	CanDecide       bool
}

type KBPublicationReviewSource struct {
	ID                   string
	SourceType           string
	Name                 string
	Description          string
	DefaultRef           string
	Status               string
	SyncStatus           string
	IndexStatus          string
	RevisionID           string
	SourceCommitSHA      string
	WikiRevision         string
	DashboardRevision    string
	WikiDisposition      string
	DashboardDisposition string
}

func (s *KBService) ConfigurePublicationApprovalGateway(gateway kbgovernance.PublicationApprovalGateway) {
	if s == nil {
		return
	}
	s.publicationApprovalMu.Lock()
	s.publicationApprovalGateway = gateway
	s.publicationApprovalMu.Unlock()
}

func (s *KBService) publicationGateway() kbgovernance.PublicationApprovalGateway {
	if s == nil {
		return nil
	}
	s.publicationApprovalMu.RLock()
	defer s.publicationApprovalMu.RUnlock()
	return s.publicationApprovalGateway
}

func (s *KBService) RequestPublicationApproval(ctx context.Context, workspaceID, subject, kbID, workingRevision, idempotencyKey string) (KBSetupView, error) {
	gateway := s.publicationGateway()
	if gateway == nil {
		return KBSetupView{}, codedConflict("kb-publication-approval-unavailable", "Knowledge base publication approvals are not configured", nil)
	}
	prepared, err := s.PreparePublication(ctx, kbgovernance.PreparePublicationCommand{
		WorkspaceID: workspaceID, KnowledgeBaseID: kbID, WorkingRevision: workingRevision,
		RequesterID: subject, IdempotencyKey: idempotencyKey, CorrelationID: idempotencyKey,
	})
	if err != nil {
		return KBSetupView{}, err
	}
	if _, err := gateway.SubmitPublication(ctx, kbgovernance.SubmitPublicationCommand{ActorPrincipalID: subject, Prepared: prepared}); err != nil {
		return KBSetupView{}, mapWikiApprovalGatewayError(err)
	}
	return s.GetKnowledgeBaseSetup(ctx, workspaceID, subject, kbID)
}

func (s *KBService) GetPublicationReview(ctx context.Context, workspaceID, subject, approvalID string) (KBPublicationReview, error) {
	if err := s.validateScope(workspaceID, subject); err != nil {
		return KBPublicationReview{}, err
	}
	request, err := s.store.GetKnowledgeBasePublicationRequestByApproval(ctx, workspaceID, strings.TrimSpace(approvalID))
	if err != nil {
		return KBPublicationReview{}, mapStoreNotFound(err)
	}
	gateway := s.publicationGateway()
	if gateway == nil {
		return KBPublicationReview{}, codedConflict("kb-publication-approval-unavailable", "Knowledge base publication approvals are not configured", nil)
	}
	approvalReview, err := gateway.InspectPublication(ctx, kbgovernance.InspectPublicationApprovalCommand{
		WorkspaceID: request.WorkspaceID, ApprovalID: request.ApprovalID, ActorPrincipalID: subject,
	})
	if err != nil {
		return KBPublicationReview{}, mapWikiApprovalGatewayError(err)
	}
	if request.RequesterID != strings.TrimSpace(subject) && !approvalReview.ActorAssigned {
		return KBPublicationReview{}, forbidden("Publication review is available only to the applicant or an assigned approver")
	}
	snapshot, err := kbgovernance.DecodePublicationSnapshot([]byte(request.SnapshotJSON))
	if err != nil {
		return KBPublicationReview{}, fmt.Errorf("decode publication review snapshot: %w", err)
	}
	if snapshot.KnowledgeBaseID != request.KnowledgeBaseID || snapshot.WorkingRevision != request.WorkingRevision {
		return KBPublicationReview{}, codedConflict("kb-publication-review-invalid", "The frozen publication review no longer matches its request", nil)
	}
	return publicationReview(request, snapshot, approvalReview), nil
}

func publicationReview(request storekb.KnowledgeBasePublicationRequest, snapshot kbgovernance.PublicationSnapshot, approvalReview kbgovernance.PublicationApprovalReview) KBPublicationReview {
	sources := make([]KBPublicationReviewSource, 0, len(snapshot.Sources))
	for _, source := range snapshot.Sources {
		sources = append(sources, KBPublicationReviewSource{
			ID: source.ID, SourceType: source.SourceType, Name: source.Name,
			Description: source.Description, DefaultRef: source.DefaultRef,
			Status: source.Status, SyncStatus: source.SyncStatus, IndexStatus: source.IndexStatus,
			RevisionID: source.RevisionID, SourceCommitSHA: source.SourceCommitSHA,
			WikiRevision: source.WikiRevision, DashboardRevision: source.DashboardRevision,
			WikiDisposition: source.WikiDisposition, DashboardDisposition: source.DashboardDisposition,
		})
	}
	return KBPublicationReview{
		KnowledgeBaseID: snapshot.KnowledgeBaseID, Name: snapshot.Name, Description: snapshot.Description,
		GroupID: snapshot.GroupID, TagIDs: append([]string(nil), snapshot.TagIDs...), Sources: sources,
		Request: request, Approvers: approvalReview.Approvers, CanDecide: approvalReview.CanDecide,
	}
}

func (s *KBService) DecidePublicationApproval(ctx context.Context, workspaceID, subject, kbID, approvalID string, expectedVersion int64, clientDecisionID, decision, reason string) (KBSetupView, error) {
	if _, err := s.requireSetupPublicationApproval(ctx, workspaceID, subject, kbID, approvalID); err != nil {
		return KBSetupView{}, err
	}
	allowed, err := s.canAdministerWorkspace(ctx, workspaceID, subject)
	if err != nil {
		return KBSetupView{}, err
	}
	if !allowed {
		return KBSetupView{}, forbidden("Publication approval decision requires workspace owner or admin access")
	}
	gateway := s.publicationGateway()
	if gateway == nil {
		return KBSetupView{}, codedConflict("kb-publication-approval-unavailable", "Knowledge base publication approvals are not configured", nil)
	}
	if _, err := gateway.DecidePublication(ctx, kbgovernance.DecidePublicationCommand{
		WorkspaceID: workspaceID, ApprovalID: approvalID, ActorPrincipalID: subject,
		ExpectedVersion: expectedVersion, ClientDecisionID: clientDecisionID,
		Decision: decision, Reason: reason, CorrelationID: kbID,
	}); err != nil {
		return KBSetupView{}, mapWikiApprovalGatewayError(err)
	}
	return s.GetKnowledgeBaseSetup(ctx, workspaceID, subject, kbID)
}

func (s *KBService) DecidePublicationReview(ctx context.Context, workspaceID, subject, approvalID string, expectedVersion int64, clientDecisionID, decision, reason string) (KBPublicationReview, error) {
	review, err := s.GetPublicationReview(ctx, workspaceID, subject, approvalID)
	if err != nil {
		return KBPublicationReview{}, err
	}
	if !review.CanDecide {
		return KBPublicationReview{}, forbidden("Publication approval is not assigned to the current account")
	}
	allowed, err := s.canAdministerWorkspace(ctx, workspaceID, subject)
	if err != nil {
		return KBPublicationReview{}, err
	}
	if !allowed {
		return KBPublicationReview{}, forbidden("Publication approval decision requires current workspace owner or admin access")
	}
	gateway := s.publicationGateway()
	if _, err := gateway.DecidePublication(ctx, kbgovernance.DecidePublicationCommand{
		WorkspaceID: workspaceID, ApprovalID: approvalID, ActorPrincipalID: subject,
		ExpectedVersion: expectedVersion, ClientDecisionID: clientDecisionID,
		Decision: decision, Reason: reason, CorrelationID: review.KnowledgeBaseID,
	}); err != nil {
		return KBPublicationReview{}, mapWikiApprovalGatewayError(err)
	}
	return s.GetPublicationReview(ctx, workspaceID, subject, approvalID)
}

func (s *KBService) WithdrawPublicationApproval(ctx context.Context, workspaceID, subject, kbID, approvalID string, expectedVersion int64, clientRequestID string) (KBSetupView, error) {
	if _, err := s.requireSetupPublicationApproval(ctx, workspaceID, subject, kbID, approvalID); err != nil {
		return KBSetupView{}, err
	}
	gateway := s.publicationGateway()
	if gateway == nil {
		return KBSetupView{}, codedConflict("kb-publication-approval-unavailable", "Knowledge base publication approvals are not configured", nil)
	}
	if _, err := gateway.WithdrawPublication(ctx, kbgovernance.WithdrawPublicationCommand{
		WorkspaceID: workspaceID, ApprovalID: approvalID, ActorPrincipalID: subject,
		ExpectedVersion: expectedVersion, ClientRequestID: clientRequestID,
	}); err != nil {
		return KBSetupView{}, mapWikiApprovalGatewayError(err)
	}
	return s.GetKnowledgeBaseSetup(ctx, workspaceID, subject, kbID)
}

func (s *KBService) requireSetupPublicationApproval(ctx context.Context, workspaceID, subject, kbID, approvalID string) (KBSetupView, error) {
	setup, err := s.GetKnowledgeBaseSetup(ctx, workspaceID, subject, kbID)
	if err != nil {
		return KBSetupView{}, err
	}
	if setup.PublicationRequest == nil || setup.PublicationRequest.ApprovalID != strings.TrimSpace(approvalID) {
		return KBSetupView{}, notFound("Publication approval was not found for this knowledge base")
	}
	return setup, nil
}

func (s *KBService) PreparePublication(ctx context.Context, command kbgovernance.PreparePublicationCommand) (kbgovernance.PreparedPublication, error) {
	command.WorkspaceID = strings.TrimSpace(command.WorkspaceID)
	command.KnowledgeBaseID = strings.TrimSpace(command.KnowledgeBaseID)
	command.WorkingRevision = strings.TrimSpace(command.WorkingRevision)
	command.RequesterID = strings.TrimSpace(command.RequesterID)
	command.IdempotencyKey = strings.TrimSpace(command.IdempotencyKey)
	command.CorrelationID = strings.TrimSpace(command.CorrelationID)
	if command.WorkingRevision == "" || command.IdempotencyKey == "" || len(command.IdempotencyKey) > 128 || len(command.CorrelationID) > maxWikiCorrelationIDBytes {
		return kbgovernance.PreparedPublication{}, invalidArgument("Publication working revision and bounded idempotency key are required")
	}
	base, err := s.GetKnowledgeBase(ctx, command.WorkspaceID, command.RequesterID, command.KnowledgeBaseID)
	if err != nil {
		return kbgovernance.PreparedPublication{}, err
	}
	if base.PublicationState != KBPublicationDraft || base.WorkingRevision != command.WorkingRevision {
		return kbgovernance.PreparedPublication{}, codedConflict("kb-publication-revision-stale", "The knowledge base working revision changed; refresh Review", nil)
	}
	if !base.IsActive {
		return kbgovernance.PreparedPublication{}, codedConflict("kb-publication-inactive", "Archived knowledge bases cannot be published", nil)
	}
	if !roleCanManage(s.knowledgeBaseRole(ctx, base, command.RequesterID)) {
		return kbgovernance.PreparedPublication{}, forbidden("Publication approval requires knowledge base owner or manager access")
	}
	if s.principalWorkspaceAccessVerifier == nil {
		return kbgovernance.PreparedPublication{}, invalidArgument("Publication approval requires workspace membership verification")
	}
	active, err := s.principalWorkspaceAccessVerifier.HasWorkspaceAccess(ctx, command.WorkspaceID, command.RequesterID)
	if err != nil {
		return kbgovernance.PreparedPublication{}, err
	}
	if !active {
		return kbgovernance.PreparedPublication{}, forbidden("Publication approval requires active workspace membership")
	}
	setup, err := s.GetKnowledgeBaseSetup(ctx, command.WorkspaceID, command.RequesterID, command.KnowledgeBaseID)
	if err != nil {
		return kbgovernance.PreparedPublication{}, err
	}
	if setup.StepSummary["review"] != "ready" {
		return kbgovernance.PreparedPublication{}, codedConflict("kb-publication-review-incomplete", "Complete all required setup steps before requesting publication", nil)
	}
	snapshot, err := s.store.LoadKnowledgeBasePublicationSnapshot(ctx, command.WorkspaceID, command.KnowledgeBaseID, false)
	if err != nil {
		return kbgovernance.PreparedPublication{}, mapStoreNotFound(err)
	}
	if snapshot.Snapshot.WorkingRevision != command.WorkingRevision {
		return kbgovernance.PreparedPublication{}, codedConflict("kb-publication-revision-stale", "The knowledge base working revision changed; refresh Review", nil)
	}
	payload := kbgovernance.PublicationPayload{
		SchemaVersion: kbgovernance.PublicationPayloadSchema,
		WorkspaceID:   command.WorkspaceID, KnowledgeBaseID: command.KnowledgeBaseID,
		WorkingRevision: command.WorkingRevision, ContentDigest: snapshot.Digest,
		SourceCount: snapshot.SourceCount, WikiReadyCount: snapshot.WikiReadyCount,
		DashboardReadyCount: snapshot.DashboardReadyCount, SkippedCount: snapshot.SkippedCount,
		RequesterID: command.RequesterID, IdempotencyKey: command.IdempotencyKey,
	}
	payloadJSON, payloadDigest, err := kbgovernance.EncodePayload(payload)
	if err != nil {
		return kbgovernance.PreparedPublication{}, fmt.Errorf("encode publication Approval payload: %w", err)
	}
	preparedAt := command.Now
	if preparedAt.IsZero() {
		preparedAt = s.now()
	}
	return kbgovernance.PreparedPublication{
		Payload: payload, PayloadJSON: payloadJSON, PayloadDigest: payloadDigest,
		SnapshotJSON: snapshot.JSON, CorrelationID: command.CorrelationID, PreparedAt: preparedAt.UTC(),
	}, nil
}

func (s *KBService) RecordPublicationApproval(ctx context.Context, command kbgovernance.RecordPublicationApprovalCommand) (kbgovernance.PublicationState, error) {
	if strings.TrimSpace(command.RequestID) == "" || strings.TrimSpace(command.ApprovalID) == "" || command.ApprovalVersion <= 0 {
		return kbgovernance.PublicationState{}, invalidArgument("Publication request and Approval identities are required")
	}
	existing, existingErr := s.store.GetKnowledgeBasePublicationRequestByApproval(ctx, command.Prepared.Payload.WorkspaceID, command.ApprovalID)
	if existingErr == nil {
		if existing.ID != strings.TrimSpace(command.RequestID) || existing.PayloadDigest != command.Prepared.PayloadDigest || existing.RequesterID != command.Prepared.Payload.RequesterID || existing.IdempotencyKey != command.Prepared.Payload.IdempotencyKey {
			return kbgovernance.PublicationState{}, codedConflict("kb-publication-approval-replay-conflict", "The publication Approval is already bound to another request", nil)
		}
		return publicationState(existing), nil
	}
	if !errors.Is(existingErr, gorm.ErrRecordNotFound) {
		return kbgovernance.PublicationState{}, existingErr
	}
	if command.ApprovalVersion != 1 {
		return kbgovernance.PublicationState{}, codedConflict("kb-publication-approval-record-missing", "A terminal publication Approval cannot create a missing domain request", nil)
	}
	fresh, err := s.PreparePublication(ctx, kbgovernance.PreparePublicationCommand{
		WorkspaceID: command.Prepared.Payload.WorkspaceID, KnowledgeBaseID: command.Prepared.Payload.KnowledgeBaseID,
		WorkingRevision: command.Prepared.Payload.WorkingRevision, RequesterID: command.Prepared.Payload.RequesterID,
		IdempotencyKey: command.Prepared.Payload.IdempotencyKey, CorrelationID: command.Prepared.CorrelationID,
	})
	if err != nil {
		return kbgovernance.PublicationState{}, err
	}
	if fresh.PayloadDigest != command.Prepared.PayloadDigest || !bytes.Equal(fresh.PayloadJSON, command.Prepared.PayloadJSON) || !bytes.Equal(fresh.SnapshotJSON, command.Prepared.SnapshotJSON) {
		return kbgovernance.PublicationState{}, codedConflict("kb-publication-approval-payload-mismatch", "The publication snapshot changed before it was recorded", nil)
	}
	now := command.Now
	if now.IsZero() {
		now = s.now()
	}
	request := storekb.KnowledgeBasePublicationRequest{
		ID: strings.TrimSpace(command.RequestID), WorkspaceID: fresh.Payload.WorkspaceID,
		KnowledgeBaseID: fresh.Payload.KnowledgeBaseID, WorkingRevision: fresh.Payload.WorkingRevision,
		RequesterID: fresh.Payload.RequesterID, ApprovalID: strings.TrimSpace(command.ApprovalID),
		ApprovalVersion: command.ApprovalVersion, Status: storekb.PublicationRequestStatusPendingApproval,
		IdempotencyKey: fresh.Payload.IdempotencyKey, PayloadDigest: fresh.PayloadDigest,
		ContentDigest: fresh.Payload.ContentDigest, SnapshotJSON: string(fresh.SnapshotJSON),
		SourceCount: fresh.Payload.SourceCount, WikiReadyCount: fresh.Payload.WikiReadyCount,
		DashboardReadyCount: fresh.Payload.DashboardReadyCount, SkippedCount: fresh.Payload.SkippedCount,
		Version: 1, CreatedAt: now, UpdatedAt: now,
	}
	stored, _, err := s.store.CreateKnowledgeBasePublicationRequest(ctx, request, now)
	if err != nil {
		switch {
		case errors.Is(err, storekb.ErrPublicationAlreadyPending):
			return kbgovernance.PublicationState{}, codedConflict("kb-publication-pending", "A publication request is already pending", err)
		case errors.Is(err, storekb.ErrPublicationReplayConflict):
			return kbgovernance.PublicationState{}, codedConflict("kb-publication-idempotency-conflict", "The publication request key is already bound to another payload", err)
		case errors.Is(err, storekb.ErrPublicationSnapshotStale):
			return kbgovernance.PublicationState{}, codedConflict("kb-publication-revision-stale", "The knowledge base changed before publication was requested", err)
		default:
			return kbgovernance.PublicationState{}, err
		}
	}
	return publicationState(stored), nil
}

func (s *KBService) ResolvePublicationApproval(ctx context.Context, command kbgovernance.ResolvePublicationApprovalCommand) (kbgovernance.PublicationState, error) {
	command.WorkspaceID, command.ApprovalID = strings.TrimSpace(command.WorkspaceID), strings.TrimSpace(command.ApprovalID)
	command.ActorPrincipalID, command.ClientDecisionID = strings.TrimSpace(command.ActorPrincipalID), strings.TrimSpace(command.ClientDecisionID)
	command.Decision, command.Reason = strings.TrimSpace(command.Decision), strings.TrimSpace(command.Reason)
	if command.WorkspaceID == "" || command.ApprovalID == "" || command.ApprovalVersion <= 0 || command.ActorPrincipalID == "" || command.ClientDecisionID == "" {
		return kbgovernance.PublicationState{}, invalidArgument("Publication Approval decision identities are required")
	}
	if command.Decision != kbgovernance.DecisionApproved && command.Decision != kbgovernance.DecisionRejected && command.Decision != kbgovernance.DecisionWithdrawn {
		return kbgovernance.PublicationState{}, invalidArgument("Publication Approval decision is invalid")
	}
	if command.Decision == kbgovernance.DecisionRejected && command.Reason == "" {
		return kbgovernance.PublicationState{}, invalidArgument("Publication Approval rejection reason is required")
	}
	request, err := s.store.GetKnowledgeBasePublicationRequestByApproval(ctx, command.WorkspaceID, command.ApprovalID)
	if err != nil {
		return kbgovernance.PublicationState{}, mapStoreNotFound(err)
	}
	if request.DecisionID == "" {
		if command.Decision == kbgovernance.DecisionWithdrawn {
			if request.RequesterID != command.ActorPrincipalID {
				return kbgovernance.PublicationState{}, forbidden("Only the publication applicant may withdraw it")
			}
			if s.principalWorkspaceAccessVerifier == nil {
				return kbgovernance.PublicationState{}, invalidArgument("Publication withdrawal requires workspace membership verification")
			}
			active, verifyErr := s.principalWorkspaceAccessVerifier.HasWorkspaceAccess(ctx, request.WorkspaceID, request.RequesterID)
			if verifyErr != nil {
				return kbgovernance.PublicationState{}, verifyErr
			}
			if !active {
				return kbgovernance.PublicationState{}, forbidden("Publication applicant is no longer an active workspace member")
			}
		} else {
			allowed, authorizeErr := s.canAdministerWorkspace(ctx, request.WorkspaceID, command.ActorPrincipalID)
			if authorizeErr != nil {
				return kbgovernance.PublicationState{}, authorizeErr
			}
			if !allowed {
				return kbgovernance.PublicationState{}, forbidden("Publication approval decision requires workspace owner or admin access")
			}
		}
	}
	authorizationValid := true
	if request.DecisionID == "" && command.Decision == kbgovernance.DecisionApproved {
		authorizationValid, err = s.publicationApplicantAuthorized(ctx, request)
		if err != nil {
			return kbgovernance.PublicationState{}, err
		}
	}
	now := command.Now
	if now.IsZero() {
		now = s.now()
	}
	audit := storekb.AuditEvent{
		ID: newKBID("audit"), WorkspaceID: request.WorkspaceID, KnowledgeBaseID: request.KnowledgeBaseID,
		ActorID: command.ActorPrincipalID, Action: "kb.publication.approval.resolved",
		ResourceType: "knowledge_base", ResourceID: request.KnowledgeBaseID,
		Summary: "Resolved knowledge base publication approval", Details: kbJSONString(map[string]string{
			"approval_id": request.ApprovalID, "request_id": request.ID,
			"decision": command.Decision, "reason": command.Reason,
		}), CreatedAt: now,
	}
	resolved, _, err := s.store.ResolveKnowledgeBasePublication(ctx, storekb.ResolveKnowledgeBasePublicationParams{
		WorkspaceID: request.WorkspaceID, ApprovalID: request.ApprovalID, ApprovalVersion: command.ApprovalVersion,
		ActorID: command.ActorPrincipalID, ClientDecisionID: command.ClientDecisionID,
		Decision: command.Decision, Reason: command.Reason, AuthorizationValid: authorizationValid,
		AuditEvent: audit, Now: now,
	})
	if errors.Is(err, storekb.ErrPublicationReplayConflict) {
		return kbgovernance.PublicationState{}, codedConflict("kb-publication-approval-replay-conflict", "The publication Approval decision was already applied with another payload", err)
	}
	if err != nil {
		return kbgovernance.PublicationState{}, err
	}
	return publicationState(resolved), nil
}

func (s *KBService) publicationApplicantAuthorized(ctx context.Context, request storekb.KnowledgeBasePublicationRequest) (bool, error) {
	base, err := s.store.GetKnowledgeBase(ctx, request.WorkspaceID, request.KnowledgeBaseID)
	if errors.Is(err, gorm.ErrRecordNotFound) {
		return false, nil
	}
	if err != nil {
		return false, err
	}
	if !base.IsActive || base.PublicationState != KBPublicationDraft {
		return false, nil
	}
	canAdministerWorkspace, err := s.canAdministerWorkspace(ctx, request.WorkspaceID, request.RequesterID)
	if err != nil {
		return false, err
	}
	if canAdministerWorkspace {
		return true, nil
	}
	if s.principalWorkspaceAccessVerifier == nil {
		return false, invalidArgument("Publication approval requires workspace membership verification")
	}
	active, err := s.principalWorkspaceAccessVerifier.HasWorkspaceAccess(ctx, request.WorkspaceID, request.RequesterID)
	if err != nil || !active {
		return false, err
	}
	if strings.TrimSpace(base.CreatedBy) == request.RequesterID {
		return true, nil
	}
	grant, err := s.store.GetKnowledgeBaseGrant(ctx, request.WorkspaceID, request.KnowledgeBaseID, "user", request.RequesterID)
	if errors.Is(err, gorm.ErrRecordNotFound) {
		return roleCanManage(normalizedKBDefaultWorkspaceMemberRole(base.DefaultWorkspaceMemberRole)), nil
	}
	if err != nil {
		return false, err
	}
	return roleCanManage(grant.Role), nil
}

func publicationState(request storekb.KnowledgeBasePublicationRequest) kbgovernance.PublicationState {
	return kbgovernance.PublicationState{
		RequestID: request.ID, ApprovalID: request.ApprovalID, ApprovalVersion: request.ApprovalVersion,
		Status: request.Status, DomainVersion: request.Version,
		Stale: request.Status == storekb.PublicationRequestStatusStale,
	}
}

var _ kbgovernance.PublicationAuthority = (*KBService)(nil)
