package converter

import (
	"encoding/base64"
	"time"

	"github.com/tidbcloud/AgentTeam/internal/api/dto"
	"github.com/tidbcloud/AgentTeam/internal/automation/dsl"
	automationservice "github.com/tidbcloud/AgentTeam/internal/service/automation"
	"github.com/tidbcloud/AgentTeam/internal/service/automation/runtime"
	automationstore "github.com/tidbcloud/AgentTeam/internal/store/automation"
)

func ToAutomationRun(run automationstore.AutomationRun) dto.AutomationRun {
	return dto.AutomationRun{
		ID:                   run.ID,
		WorkspaceID:          run.WorkspaceID,
		IdempotencyKey:       optionalString(run.IdempotencyKey),
		SourceKind:           run.SourceKind,
		ContentID:            run.ContentID,
		ContentVersionID:     run.ContentVersionID,
		RepositoryOwner:      run.RepositoryOwner,
		RepositoryName:       run.RepositoryName,
		RepositoryFullName:   run.RepositoryFullName,
		DefinitionPath:       run.DefinitionPath,
		GitRef:               run.GitRef,
		CommitSHA:            run.CommitSHA,
		DefinitionID:         run.DefinitionID,
		Name:                 run.Name,
		RunName:              run.RunName,
		Status:               run.Status,
		InputJSON:            run.InputJSON,
		OutputJSON:           run.OutputJSON,
		ErrorJSON:            run.ErrorJSON,
		Error:                ToAutomationRunError(automationservice.ParseAutomationStructuredError(run.ErrorJSON)),
		TriggerKind:          run.TriggerKind,
		TriggerRef:           run.TriggerRef,
		TriggerContext:       ToAutomationTriggerContext(automationservice.AutomationTriggerContextFromMetadata(run.Metadata, run.TriggerKind)),
		TargetKind:           run.TargetKind,
		TargetRef:            run.TargetRef,
		OwnerPrincipalID:     run.OwnerPrincipalID,
		CreatedByPrincipalID: run.CreatedByPrincipalID,
		CurrentJobID:         run.CurrentJobID,
		Summary:              run.Summary,
		Metadata:             run.Metadata,
		StartedAt:            optionalTime(run.StartedAt),
		CompletedAt:          optionalTime(run.CompletedAt),
		CreatedAt:            run.CreatedAt.UTC().Format(timeLayout),
		UpdatedAt:            run.UpdatedAt.UTC().Format(timeLayout),
	}
}

func optionalString(value *string) string {
	if value == nil {
		return ""
	}
	return *value
}

func ToAutomationRuns(runs []automationstore.AutomationRun) []dto.AutomationRun {
	if runs == nil {
		return nil
	}
	out := make([]dto.AutomationRun, 0, len(runs))
	for _, run := range runs {
		out = append(out, ToAutomationRun(run))
	}
	return out
}

func ToAutomationDashboard(dashboard automationservice.AutomationDashboard) dto.AutomationDashboard {
	return dto.AutomationDashboard{
		ObservedAt:     dashboard.ObservedAt.UTC().Format(time.RFC3339Nano),
		Health:         ToAutomationDashboardHealth(dashboard.Health),
		Metrics:        ToAutomationDashboardMetrics(dashboard.Metrics),
		AttentionItems: ToAutomationDashboardAttentionItems(dashboard.AttentionItems),
	}
}

func ToAutomationDashboardHealth(health automationservice.AutomationDashboardHealth) dto.AutomationDashboardHealth {
	return dto.AutomationDashboardHealth{Status: health.Status, IssueCount: health.IssueCount}
}

func ToAutomationDashboardAttentionItem(item automationservice.AutomationDashboardAttentionItem) dto.AutomationDashboardAttentionItem {
	return dto.AutomationDashboardAttentionItem{Kind: item.Kind, Severity: item.Severity, Count: item.Count, TargetPath: item.TargetPath}
}

