// Package providerhub adapts provider-hub write commands to agent-manager.
package providerhub

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

	"github.com/google/uuid"
	"google.golang.org/grpc"

	providersv1 "github.com/codex-k8s/kodex/proto/gen/go/kodex/providers/v1"
	"github.com/codex-k8s/kodex/services/internal/agent-manager/internal/clients/grpcclient"
	"github.com/codex-k8s/kodex/services/internal/agent-manager/internal/domain/errs"
	agentservice "github.com/codex-k8s/kodex/services/internal/agent-manager/internal/domain/service"
	"github.com/codex-k8s/kodex/services/internal/agent-manager/internal/domain/types/value"
)

const (
	callerID            = "agent-manager"
	defaultWriteTimeout = 10 * time.Second
)

// Config contains provider-hub client settings.
type Config struct {
	Addr      string
	AuthToken string
	Timeout   time.Duration
}

type providerHubClient interface {
	CreateIssue(context.Context, *providersv1.CreateIssueRequest, ...grpcCallOption) (*providerOperationResponse, error)
	UpdateIssue(context.Context, *providersv1.UpdateIssueRequest, ...grpcCallOption) (*providerOperationResponse, error)
	CreateComment(context.Context, *providersv1.CreateCommentRequest, ...grpcCallOption) (*providerOperationResponse, error)
	UpdateComment(context.Context, *providersv1.UpdateCommentRequest, ...grpcCallOption) (*providerOperationResponse, error)
	UpdatePullRequest(context.Context, *providersv1.UpdatePullRequestRequest, ...grpcCallOption) (*providerOperationResponse, error)
	CreateReviewSignal(context.Context, *providersv1.CreateReviewSignalRequest, ...grpcCallOption) (*providerOperationResponse, error)
}

type grpcCallOption = grpc.CallOption
type providerOperationResponse = providersv1.ProviderOperationResponse

type providerFollowUpCommand int

const (
	providerFollowUpCreateIssue providerFollowUpCommand = iota + 1
	providerFollowUpUpdateIssue
	providerFollowUpCreateComment
	providerFollowUpUpdateComment
	providerFollowUpUpdatePullRequest
	providerFollowUpCreateReviewSignal
)

var providerOperationTypeByName = map[string]providersv1.ProviderOperationType{
	agentservice.ProviderOperationTypeCreateIssue:        providersv1.ProviderOperationType_PROVIDER_OPERATION_TYPE_CREATE_ISSUE,
	agentservice.ProviderOperationTypeUpdateIssue:        providersv1.ProviderOperationType_PROVIDER_OPERATION_TYPE_UPDATE_ISSUE,
	agentservice.ProviderOperationTypeCreateComment:      providersv1.ProviderOperationType_PROVIDER_OPERATION_TYPE_CREATE_COMMENT,
	agentservice.ProviderOperationTypeUpdateComment:      providersv1.ProviderOperationType_PROVIDER_OPERATION_TYPE_UPDATE_COMMENT,
	agentservice.ProviderOperationTypeUpdatePullRequest:  providersv1.ProviderOperationType_PROVIDER_OPERATION_TYPE_UPDATE_PULL_REQUEST,
	agentservice.ProviderOperationTypeCreateReviewSignal: providersv1.ProviderOperationType_PROVIDER_OPERATION_TYPE_CREATE_REVIEW_SIGNAL,
}

// FollowUpDispatcher calls provider-hub typed write commands.
type FollowUpDispatcher struct {
	client    providerHubClient
	authToken string
	timeout   time.Duration
}

var _ agentservice.ProviderFollowUpDispatcher = (*FollowUpDispatcher)(nil)

// NewConnection creates a gRPC connection to provider-hub.
func NewConnection(cfg Config) (*grpc.ClientConn, error) {
	return grpcclient.NewConnection(cfg.Addr, "provider-hub")
}

// NewFollowUpDispatcher creates a provider-hub follow-up write client.
func NewFollowUpDispatcher(client providersv1.ProviderHubServiceClient, cfg Config) (*FollowUpDispatcher, error) {
	return newFollowUpDispatcher(client, cfg)
}

func newFollowUpDispatcher(client providerHubClient, cfg Config) (*FollowUpDispatcher, error) {
	settings, err := grpcclient.RequiredClientSettings(client, cfg.AuthToken, cfg.Timeout, defaultWriteTimeout, "provider-hub")
	if err != nil {
		return nil, err
	}
	dispatcher := &FollowUpDispatcher{client: client}
	dispatcher.authToken = settings.AuthToken
	dispatcher.timeout = settings.Timeout
	return dispatcher, nil
}

// CreateIssue sends a typed create Issue command through provider-hub.
func (dispatcher *FollowUpDispatcher) CreateIssue(ctx context.Context, input agentservice.ProviderCreateIssueInput) (agentservice.ProviderCommandResult, error) {
	return dispatcher.dispatchProviderCommand(ctx, providerFollowUpCreateIssue, createIssueRequest(input))
}

// UpdateIssue sends a typed update Issue command through provider-hub.
func (dispatcher *FollowUpDispatcher) UpdateIssue(ctx context.Context, input agentservice.ProviderUpdateIssueInput) (agentservice.ProviderCommandResult, error) {
	return dispatcher.dispatchProviderCommand(ctx, providerFollowUpUpdateIssue, updateIssueRequest(input))
}

// CreateComment sends a typed create comment command through provider-hub.
func (dispatcher *FollowUpDispatcher) CreateComment(ctx context.Context, input agentservice.ProviderCreateCommentInput) (agentservice.ProviderCommandResult, error) {
	return dispatcher.dispatchProviderCommand(ctx, providerFollowUpCreateComment, createCommentRequest(input))
}

// UpdateComment sends a typed update comment command through provider-hub.
func (dispatcher *FollowUpDispatcher) UpdateComment(ctx context.Context, input agentservice.ProviderUpdateCommentInput) (agentservice.ProviderCommandResult, error) {
	return dispatcher.dispatchProviderCommand(ctx, providerFollowUpUpdateComment, updateCommentRequest(input))
}

// UpdatePullRequest sends a typed update PR/MR command through provider-hub.
func (dispatcher *FollowUpDispatcher) UpdatePullRequest(ctx context.Context, input agentservice.ProviderUpdatePullRequestInput) (agentservice.ProviderCommandResult, error) {
	return dispatcher.dispatchProviderCommand(ctx, providerFollowUpUpdatePullRequest, updatePullRequestRequest(input))
}

// CreateReviewSignal sends a typed review signal command through provider-hub.
func (dispatcher *FollowUpDispatcher) CreateReviewSignal(ctx context.Context, input agentservice.ProviderCreateReviewSignalInput) (agentservice.ProviderCommandResult, error) {
	return dispatcher.dispatchProviderCommand(ctx, providerFollowUpCreateReviewSignal, createReviewSignalRequest(input))
}

func (dispatcher *FollowUpDispatcher) dispatchProviderCommand(ctx context.Context, command providerFollowUpCommand, request any) (agentservice.ProviderCommandResult, error) {
	return dispatcher.dispatch(ctx, func(callCtx context.Context) (*providersv1.ProviderOperationResponse, error) {
		switch command {
		case providerFollowUpCreateIssue:
			return dispatcher.client.CreateIssue(callCtx, request.(*providersv1.CreateIssueRequest))
		case providerFollowUpUpdateIssue:
			return dispatcher.client.UpdateIssue(callCtx, request.(*providersv1.UpdateIssueRequest))
		case providerFollowUpCreateComment:
			return dispatcher.client.CreateComment(callCtx, request.(*providersv1.CreateCommentRequest))
		case providerFollowUpUpdateComment:
			return dispatcher.client.UpdateComment(callCtx, request.(*providersv1.UpdateCommentRequest))
		case providerFollowUpUpdatePullRequest:
			return dispatcher.client.UpdatePullRequest(callCtx, request.(*providersv1.UpdatePullRequestRequest))
		case providerFollowUpCreateReviewSignal:
			return dispatcher.client.CreateReviewSignal(callCtx, request.(*providersv1.CreateReviewSignalRequest))
		default:
			return nil, errs.ErrInvalidArgument
		}
	})
}