func ToAutomationDashboardAttentionItems(items []automationservice.AutomationDashboardAttentionItem) []dto.AutomationDashboardAttentionItem {
	if items == nil {
		return nil
	}
	out := make([]dto.AutomationDashboardAttentionItem, 0, len(items))
	for _, item := range items {
		out = append(out, ToAutomationDashboardAttentionItem(item))
	}
	return out
}

func ToAutomationDashboardMetrics(metrics automationservice.AutomationDashboardMetrics) dto.AutomationDashboardMetrics {
	return dto.AutomationDashboardMetrics{
		ObservedAt: metrics.ObservedAt.UTC().Format(time.RFC3339Nano),
		QueueMetrics: dto.AutomationQueueMetrics{
			Ready:         metrics.QueueMetrics.Ready,
			Leased:        metrics.QueueMetrics.Leased,
			Done:          metrics.QueueMetrics.Done,
			Failed:        metrics.QueueMetrics.Failed,
			Canceled:      metrics.QueueMetrics.Canceled,
			ExpiredLeases: metrics.QueueMetrics.ExpiredLeases,
			RetryReady:    metrics.QueueMetrics.RetryReady,
		},
		RunMetrics: dto.AutomationRunMetrics{
			Queued:               metrics.RunMetrics.Queued,
			Running:              metrics.RunMetrics.Running,
			Waiting:              metrics.RunMetrics.Waiting,
			Blocked:              metrics.RunMetrics.Blocked,
			Failed:               metrics.RunMetrics.Failed,
			Succeeded:            metrics.RunMetrics.Succeeded,
			Canceled:             metrics.RunMetrics.Canceled,
			ActiveSlots:          metrics.RunMetrics.ActiveSlots,
			LeakedActiveSlots:    metrics.RunMetrics.LeakedActiveSlots,
			TerminalInconsistent: metrics.RunMetrics.TerminalInconsistent,
		},
		WaitMetrics: dto.AutomationWaitMetrics{
			Active:           metrics.WaitMetrics.Active,
			Resolved:         metrics.WaitMetrics.Resolved,
			Canceled:         metrics.WaitMetrics.Canceled,
			ActiveAgentTasks: metrics.WaitMetrics.ActiveAgentTasks,
			ActiveEventWaits: metrics.WaitMetrics.ActiveEventWaits,
		},
		RunnerMetrics: dto.AutomationRunnerMetrics{
			Registered:      metrics.RunnerMetrics.Registered,
			Active:          metrics.RunnerMetrics.Active,
			Revoked:         metrics.RunnerMetrics.Revoked,
			ActiveSessions:  metrics.RunnerMetrics.ActiveSessions,
			ExpiredSessions: metrics.RunnerMetrics.ExpiredSessions,
			RevokedSessions: metrics.RunnerMetrics.RevokedSessions,
		},
		BlobMetrics: dto.AutomationBlobMetrics{
			LogChunks:        metrics.BlobMetrics.LogChunks,
			Artifacts:        metrics.BlobMetrics.Artifacts,
			ExpiredArtifacts: metrics.BlobMetrics.ExpiredArtifacts,
		},
		Reconciler: ToAutomationReconciler(metrics.Reconciler),
	}
}

func ToAutomationReconciler(status runtime.ReconcilerStatus) dto.AutomationReconciler {
	return dto.AutomationReconciler{
		LastStartedAt:       timeOrEmpty(status.LastStartedAt),
		LastCompletedAt:     timeOrEmpty(status.LastCompletedAt),
		LastSuccessAt:       timeOrEmpty(status.LastSuccessAt),
		LastFailureAt:       timeOrEmpty(status.LastFailureAt),
		LastError:           status.LastError,
		ErrorCount:          status.ErrorCount,
		ConsecutiveFailures: status.ConsecutiveFailures,
		LastResult: dto.AutomationReconcileResult{
			StartedAt:              timeOrEmpty(status.LastResult.StartedAt),
			CompletedAt:            timeOrEmpty(status.LastResult.CompletedAt),
			RecoveredExpiredJobs:   status.LastResult.RecoveredExpiredJobs,
			ReconciledDependencies: status.LastResult.ReconciledDependencies,
			ReconciledRuns:         status.LastResult.ReconciledRuns,
			RepairedActiveRunSlots: status.LastResult.RepairedActiveRunSlots,
		},
	}
}

func ToAutomationRunOverview(overview automationservice.AutomationRunOverview) dto.AutomationRunOverview {
	return dto.AutomationRunOverview{
		ObservedAt:     overview.ObservedAt.UTC().Format(time.RFC3339Nano),
		Run:            ToAutomationRun(overview.Run),
		JobRuns:        ToAutomationJobRuns(overview.JobRuns),
		QueueJobs:      ToAutomationQueueJobs(overview.QueueJobs),
		RunnerSessions: ToAutomationRunnerSessionRefs(overview.RunnerSessions),
		Waits:          ToAutomationWaits(overview.Waits),
		Events:         ToAutomationEvents(overview.Events),
		LogChunks:      ToAutomationLogChunks(overview.LogChunks),
		Artifacts:      ToAutomationArtifacts(overview.Artifacts),
		BlobSummaries:  ToAutomationRunBlobSummaries(overview.BlobSummaries),
		Metrics:        ToAutomationDashboardMetrics(overview.Metrics),
	}
}

func ToAutomationQueueJob(job automationstore.AutomationJob) dto.AutomationQueueJob {
	return dto.AutomationQueueJob{
		ID:             job.ID,
		WorkspaceID:    job.WorkspaceID,
		RunID:          job.RunID,
		JobRunID:       job.JobRunID,
		QueueName:      job.QueueName,
		Status:         job.Status,
		Priority:       job.Priority,
		ScheduledAt:    job.ScheduledAt.UTC().Format(time.RFC3339Nano),
		NotBefore:      optionalTime(job.NotBefore),
		LeaseOwner:     job.LeaseOwner,
		LeaseUntil:     optionalTime(job.LeaseUntil),
		Attempt:        job.Attempt,
		MaxAttempts:    job.MaxAttempts,
		RunnerGroup:    job.RunnerGroup,
		RunnerGroupKey: job.RunnerGroupKey,
		LabelsJSON:     job.LabelsJSON,
		LabelKey:       job.LabelKey,
		LabelCount:     job.LabelCount,
		ShardBucket:    job.ShardBucket,
		CreatedAt:      job.CreatedAt.UTC().Format(time.RFC3339Nano),
		UpdatedAt:      job.UpdatedAt.UTC().Format(time.RFC3339Nano),
	}
}

func ToAutomationQueueJobs(jobs []automationstore.AutomationJob) []dto.AutomationQueueJob {
	if jobs == nil {
		return nil
	}
	out := make([]dto.AutomationQueueJob, 0, len(jobs))
	for _, job := range jobs {
		out = append(out, ToAutomationQueueJob(job))
	}
	return out
}

func ToAutomationRunnerSessionRef(session automationstore.AutomationRunnerSession) dto.AutomationRunnerSessionRef {
	return dto.AutomationRunnerSessionRef{
		WorkspaceID: session.WorkspaceID,
		RunnerID:    session.RunnerID,
		SessionID:   session.SessionID,
		LeaseOwner:  session.LeaseOwner,
		RunnerGroup: session.RunnerGroup,
		LabelsJSON:  session.LabelsJSON,
		Status:      session.Status,
		HeartbeatAt: session.HeartbeatAt.UTC().Format(time.RFC3339Nano),
		ExpiresAt:   session.ExpiresAt.UTC().Format(time.RFC3339Nano),
		RevokedAt:   optionalTime(session.RevokedAt),
		CreatedAt:   session.CreatedAt.UTC().Format(time.RFC3339Nano),
		UpdatedAt:   session.UpdatedAt.UTC().Format(time.RFC3339Nano),
	}
}