func (dispatcher *FollowUpDispatcher) dispatch(ctx context.Context, call func(context.Context) (*providersv1.ProviderOperationResponse, error)) (agentservice.ProviderCommandResult, error) {
	if err := dispatcher.requireReady(); err != nil {
		return agentservice.ProviderCommandResult{}, err
	}
	callCtx, cancel := context.WithTimeout(dispatcher.outgoingContext(ctx), dispatcher.timeout)
	defer cancel()
	response, err := call(callCtx)
	if err != nil {
		return agentservice.ProviderCommandResult{}, mapProviderHubWriteError(err)
	}
	return providerCommandResult(response)
}

func (dispatcher *FollowUpDispatcher) requireReady() error {
	if dispatcher == nil || dispatcher.client == nil {
		return errs.ErrDependencyUnavailable
	}
	return nil
}

func (dispatcher *FollowUpDispatcher) outgoingContext(ctx context.Context) context.Context {
	return grpcclient.OutgoingContext(ctx, dispatcher.authToken, callerID)
}

func createIssueRequest(input agentservice.ProviderCreateIssueInput) *providersv1.CreateIssueRequest {
	return &providersv1.CreateIssueRequest{
		ProjectId:              input.ProjectID.String(),
		RepositoryId:           input.RepositoryID.String(),
		ProviderSlug:           strings.TrimSpace(input.ProviderSlug),
		Title:                  strings.TrimSpace(input.Title),
		Body:                   strings.TrimSpace(input.Body),
		Labels:                 append([]string(nil), input.Labels...),
		AssigneeProviderLogins: append([]string(nil), input.AssigneeProviderLogins...),
		Milestone:              optionalString(strings.TrimSpace(input.Milestone)),
		WorkItemType:           optionalString(strings.TrimSpace(input.WorkItemType)),
		WatermarkJson:          optionalString(string(input.WatermarkJSON)),
		Meta:                   commandMeta(input.Meta, input.OperationPolicyContext, input.ApprovalGateRef),
		ExternalAccountId:      input.ExternalAccountID.String(),
		RepositoryTarget:       providerTarget(input.RepositoryTarget),
	}
}

func updateIssueRequest(input agentservice.ProviderUpdateIssueInput) *providersv1.UpdateIssueRequest {
	return &providersv1.UpdateIssueRequest{
		Target:                  providerTarget(input.Target),
		Title:                   optionalStringPointer(input.Title),
		Body:                    optionalStringPointer(input.Body),
		Labels:                  stringListPatch(input.Labels),
		AssigneeProviderLogins:  stringListPatch(input.AssigneeProviderLogins),
		Milestone:               optionalStringPointer(input.Milestone),
		State:                   optionalStringPointer(input.State),
		WorkItemType:            optionalStringPointer(input.WorkItemType),
		WatermarkJson:           optionalBytesPointer(input.WatermarkJSON),
		ExpectedProviderVersion: optionalString(strings.TrimSpace(input.ExpectedProviderVersion)),
		Meta:                    commandMeta(input.Meta, input.OperationPolicyContext, input.ApprovalGateRef),
		ExternalAccountId:       input.ExternalAccountID.String(),
	}
}

func createCommentRequest(input agentservice.ProviderCreateCommentInput) *providersv1.CreateCommentRequest {
	return &providersv1.CreateCommentRequest{
		Target:            providerTarget(input.Target),
		Body:              strings.TrimSpace(input.Body),
		Meta:              commandMeta(input.Meta, input.OperationPolicyContext, input.ApprovalGateRef),
		ExternalAccountId: input.ExternalAccountID.String(),
	}
}

func updateCommentRequest(input agentservice.ProviderUpdateCommentInput) *providersv1.UpdateCommentRequest {
	return &providersv1.UpdateCommentRequest{
		Target:                  providerTarget(input.Target),
		ProviderCommentId:       strings.TrimSpace(input.ProviderCommentID),
		Body:                    strings.TrimSpace(input.Body),
		ExpectedProviderVersion: optionalString(strings.TrimSpace(input.ExpectedProviderVersion)),
		Meta:                    commandMeta(input.Meta, input.OperationPolicyContext, input.ApprovalGateRef),
		ExternalAccountId:       input.ExternalAccountID.String(),
	}
}

func updatePullRequestRequest(input agentservice.ProviderUpdatePullRequestInput) *providersv1.UpdatePullRequestRequest {
	return &providersv1.UpdatePullRequestRequest{
		Target:                  providerTarget(input.Target),
		Title:                   optionalStringPointer(input.Title),
		Body:                    optionalStringPointer(input.Body),
		Labels:                  stringListPatch(input.Labels),
		AssigneeProviderLogins:  stringListPatch(input.AssigneeProviderLogins),
		Milestone:               optionalStringPointer(input.Milestone),
		State:                   optionalStringPointer(input.State),
		BaseBranch:              optionalStringPointer(input.BaseBranch),
		MaintainerCanModify:     input.MaintainerCanModify,
		WatermarkJson:           optionalBytesPointer(input.WatermarkJSON),
		ExpectedProviderVersion: optionalString(strings.TrimSpace(input.ExpectedProviderVersion)),
		Meta:                    commandMeta(input.Meta, input.OperationPolicyContext, input.ApprovalGateRef),
		ExternalAccountId:       input.ExternalAccountID.String(),
	}
}

func createReviewSignalRequest(input agentservice.ProviderCreateReviewSignalInput) *providersv1.CreateReviewSignalRequest {
	return &providersv1.CreateReviewSignalRequest{
		Target:            providerTarget(input.Target),
		Kind:              reviewSignalKind(input.Kind),
		Body:              strings.TrimSpace(input.Body),
		InlineComments:    reviewInlineComments(input.InlineComments),
		Meta:              commandMeta(input.Meta, input.OperationPolicyContext, input.ApprovalGateRef),
		ExternalAccountId: input.ExternalAccountID.String(),
	}
}

func commandMeta(meta value.CommandMeta, policy agentservice.ProviderOperationPolicyContext, gate agentservice.ProviderApprovalGateReference) *providersv1.CommandMeta {
	commandID := optionalUUIDString(meta.CommandID)
	idempotencyKey := optionalString(strings.TrimSpace(meta.IdempotencyKey))
	requestID := firstNonEmpty(optionalStringValue(commandID), strings.TrimSpace(meta.IdempotencyKey), strings.TrimSpace(meta.CommandID.String()))
	return &providersv1.CommandMeta{
		CommandId:              commandID,
		IdempotencyKey:         idempotencyKey,
		Actor:                  actor(meta.Actor),
		Reason:                 "agent-follow-up-dispatch",
		RequestId:              requestID,
		RequestContext:         &providersv1.RequestContext{Source: callerID},
		OperationPolicyContext: policyContext(policy),
		ApprovalGateRef:        approvalGateRef(gate),
	}
}

func actor(actor value.Actor) *providersv1.Actor {
	return &providersv1.Actor{Type: strings.TrimSpace(actor.Type), Id: strings.TrimSpace(actor.ID)}
}

func providerTarget(target agentservice.ProviderCommandTarget) *providersv1.ProviderTarget {
	return &providersv1.ProviderTarget{
		ProviderSlug:         strings.TrimSpace(target.ProviderSlug),
		RepositoryFullName:   optionalString(strings.TrimSpace(target.RepositoryFullName)),
		ProviderRepositoryId: optionalString(strings.TrimSpace(target.ProviderRepositoryID)),
		WorkItemKind:         optionalWorkItemKind(target.WorkItemKind),
		Number:               optionalPositiveInt64(target.Number),
		ProviderObjectId:     optionalString(strings.TrimSpace(target.ProviderObjectID)),
		WebUrl:               optionalString(strings.TrimSpace(target.WebURL)),
	}
}