func ToAutomationRunnerSessionRefs(sessions []automationstore.AutomationRunnerSession) []dto.AutomationRunnerSessionRef {
	if sessions == nil {
		return nil
	}
	out := make([]dto.AutomationRunnerSessionRef, 0, len(sessions))
	for _, session := range sessions {
		out = append(out, ToAutomationRunnerSessionRef(session))
	}
	return out
}

func ToAutomationRunBlobSummary(summary automationstore.AutomationRunBlobSummary) dto.AutomationRunBlobSummary {
	return dto.AutomationRunBlobSummary{
		JobRunID:         summary.JobRunID,
		LogChunks:        summary.LogChunks,
		LogBytes:         summary.LogBytes,
		ArtifactCount:    summary.ArtifactCount,
		ArtifactBytes:    summary.ArtifactBytes,
		LatestLogAt:      optionalTime(summary.LatestLogAt),
		LatestArtifactAt: optionalTime(summary.LatestArtifactAt),
	}
}

func ToAutomationRunBlobSummaries(summaries []automationstore.AutomationRunBlobSummary) []dto.AutomationRunBlobSummary {
	if summaries == nil {
		return nil
	}
	out := make([]dto.AutomationRunBlobSummary, 0, len(summaries))
	for _, summary := range summaries {
		out = append(out, ToAutomationRunBlobSummary(summary))
	}
	return out
}

func ToAutomationSourceRepositories(repos []automationservice.AutomationSourceRepository) []dto.AutomationSourceRepository {
	if repos == nil {
		return nil
	}
	out := make([]dto.AutomationSourceRepository, 0, len(repos))
	for _, repo := range repos {
		out = append(out, dto.AutomationSourceRepository{ID: repo.ID, Owner: repo.Owner, Name: repo.Name, FullName: repo.FullName, DefaultBranch: repo.DefaultBranch, Private: repo.Private, HTMLURL: repo.HTMLURL})
	}
	return out
}

func ToAutomationSourceFile(file automationservice.AutomationSourceFile) dto.AutomationSourceFile {
	return dto.AutomationSourceFile{RepositoryOwner: file.RepositoryOwner, RepositoryName: file.RepositoryName, Path: file.Path, Name: file.Name, SHA: file.SHA, Size: file.Size, Ref: file.Ref, ContentID: file.ContentID, ContentVersionID: file.ContentVersionID}
}

func ToAutomationSourceFiles(files []automationservice.AutomationSourceFile) []dto.AutomationSourceFile {
	if files == nil {
		return nil
	}
	out := make([]dto.AutomationSourceFile, 0, len(files))
	for _, file := range files {
		out = append(out, ToAutomationSourceFile(file))
	}
	return out
}

func ToAutomationSourceContent(content automationservice.AutomationSourceContent) dto.AutomationSourceContent {
	return dto.AutomationSourceContent{AutomationSourceFile: ToAutomationSourceFile(content.AutomationSourceFile), Content: content.Content, Encoding: content.Encoding}
}

func ToAutomationSourceSaveResponse(result automationservice.AutomationSourceWriteResult) dto.AutomationSourceSaveResponse {
	return dto.AutomationSourceSaveResponse{
		Content: dto.AutomationSourceSavedContent{
			AutomationSourceFile: ToAutomationSourceFile(result.Content.AutomationSourceFile),
			Encoding:             result.Content.Encoding,
		},
		Commit: dto.AutomationSourceCommit{SHA: result.Commit.SHA, ContentVersionID: result.Commit.ContentVersionID, Message: result.Commit.Message},
	}
}

func ToAutomationSecret(secret automationservice.AutomationSecretMetadata) dto.AutomationSecret {
	return dto.AutomationSecret{
		WorkspaceID:     secret.WorkspaceID,
		Scope:           "repository",
		RepositoryOwner: secret.RepositoryOwner,
		RepositoryName:  secret.RepositoryName,
		Name:            secret.Name,
		CreatedBy:       secret.CreatedBy,
		UpdatedBy:       secret.UpdatedBy,
		CreatedAt:       secret.CreatedAt.UTC().Format(timeLayout),
		UpdatedAt:       secret.UpdatedAt.UTC().Format(timeLayout),
	}
}