func policyContext(policy agentservice.ProviderOperationPolicyContext) *providersv1.ProviderOperationPolicyContext {
	return &providersv1.ProviderOperationPolicyContext{
		ProjectId:         optionalString(policy.ProjectID),
		RepositoryId:      optionalString(policy.RepositoryID),
		Stage:             optionalString(policy.Stage),
		RoleId:            optionalString(policy.RoleID),
		RoleKey:           optionalString(policy.RoleKey),
		OperationType:     operationType(policy.OperationType),
		TargetRef:         optionalString(policy.TargetRef),
		ChangedFields:     append([]string(nil), policy.ChangedFields...),
		RiskTags:          append([]string(nil), policy.RiskTags...),
		RiskLevel:         riskLevel(policy.RiskLevel),
		ApprovalRequired:  policy.ApprovalRequired,
		PolicyVersion:     optionalString(policy.PolicyVersion),
		PolicySnapshotRef: optionalString(policy.PolicySnapshotRef),
	}
}

func approvalGateRef(gate agentservice.ProviderApprovalGateReference) *providersv1.ApprovalGateReference {
	if gate.ApprovalID == "" && gate.GateType == "" && gate.Decision == "" && gate.DecidedByActorID == "" && gate.DecidedAt == "" && gate.EvidenceRef == "" && gate.PolicyVersion == "" {
		return nil
	}
	return &providersv1.ApprovalGateReference{
		ApprovalId:       strings.TrimSpace(gate.ApprovalID),
		GateType:         strings.TrimSpace(gate.GateType),
		Decision:         strings.TrimSpace(gate.Decision),
		DecidedByActorId: optionalString(gate.DecidedByActorID),
		DecidedAt:        optionalString(gate.DecidedAt),
		EvidenceRef:      optionalString(gate.EvidenceRef),
		PolicyVersion:    optionalString(gate.PolicyVersion),
	}
}

func providerCommandResult(response *providersv1.ProviderOperationResponse) (agentservice.ProviderCommandResult, error) {
	if response == nil || response.GetProviderOperation() == nil {
		return agentservice.ProviderCommandResult{}, errs.ErrDependencyUnavailable
	}
	operation := response.GetProviderOperation()
	result := response.GetResult()
	commandResult := agentservice.ProviderCommandResult{
		ProviderOperationRef: providerOperationRef(operation.GetProviderOperationId()),
		Status:               operationStatus(operation.GetStatus()),
		ResultRef:            firstNonEmpty(result.GetResultRef(), operation.GetResultRef()),
		ProviderObjectID:     firstNonEmpty(result.GetProviderObjectId(), ""),
		ProviderVersion:      firstNonEmpty(result.GetProviderVersion(), operation.GetProviderVersion()),
		ErrorCode:            strings.TrimSpace(operation.GetErrorCode()),
		ErrorMessage:         strings.TrimSpace(operation.GetErrorMessage()),
	}
	if target := result.GetTarget(); target != nil {
		commandResult.Target = providerTargetFromProto(target)
	} else if projection := response.GetWorkItemProjection(); projection != nil {
		commandResult.Target = providerTargetFromProjection(projection)
	} else if comment := response.GetCommentProjection(); comment != nil {
		commandResult.Target = providerTargetFromCommentProjection(comment)
	}
	if commandResult.Status == "" {
		return agentservice.ProviderCommandResult{}, errs.ErrDependencyUnavailable
	}
	return commandResult, nil
}

func providerTargetFromProto(target *providersv1.ProviderTarget) agentservice.ProviderCommandTarget {
	return providerTargetFromValues(
		target.GetProviderSlug(),
		target.GetRepositoryFullName(),
		target.GetProviderRepositoryId(),
		target.GetWorkItemKind(),
		target.GetNumber(),
		target.GetProviderObjectId(),
		target.GetWebUrl(),
	)
}

func providerTargetFromProjection(projection *providersv1.WorkItemProjection) agentservice.ProviderCommandTarget {
	return providerTargetFromValues(
		projection.GetProviderSlug(),
		projection.GetRepositoryFullName(),
		"",
		projection.GetKind(),
		projection.GetNumber(),
		projection.GetProviderWorkItemId(),
		projection.GetWebUrl(),
	)
}