func ToAutomationSecrets(secrets []automationservice.AutomationSecretMetadata) []dto.AutomationSecret {
	if len(secrets) == 0 {
		return nil
	}
	out := make([]dto.AutomationSecret, 0, len(secrets))
	for _, secret := range secrets {
		out = append(out, ToAutomationSecret(secret))
	}
	return out
}

func ToAutomationJobRun(jobRun automationstore.AutomationJobRun) dto.AutomationJobRun {
	return dto.AutomationJobRun{
		ID:            jobRun.ID,
		WorkspaceID:   jobRun.WorkspaceID,
		RunID:         jobRun.RunID,
		JobID:         jobRun.JobID,
		JobType:       jobRun.JobType,
		Status:        jobRun.Status,
		Attempt:       jobRun.Attempt,
		MaxAttempts:   jobRun.MaxAttempts,
		RunsOnJSON:    jobRun.RunsOnJSON,
		InputJSON:     jobRun.InputJSON,
		OutputJSON:    jobRun.OutputJSON,
		Error:         ToAutomationRunError(automationservice.ParseAutomationStructuredError(jobRun.ErrorJSON)),
		ErrorJSON:     jobRun.ErrorJSON,
		DependsOnJSON: jobRun.DependsOnJSON,
		LeaseOwner:    jobRun.LeaseOwner,
		LeaseUntil:    optionalTime(jobRun.LeaseUntil),
		ScheduledAt:   jobRun.ScheduledAt.UTC().Format(time.RFC3339Nano),
		StartedAt:     optionalTime(jobRun.StartedAt),
		CompletedAt:   optionalTime(jobRun.CompletedAt),
		CreatedAt:     jobRun.CreatedAt.UTC().Format(time.RFC3339Nano),
		UpdatedAt:     jobRun.UpdatedAt.UTC().Format(time.RFC3339Nano),
	}
}

func ToAutomationJobRuns(jobRuns []automationstore.AutomationJobRun) []dto.AutomationJobRun {
	if jobRuns == nil {
		return nil
	}
	out := make([]dto.AutomationJobRun, 0, len(jobRuns))
	for _, jobRun := range jobRuns {
		out = append(out, ToAutomationJobRun(jobRun))
	}
	return out
}

func ToAutomationEvent(event automationstore.AutomationEvent) dto.AutomationEvent {
	return dto.AutomationEvent{
		ID:               event.ID,
		WorkspaceID:      event.WorkspaceID,
		RunID:            event.RunID,
		JobRunID:         event.JobRunID,
		EventType:        event.EventType,
		ActorPrincipalID: event.ActorPrincipalID,
		ResourceType:     event.ResourceType,
		ResourceID:       event.ResourceID,
		Summary:          event.Summary,
		MetadataJSON:     event.MetadataJSON,
		CreatedAt:        event.CreatedAt.UTC().Format(time.RFC3339Nano),
	}
}

func ToAutomationEvents(events []automationstore.AutomationEvent) []dto.AutomationEvent {
	if events == nil {
		return nil
	}
	out := make([]dto.AutomationEvent, 0, len(events))
	for _, event := range events {
		out = append(out, ToAutomationEvent(event))
	}
	return out
}

func ToAutomationLogChunk(chunk automationstore.AutomationLogChunk) dto.AutomationLogChunk {
	return dto.AutomationLogChunk{
		ID:          chunk.ID,
		WorkspaceID: chunk.WorkspaceID,
		RunID:       chunk.RunID,
		JobRunID:    chunk.JobRunID,
		Stream:      chunk.Stream,
		Sequence:    chunk.Sequence,
		SizeBytes:   chunk.SizeBytes,
		SHA256:      chunk.SHA256,
		ContentType: chunk.ContentType,
		CreatedAt:   chunk.CreatedAt.UTC().Format(time.RFC3339Nano),
	}
}

func ToAutomationLogChunks(chunks []automationstore.AutomationLogChunk) []dto.AutomationLogChunk {
	if chunks == nil {
		return nil
	}
	out := make([]dto.AutomationLogChunk, 0, len(chunks))
	for _, chunk := range chunks {
		out = append(out, ToAutomationLogChunk(chunk))
	}
	return out
}

func ToAutomationLogChunkContent(content automationservice.AutomationLogChunkContent) dto.AutomationLogChunkContent {
	return dto.AutomationLogChunkContent{AutomationLogChunk: ToAutomationLogChunk(content.Chunk), Content: content.Content}
}

func ToAutomationArtifact(artifact automationstore.AutomationArtifact) dto.AutomationArtifact {
	return dto.AutomationArtifact{
		ID:          artifact.ID,
		WorkspaceID: artifact.WorkspaceID,
		RunID:       artifact.RunID,
		JobRunID:    artifact.JobRunID,
		Name:        artifact.Name,
		SizeBytes:   artifact.SizeBytes,
		SHA256:      artifact.SHA256,
		ContentType: artifact.ContentType,
		ExpiresAt:   optionalTime(artifact.ExpiresAt),
		CreatedAt:   artifact.CreatedAt.UTC().Format(time.RFC3339Nano),
		UpdatedAt:   artifact.UpdatedAt.UTC().Format(time.RFC3339Nano),
	}
}

func ToAutomationArtifacts(artifacts []automationstore.AutomationArtifact) []dto.AutomationArtifact {
	if artifacts == nil {
		return nil
	}
	out := make([]dto.AutomationArtifact, 0, len(artifacts))
	for _, artifact := range artifacts {
		out = append(out, ToAutomationArtifact(artifact))
	}
	return out
}

func ToAutomationArtifactContent(content automationservice.AutomationArtifactContent) dto.AutomationArtifactContent {
	return dto.AutomationArtifactContent{AutomationArtifact: ToAutomationArtifact(content.Artifact), ContentBase64: base64.StdEncoding.EncodeToString(content.Content)}
}

func ToAutomationWait(wait automationstore.AutomationWait) dto.AutomationWait {
	return dto.AutomationWait{
		ID:              wait.ID,
		WorkspaceID:     wait.WorkspaceID,
		RunID:           wait.RunID,
		JobRunID:        wait.JobRunID,
		WaitKind:        wait.WaitKind,
		Status:          wait.Status,
		ResourceType:    wait.ResourceType,
		ResourceID:      wait.ResourceID,
		ResumeEventType: wait.ResumeEventType,
		InputJSON:       wait.InputJSON,
		ResultJSON:      wait.ResultJSON,
		CreatedBy:       wait.CreatedBy,
		ResolvedBy:      wait.ResolvedBy,
		CreatedAt:       wait.CreatedAt.UTC().Format(time.RFC3339Nano),
		UpdatedAt:       wait.UpdatedAt.UTC().Format(time.RFC3339Nano),
		ResolvedAt:      optionalTime(wait.ResolvedAt),
		CanceledAt:      optionalTime(wait.CanceledAt),
	}
}

func ToAutomationWaits(waits []automationstore.AutomationWait) []dto.AutomationWait {
	if waits == nil {
		return nil
	}
	out := make([]dto.AutomationWait, 0, len(waits))
	for _, wait := range waits {
		out = append(out, ToAutomationWait(wait))
	}
	return out
}

func ToAutomationRunnerRegistration(registration automationservice.AutomationRunnerRegistration) dto.AutomationRunnerRegistration {
	return dto.AutomationRunnerRegistration{
		RunnerID:          registration.RunnerID,
		Name:              registration.Name,
		RunnerGroup:       registration.RunnerGroup,
		Labels:            registration.Labels,
		RegistrationToken: registration.RegistrationToken,
		TokenExpiresAt:    registration.TokenExpiresAt.UTC().Format(time.RFC3339Nano),
		RegisteredAt:      registration.RegisteredAt.UTC().Format(time.RFC3339Nano),
	}
}