func providerTargetFromCommentProjection(projection *providersv1.CommentProjection) agentservice.ProviderCommandTarget {
	return providerTargetFromValues("", "", "", providersv1.WorkItemKind_WORK_ITEM_KIND_UNSPECIFIED, 0, projection.GetProviderCommentId(), "")
}

func providerTargetFromValues(slug, fullName, repositoryID string, kind providersv1.WorkItemKind, number int64, objectID, webURL string) agentservice.ProviderCommandTarget {
	target := agentservice.ProviderCommandTarget{}
	target.ProviderSlug = strings.TrimSpace(slug)
	target.RepositoryFullName = strings.TrimSpace(fullName)
	target.ProviderRepositoryID = strings.TrimSpace(repositoryID)
	target.WorkItemKind = workItemKind(kind)
	target.Number = number
	target.ProviderObjectID = strings.TrimSpace(objectID)
	target.WebURL = strings.TrimSpace(webURL)
	return target
}

func mapProviderHubWriteError(err error) error {
	if err == nil {
		return nil
	}
	if errors.Is(err, context.Canceled) {
		return err
	}
	return grpcclient.MapReadError(err, "provider-hub write command failed")
}

func providerOperationRef(id string) string {
	trimmed := strings.TrimSpace(id)
	if trimmed == "" {
		return ""
	}
	return "provider_operation:" + trimmed
}

func optionalString(value string) *string {
	trimmed := strings.TrimSpace(value)
	if trimmed == "" {
		return nil
	}
	return &trimmed
}

func optionalStringPointer(value *string) *string {
	if value == nil {
		return nil
	}
	trimmed := strings.TrimSpace(*value)
	return &trimmed
}

func optionalBytesPointer(value *[]byte) *string {
	if value == nil {
		return nil
	}
	trimmed := strings.TrimSpace(string(*value))
	return &trimmed
}

func optionalStringValue(value *string) string {
	if value == nil {
		return ""
	}
	return strings.TrimSpace(*value)
}

func optionalUUIDString(id uuid.UUID) *string {
	if id == uuid.Nil {
		return nil
	}
	value := id.String()
	return &value
}

func optionalPositiveInt64(value int64) *int64 {
	if value <= 0 {
		return nil
	}
	return &value
}

func optionalWorkItemKind(kind string) *providersv1.WorkItemKind {
	mapped := workItemKindToProto(kind)
	if mapped == providersv1.WorkItemKind_WORK_ITEM_KIND_UNSPECIFIED {
		return nil
	}
	return &mapped
}

func stringListPatch(patch *agentservice.ProviderStringListPatch) *providersv1.StringListPatch {
	if patch == nil {
		return nil
	}
	return &providersv1.StringListPatch{Values: append([]string(nil), patch.Values...)}
}

func reviewInlineComments(comments []agentservice.ProviderReviewInlineComment) []*providersv1.ReviewInlineComment {
	if len(comments) == 0 {
		return nil
	}
	result := make([]*providersv1.ReviewInlineComment, 0, len(comments))
	for _, comment := range comments {
		result = append(result, &providersv1.ReviewInlineComment{
			Path:                       strings.TrimSpace(comment.Path),
			Body:                       strings.TrimSpace(comment.Body),
			Line:                       comment.Line,
			StartLine:                  comment.StartLine,
			Side:                       optionalString(strings.TrimSpace(comment.Side)),
			StartSide:                  optionalString(strings.TrimSpace(comment.StartSide)),
			InReplyToProviderCommentId: optionalString(strings.TrimSpace(comment.InReplyToProviderCommentID)),
		})
	}
	return result
}

func workItemKindToProto(kind string) providersv1.WorkItemKind {
	switch strings.TrimSpace(kind) {
	case "issue":
		return providersv1.WorkItemKind_WORK_ITEM_KIND_ISSUE
	case "pull_request":
		return providersv1.WorkItemKind_WORK_ITEM_KIND_PULL_REQUEST
	case "merge_request":
		return providersv1.WorkItemKind_WORK_ITEM_KIND_MERGE_REQUEST
	default:
		return providersv1.WorkItemKind_WORK_ITEM_KIND_UNSPECIFIED
	}
}