func ToAutomationRunnerSession(session automationservice.AutomationRunnerSession) dto.AutomationRunnerSession {
	return dto.AutomationRunnerSession{
		RunnerID:     session.RunnerID,
		SessionID:    session.SessionID,
		LeaseOwner:   session.LeaseOwner,
		SessionToken: session.SessionToken,
		HeartbeatAt:  session.HeartbeatAt.UTC().Format(time.RFC3339Nano),
		ExpiresAt:    session.ExpiresAt.UTC().Format(time.RFC3339Nano),
		CreatedAt:    session.CreatedAt.UTC().Format(time.RFC3339Nano),
	}
}

func ToAutomationRunnerHeartbeat(heartbeat automationservice.AutomationRunnerHeartbeat) dto.AutomationRunnerHeartbeat {
	return dto.AutomationRunnerHeartbeat{
		RunnerID:        heartbeat.RunnerID,
		SessionID:       heartbeat.SessionID,
		LeaseOwner:      heartbeat.LeaseOwner,
		HeartbeatAt:     heartbeat.HeartbeatAt.UTC().Format(time.RFC3339Nano),
		ExpiresAt:       heartbeat.ExpiresAt.UTC().Format(time.RFC3339Nano),
		CancelRequested: heartbeat.CancelRequested,
	}
}

func ToAutomationRunnerJobLease(lease automationservice.AutomationRunnerJobLease) dto.AutomationRunnerJobLease {
	return dto.AutomationRunnerJobLease{
		QueueJobID: lease.QueueJobID,
		LeaseOwner: lease.LeaseOwner,
		LeaseUntil: optionalTime(lease.LeaseUntil),
	}
}

func ToAutomationRunnerJobs(jobs []automationservice.AutomationRunnerJob) []dto.AutomationRunnerJob {
	if jobs == nil {
		return nil
	}
	out := make([]dto.AutomationRunnerJob, 0, len(jobs))
	for _, job := range jobs {
		out = append(out, ToAutomationRunnerJob(job))
	}
	return out
}

func ToAutomationRunnerJob(job automationservice.AutomationRunnerJob) dto.AutomationRunnerJob {
	return dto.AutomationRunnerJob{
		QueueJobID:         job.QueueJobID,
		WorkspaceID:        job.WorkspaceID,
		RunID:              job.RunID,
		JobRunID:           job.JobRunID,
		JobID:              job.JobID,
		QueueName:          job.QueueName,
		Status:             job.Status,
		Attempt:            job.Attempt,
		MaxAttempts:        job.MaxAttempts,
		LeaseOwner:         job.LeaseOwner,
		LeaseUntil:         optionalTime(job.LeaseUntil),
		RepositoryOwner:    job.RepositoryOwner,
		RepositoryName:     job.RepositoryName,
		RepositoryFullName: job.RepositoryFullName,
		DefinitionPath:     job.DefinitionPath,
		GitRef:             job.GitRef,
		CommitSHA:          job.CommitSHA,
		ContentID:          job.ContentID,
		ContentVersionID:   job.ContentVersionID,
		AutomationName:     job.AutomationName,
		RunsOnJSON:         job.RunsOnJSON,
		RunnerGroup:        job.RunnerGroup,
		Labels:             job.Labels,
		InputJSON:          job.InputJSON,
		DependsOnJSON:      job.DependsOnJSON,
		TriggerContextJSON: job.TriggerContextJSON,
	}
}

func ToAutomationValidationReport(report dsl.ValidationReport) dto.AutomationValidationReport {
	out := dto.AutomationValidationReport{Valid: report.Valid}
	if len(report.Errors) > 0 {
		out.Errors = make([]dto.AutomationValidationError, 0, len(report.Errors))
		for _, err := range report.Errors {
			out.Errors = append(out.Errors, dto.AutomationValidationError{Code: err.Code, Path: err.Path, Message: err.Message})
		}
	}
	return out
}