func workItemKind(kind providersv1.WorkItemKind) string {
	switch kind {
	case providersv1.WorkItemKind_WORK_ITEM_KIND_ISSUE:
		return "issue"
	case providersv1.WorkItemKind_WORK_ITEM_KIND_PULL_REQUEST:
		return "pull_request"
	case providersv1.WorkItemKind_WORK_ITEM_KIND_MERGE_REQUEST:
		return "merge_request"
	default:
		return ""
	}
}

func operationType(operationType string) providersv1.ProviderOperationType {
	if mapped, ok := providerOperationTypeByName[strings.TrimSpace(operationType)]; ok {
		return mapped
	}
	return providersv1.ProviderOperationType_PROVIDER_OPERATION_TYPE_UNSPECIFIED
}

func reviewSignalKind(kind agentservice.ProviderReviewSignalKind) providersv1.ReviewSignalKind {
	switch kind {
	case agentservice.ProviderReviewSignalKindComment:
		return providersv1.ReviewSignalKind_REVIEW_SIGNAL_KIND_COMMENT
	case agentservice.ProviderReviewSignalKindApproval:
		return providersv1.ReviewSignalKind_REVIEW_SIGNAL_KIND_APPROVAL
	case agentservice.ProviderReviewSignalKindChangesRequested:
		return providersv1.ReviewSignalKind_REVIEW_SIGNAL_KIND_CHANGES_REQUESTED
	default:
		return providersv1.ReviewSignalKind_REVIEW_SIGNAL_KIND_UNSPECIFIED
	}
}

func riskLevel(level string) providersv1.ProviderOperationRiskLevel {
	levels := map[string]providersv1.ProviderOperationRiskLevel{
		agentservice.ProviderRiskLevelLow:      providersv1.ProviderOperationRiskLevel_PROVIDER_OPERATION_RISK_LEVEL_LOW,
		agentservice.ProviderRiskLevelMedium:   providersv1.ProviderOperationRiskLevel_PROVIDER_OPERATION_RISK_LEVEL_MEDIUM,
		agentservice.ProviderRiskLevelHigh:     providersv1.ProviderOperationRiskLevel_PROVIDER_OPERATION_RISK_LEVEL_HIGH,
		agentservice.ProviderRiskLevelCritical: providersv1.ProviderOperationRiskLevel_PROVIDER_OPERATION_RISK_LEVEL_CRITICAL,
	}
	if mapped, ok := levels[strings.TrimSpace(level)]; ok {
		return mapped
	}
	return providersv1.ProviderOperationRiskLevel_PROVIDER_OPERATION_RISK_LEVEL_UNSPECIFIED
}

func operationStatus(status providersv1.ProviderOperationStatus) agentservice.ProviderOperationStatus {
	statuses := map[providersv1.ProviderOperationStatus]agentservice.ProviderOperationStatus{
		providersv1.ProviderOperationStatus_PROVIDER_OPERATION_STATUS_SUCCEEDED:        agentservice.ProviderOperationStatusSucceeded,
		providersv1.ProviderOperationStatus_PROVIDER_OPERATION_STATUS_FAILED:           agentservice.ProviderOperationStatusFailed,
		providersv1.ProviderOperationStatus_PROVIDER_OPERATION_STATUS_RETRYABLE_FAILED: agentservice.ProviderOperationStatusRetryableFailed,
		providersv1.ProviderOperationStatus_PROVIDER_OPERATION_STATUS_DENIED:           agentservice.ProviderOperationStatusDenied,
		providersv1.ProviderOperationStatus_PROVIDER_OPERATION_STATUS_IN_PROGRESS:      agentservice.ProviderOperationStatusInProgress,
	}
	if mapped, ok := statuses[status]; ok {
		return mapped
	}
	return ""
}

func firstNonEmpty(values ...string) string {
	for _, value := range values {
		trimmed := strings.TrimSpace(value)
		if trimmed != "" && trimmed != uuid.Nil.String() {
			return trimmed
		}
	}
	return ""
}