func ToAutomationRunError(err *automationservice.AutomationStructuredError) *automationservice.AutomationStructuredError {
	if err == nil {
		return nil
	}
	return &automationservice.AutomationStructuredError{
		Code:       err.Code,
		Message:    err.Message,
		Retryable:  err.Retryable,
		UserAction: err.UserAction,
		Details:    err.Details,
	}
}

func ToAutomationTriggerContext(context *automationservice.AutomationTriggerContext) *dto.AutomationTriggerContext {
	if context == nil {
		return nil
	}
	return &dto.AutomationTriggerContext{
		TriggerKind:          context.TriggerKind,
		RepositoryFullName:   context.RepositoryFullName,
		Ref:                  context.Ref,
		Branch:               context.Branch,
		BeforeSHA:            context.BeforeSHA,
		AfterSHA:             context.AfterSHA,
		ActorRef:             context.ActorRef,
		ActorDisplay:         context.ActorDisplay,
		EventID:              context.EventID,
		ScheduleDefinitionID: context.ScheduleDefinitionID,
		AutomationRunID:      context.AutomationRunID,
		RawPayloadRef:        context.RawPayloadRef,
	}
}

func ToAutomationRunnerReadiness(readiness automationservice.AutomationRunnerReadiness) automationservice.AutomationRunnerReadiness {
	out := automationservice.AutomationRunnerReadiness{
		RequiredLabels:      append([]string(nil), readiness.RequiredLabels...),
		RequiredGroup:       readiness.RequiredGroup,
		MatchingRunnerCount: readiness.MatchingRunnerCount,
		OnlineRunnerCount:   readiness.OnlineRunnerCount,
		OfflineRunnerCount:  readiness.OfflineRunnerCount,
		IdleRunnerCount:     readiness.IdleRunnerCount,
		BusyRunnerCount:     readiness.BusyRunnerCount,
		QueueDepth:          readiness.QueueDepth,
		ReasonCode:          readiness.ReasonCode,
		Runnable:            readiness.Runnable,
	}
	if len(readiness.Pools) > 0 {
		out.Pools = make([]automationservice.AutomationRunnerReadinessPool, 0, len(readiness.Pools))
		for _, pool := range readiness.Pools {
			out.Pools = append(out.Pools, automationservice.AutomationRunnerReadinessPool{
				RequiredLabels:      append([]string(nil), pool.RequiredLabels...),
				RequiredGroup:       pool.RequiredGroup,
				MatchingRunnerCount: pool.MatchingRunnerCount,
				OnlineRunnerCount:   pool.OnlineRunnerCount,
				OfflineRunnerCount:  pool.OfflineRunnerCount,
				IdleRunnerCount:     pool.IdleRunnerCount,
				BusyRunnerCount:     pool.BusyRunnerCount,
				QueueDepth:          pool.QueueDepth,
				ReasonCode:          pool.ReasonCode,
				Runnable:            pool.Runnable,
			})
		}
	}
	return out
}

func ToAutomationPinnedRepo(pin automationstore.AutomationPinnedRepo) dto.AutomationPinnedRepo {
	return dto.AutomationPinnedRepo{
		WorkspaceID: pin.WorkspaceID,
		PrincipalID: pin.PrincipalID,
		Owner:       pin.Owner,
		Repo:        pin.Repo,
		FullName:    pin.Owner + "/" + pin.Repo,
		CreatedAt:   pin.CreatedAt.UTC().Format(timeLayout),
		UpdatedAt:   pin.UpdatedAt.UTC().Format(timeLayout),
	}
}

func ToAutomationPinnedRepos(pins []automationstore.AutomationPinnedRepo) []dto.AutomationPinnedRepo {
	if len(pins) == 0 {
		return nil
	}
	out := make([]dto.AutomationPinnedRepo, 0, len(pins))
	for _, pin := range pins {
		out = append(out, ToAutomationPinnedRepo(pin))
	}
	return out
}
