package automation

import (
	"net/http"
	"strconv"
	"strings"

	"github.com/tidbcloud/AgentTeam/internal/api/dto"
	"github.com/tidbcloud/AgentTeam/internal/api/dto/converter"
	"github.com/tidbcloud/AgentTeam/internal/api/meshxapi"
	automationservice "github.com/tidbcloud/AgentTeam/internal/service/automation"
)

func (h *AutomationsHandler) StartRepositoryAutomationRun(r meshxapi.AuthenticatedJSON[dto.AutomationRepositoryRunRequest]) (meshxapi.Response, error) {
	if err := h.ensureService(); err != nil {
		return meshxapi.Response{}, err
	}
	owner := r.Request.PathValue("owner")
	repo := r.Request.PathValue("repo")
	run, err := h.service.StartRepositoryAutomationRun(r.Context(), r.WorkspaceID(), r.Subject(), r.Body.Input(owner, repo, h.authorizationHeader(r.Request)))
	if err != nil {
		return meshxapi.Response{}, err
	}
	return meshxapi.Created(converter.ToAutomationRun(run)), nil
}

func (h *AutomationsHandler) StartRepositoryAutomationRunsForPushEvent(r meshxapi.AuthenticatedJSON[dto.AutomationRepositoryPushEventRequest]) (meshxapi.Response, error) {
	if err := h.ensureService(); err != nil {
		return meshxapi.Response{}, err
	}
	owner := r.Request.PathValue("owner")
	repo := r.Request.PathValue("repo")
	runs, err := h.service.StartRepositoryAutomationRunsForPushEvent(r.Context(), r.WorkspaceID(), r.Subject(), r.Body.Input(owner, repo, h.authorizationHeader(r.Request)))
	if err != nil {
		return meshxapi.Response{}, err
	}
	return meshxapi.Created(dto.AutomationRepositoryPushEventResponse{Runs: converter.ToAutomationRuns(runs)}), nil
}

func (h *AutomationsHandler) ListAutomationRepositories(r meshxapi.AuthenticatedRequest) (meshxapi.Response, error) {
	if err := h.ensureService(); err != nil {
		return meshxapi.Response{}, err
	}
	page, perPage := paginationWithMax(r.Request, automationservice.MaxAutomationRepositorySourceCandidates)
	repos, err := h.service.ListAutomationRepositories(r.Context(), r.WorkspaceID(), r.Subject(), h.authorizationHeader(r.Request), page, perPage)
	if err != nil {
		return meshxapi.Response{}, err
	}
	return meshxapi.OK(converter.ToAutomationSourceRepositories(repos)), nil
}

func (h *AutomationsHandler) ListRepositoryAutomationFiles(r meshxapi.AuthenticatedRequest) (meshxapi.Response, error) {
	if err := h.ensureService(); err != nil {
		return meshxapi.Response{}, err
	}
	files, err := h.service.ListRepositoryAutomationFiles(r.Context(), r.WorkspaceID(), r.Subject(), h.automationSourceQuery(r.Request, ""))
	if err != nil {
		return meshxapi.Response{}, err
	}
	return meshxapi.OK(converter.ToAutomationSourceFiles(files)), nil
}

func (h *AutomationsHandler) GetRepositoryAutomationFile(r meshxapi.AuthenticatedRequest) (meshxapi.Response, error) {
	if err := h.ensureService(); err != nil {
		return meshxapi.Response{}, err
	}
	content, err := h.service.GetRepositoryAutomationFile(r.Context(), r.WorkspaceID(), r.Subject(), h.automationSourceQuery(r.Request, r.Request.PathValue("definitionPath")))
	if err != nil {
		return meshxapi.Response{}, err
	}
	return meshxapi.OK(converter.ToAutomationSourceContent(content)), nil
}

func (h *AutomationsHandler) SaveRepositoryAutomationFile(r meshxapi.AuthenticatedJSON[dto.AutomationSourceSaveRequest]) (meshxapi.Response, error) {
	if err := h.ensureService(); err != nil {
		return meshxapi.Response{}, err
	}
	result, err := h.service.SaveRepositoryAutomationFile(r.Context(), r.WorkspaceID(), r.Subject(), r.Body.Input(r.Request.PathValue("owner"), r.Request.PathValue("repo"), r.Request.PathValue("definitionPath"), h.authorizationHeader(r.Request)))
	if err != nil {
		return meshxapi.Response{}, err
	}
	if result.Created {
		return meshxapi.Created(converter.ToAutomationSourceSaveResponse(result)), nil
	}
	return meshxapi.OK(converter.ToAutomationSourceSaveResponse(result)), nil
}

// DeleteRepositoryAutomationFile handles DELETE /api/v1/automations/repositories/{owner}/{repo}/definitions/{definitionPath}.
// It removes one AGS-backed automation source file using contents API sha
// precondition semantics.
func (h *AutomationsHandler) DeleteRepositoryAutomationFile(r meshxapi.AuthenticatedJSON[dto.AutomationSourceDeleteRequest]) (meshxapi.Response, error) {
	if err := h.ensureService(); err != nil {
		return meshxapi.Response{}, err
	}
	if err := h.service.DeleteRepositoryAutomationFile(r.Context(), r.WorkspaceID(), r.Subject(), r.Body.Input(r.Request.PathValue("owner"), r.Request.PathValue("repo"), r.Request.PathValue("definitionPath"), h.authorizationHeader(r.Request))); err != nil {
		return meshxapi.Response{}, err
	}
	return meshxapi.NoContent(), nil
}

func (h *AutomationsHandler) ValidateRepositoryAutomationFile(r meshxapi.AuthenticatedJSON[dto.AutomationSourceValidateRequest]) (meshxapi.Response, error) {
	if err := h.ensureService(); err != nil {
		return meshxapi.Response{}, err
	}
	report, err := h.service.ValidateRepositoryAutomationContent(r.Context(), r.WorkspaceID(), r.Subject(), r.Body.Input(r.Request.PathValue("owner"), r.Request.PathValue("repo"), r.Request.PathValue("definitionPath")))
	if err != nil {
		return meshxapi.Response{}, err
	}
	return meshxapi.OK(converter.ToAutomationValidationReport(report)), nil
}

func (h *AutomationsHandler) PreflightRepositoryAutomationFile(r meshxapi.AuthenticatedJSON[dto.AutomationSourcePreflightRequest]) (meshxapi.Response, error) {
	if err := h.ensureService(); err != nil {
		return meshxapi.Response{}, err
	}
	report, err := h.service.PreflightRepositoryAutomation(r.Context(), r.WorkspaceID(), r.Subject(), r.Body.Input(r.Request.PathValue("owner"), r.Request.PathValue("repo"), r.Request.PathValue("definitionPath"), h.authorizationHeader(r.Request)))
	if err != nil {
		return meshxapi.Response{}, err
	}
	return meshxapi.OK(automationservice.AutomationPreflightReport(report)), nil
}

func (h *AutomationsHandler) GetRepositoryAutomationRunnerReadiness(r meshxapi.AuthenticatedRequest) (meshxapi.Response, error) {
	if err := h.ensureService(); err != nil {
		return meshxapi.Response{}, err
	}
	readiness, err := h.service.GetRepositoryAutomationRunnerReadiness(r.Context(), r.WorkspaceID(), r.Subject(), h.automationSourceQuery(r.Request, r.Request.PathValue("definitionPath")))
	if err != nil {
		return meshxapi.Response{}, err
	}
	return meshxapi.OK(converter.ToAutomationRunnerReadiness(readiness)), nil
}

// ListAutomationSecrets handles GET /api/v1/automations/repositories/{owner}/{repo}/secrets.
// It returns metadata for repo-scoped automation secrets without secret material.
func (h *AutomationsHandler) ListAutomationSecrets(r meshxapi.AuthenticatedRequest) (meshxapi.Response, error) {
	if err := h.ensureService(); err != nil {
		return meshxapi.Response{}, err
	}
	page, perPage := paginationWithMax(r.Request, maxPerPage)
	result, err := h.service.ListAutomationSecrets(r.Context(), r.WorkspaceID(), r.Subject(), r.Request.PathValue("owner"), r.Request.PathValue("repo"), page, perPage)
	if err != nil {
		return meshxapi.Response{}, err
	}
	return meshxapi.OK(dto.AutomationSecretList{Items: converter.ToAutomationSecrets(result.Secrets), HasMore: result.HasMore}), nil
}

// UpsertAutomationSecret handles PUT /api/v1/automations/repositories/{owner}/{repo}/secrets/{name}.
// It stores the submitted plaintext as encrypted secret material and returns metadata only.
func (h *AutomationsHandler) UpsertAutomationSecret(r meshxapi.AuthenticatedJSON[dto.AutomationSecretPutRequest]) (meshxapi.Response, error) {
	if err := h.ensureService(); err != nil {
		return meshxapi.Response{}, err
	}
	secret, err := h.service.UpsertAutomationSecret(r.Context(), r.WorkspaceID(), r.Subject(), r.Body.Input(r.Request.PathValue("owner"), r.Request.PathValue("repo"), r.Request.PathValue("name")))
	if err != nil {
		return meshxapi.Response{}, err
	}
	return meshxapi.OK(converter.ToAutomationSecret(secret)), nil
}

// DeleteAutomationSecret handles DELETE /api/v1/automations/repositories/{owner}/{repo}/secrets/{name}.
// It removes one repo-scoped automation secret without reading plaintext.
func (h *AutomationsHandler) DeleteAutomationSecret(r meshxapi.AuthenticatedRequest) (meshxapi.Response, error) {
	if err := h.ensureService(); err != nil {
		return meshxapi.Response{}, err
	}
	if err := h.service.DeleteAutomationSecret(r.Context(), r.WorkspaceID(), r.Subject(), r.Request.PathValue("owner"), r.Request.PathValue("repo"), r.Request.PathValue("name")); err != nil {
		return meshxapi.Response{}, err
	}
	return meshxapi.NoContent(), nil
}

func (h *AutomationsHandler) ListRuns(r meshxapi.AuthenticatedRequest) (meshxapi.Response, error) {
	if err := h.ensureService(); err != nil {
		return meshxapi.Response{}, err
	}
	page, perPage := paginationWithMax(r.Request, maxPerPage)
	query := r.Request.URL.Query()
	result, err := h.service.ListAutomationRuns(r.Context(), r.WorkspaceID(), r.Subject(), strings.TrimSpace(query.Get("status")), strings.TrimSpace(query.Get("repository")), strings.TrimSpace(query.Get("q")), page, perPage)
	if err != nil {
		return meshxapi.Response{}, err
	}
	return meshxapi.OK(dto.AutomationRunList{
		Items:   converter.ToAutomationRuns(result.Runs),
		HasMore: result.HasMore,
	}), nil
}

func (h *AutomationsHandler) GetDashboard(r meshxapi.AuthenticatedRequest) (meshxapi.Response, error) {
	if err := h.ensureService(); err != nil {
		return meshxapi.Response{}, err
	}
	dashboard, err := h.service.GetAutomationDashboard(r.Context(), r.WorkspaceID(), r.Subject())
	if err != nil {
		return meshxapi.Response{}, err
	}
	return meshxapi.OK(converter.ToAutomationDashboard(dashboard)), nil
}

func (h *AutomationsHandler) GetRun(r meshxapi.AuthenticatedRequest) (meshxapi.Response, error) {
	if err := h.ensureService(); err != nil {
		return meshxapi.Response{}, err
	}
	run, err := h.service.GetAutomationRun(r.Context(), r.WorkspaceID(), r.Subject(), r.Request.PathValue("run_id"))
	if err != nil {
		return meshxapi.Response{}, err
	}
	return meshxapi.OK(converter.ToAutomationRun(run)), nil
}

func (h *AutomationsHandler) GetRunOverview(r meshxapi.AuthenticatedRequest) (meshxapi.Response, error) {
	if err := h.ensureService(); err != nil {
		return meshxapi.Response{}, err
	}
	overview, err := h.service.GetAutomationRunOverview(r.Context(), r.WorkspaceID(), r.Subject(), r.Request.PathValue("run_id"))
	if err != nil {
		return meshxapi.Response{}, err
	}
	return meshxapi.OK(converter.ToAutomationRunOverview(overview)), nil
}

func (h *AutomationsHandler) ListRunJobRuns(r meshxapi.AuthenticatedRequest) (meshxapi.Response, error) {
	if err := h.ensureService(); err != nil {
		return meshxapi.Response{}, err
	}
	jobRuns, err := h.service.ListAutomationJobRuns(r.Context(), r.WorkspaceID(), r.Subject(), r.Request.PathValue("run_id"))
	if err != nil {
		return meshxapi.Response{}, err
	}
	return meshxapi.OK(converter.ToAutomationJobRuns(jobRuns)), nil
}

func (h *AutomationsHandler) ListRunEvents(r meshxapi.AuthenticatedRequest) (meshxapi.Response, error) {
	if err := h.ensureService(); err != nil {
		return meshxapi.Response{}, err
	}
	page, perPage := paginationWithMax(r.Request, maxPerPage)
	events, err := h.service.ListAutomationEvents(r.Context(), r.WorkspaceID(), r.Subject(), r.Request.PathValue("run_id"), page, perPage)
	if err != nil {
		return meshxapi.Response{}, err
	}
	return meshxapi.OK(converter.ToAutomationEvents(events)), nil
}

// ListRunLogChunks handles GET /api/v1/automations/runs/{run_id}/logs.
// It returns paginated log chunk metadata scoped to the authenticated run.
func (h *AutomationsHandler) ListRunLogChunks(r meshxapi.AuthenticatedRequest) (meshxapi.Response, error) {
	if err := h.ensureService(); err != nil {
		return meshxapi.Response{}, err
	}
	page, perPage := paginationWithMax(r.Request, maxPerPage)
	query := r.Request.URL.Query()
	chunks, err := h.service.ListAutomationLogChunks(r.Context(), r.WorkspaceID(), r.Subject(), r.Request.PathValue("run_id"), strings.TrimSpace(query.Get("jobRunId")), strings.TrimSpace(query.Get("stream")), page, perPage)
	if err != nil {
		return meshxapi.Response{}, err
	}
	return meshxapi.OK(converter.ToAutomationLogChunks(chunks)), nil
}

// ReadRunLogChunk handles GET /api/v1/automations/runs/{run_id}/jobs/{job_run_id}/logs/{stream}/{sequence}.
// It returns one scoped log chunk with its text payload.
func (h *AutomationsHandler) ReadRunLogChunk(r meshxapi.AuthenticatedRequest) (meshxapi.Response, error) {
	if err := h.ensureService(); err != nil {
		return meshxapi.Response{}, err
	}
	sequence, err := pathLogSequence(r.Request.PathValue("sequence"))
	if err != nil {
		return meshxapi.Response{}, err
	}
	content, err := h.service.ReadAutomationLogChunkContent(r.Context(), r.WorkspaceID(), r.Subject(), r.Request.PathValue("run_id"), r.Request.PathValue("job_run_id"), r.Request.PathValue("stream"), sequence)
	if err != nil {
		return meshxapi.Response{}, err
	}
	return meshxapi.OK(converter.ToAutomationLogChunkContent(content)), nil
}

// ListRunArtifacts handles GET /api/v1/automations/runs/{run_id}/artifacts.
// It returns paginated artifact metadata scoped to the authenticated run.
func (h *AutomationsHandler) ListRunArtifacts(r meshxapi.AuthenticatedRequest) (meshxapi.Response, error) {
	if err := h.ensureService(); err != nil {
		return meshxapi.Response{}, err
	}
	page, perPage := paginationWithMax(r.Request, maxPerPage)
	artifacts, err := h.service.ListAutomationArtifacts(r.Context(), r.WorkspaceID(), r.Subject(), r.Request.PathValue("run_id"), strings.TrimSpace(r.Request.URL.Query().Get("jobRunId")), page, perPage)
	if err != nil {
		return meshxapi.Response{}, err
	}
	return meshxapi.OK(converter.ToAutomationArtifacts(artifacts)), nil
}

// ReadRunArtifact handles GET /api/v1/automations/runs/{run_id}/artifacts/{artifact_id}.
// It returns one scoped artifact with its base64-encoded payload.
func (h *AutomationsHandler) ReadRunArtifact(r meshxapi.AuthenticatedRequest) (meshxapi.Response, error) {
	if err := h.ensureService(); err != nil {
		return meshxapi.Response{}, err
	}
	content, err := h.service.ReadAutomationArtifactContent(r.Context(), r.WorkspaceID(), r.Subject(), r.Request.PathValue("run_id"), r.Request.PathValue("artifact_id"))
	if err != nil {
		return meshxapi.Response{}, err
	}
	return meshxapi.OK(converter.ToAutomationArtifactContent(content)), nil
}

func (h *AutomationsHandler) ListRunWaits(r meshxapi.AuthenticatedRequest) (meshxapi.Response, error) {
	if err := h.ensureService(); err != nil {
		return meshxapi.Response{}, err
	}
	waits, err := h.service.ListAutomationWaits(r.Context(), r.WorkspaceID(), r.Subject(), r.Request.PathValue("run_id"), strings.TrimSpace(r.Request.URL.Query().Get("status")))
	if err != nil {
		return meshxapi.Response{}, err
	}
	return meshxapi.OK(converter.ToAutomationWaits(waits)), nil
}

func (h *AutomationsHandler) ResumeRunWait(r meshxapi.AuthenticatedJSON[dto.AutomationWaitResumeRequest]) (meshxapi.Response, error) {
	if err := h.ensureService(); err != nil {
		return meshxapi.Response{}, err
	}
	wait, err := h.service.ResumeAutomationWait(r.Context(), r.WorkspaceID(), r.Subject(), r.Request.PathValue("run_id"), r.Request.PathValue("wait_id"), r.Body.ResultJSON)
	if err != nil {
		return meshxapi.Response{}, err
	}
	return meshxapi.OK(converter.ToAutomationWait(wait)), nil
}

func (h *AutomationsHandler) CompleteRunTaskWait(r meshxapi.AuthenticatedJSON[dto.AutomationWaitTaskCompleteRequest]) (meshxapi.Response, error) {
	if err := h.ensureService(); err != nil {
		return meshxapi.Response{}, err
	}
	wait, err := h.service.CompleteAutomationTaskWait(r.Context(), r.WorkspaceID(), r.Subject(), r.Request.PathValue("run_id"), r.Request.PathValue("wait_id"), automationservice.AutomationWaitTaskCompleteMutation{TaskID: r.Body.TaskID, TaskNumber: r.Body.TaskNumber, Status: r.Body.Status, ResultJSON: r.Body.ResultJSON})
	if err != nil {
		return meshxapi.Response{}, err
	}
	return meshxapi.OK(converter.ToAutomationWait(wait)), nil
}

func (h *AutomationsHandler) SignalRunEventWait(r meshxapi.AuthenticatedJSON[dto.AutomationWaitEventSignalRequest]) (meshxapi.Response, error) {
	if err := h.ensureService(); err != nil {
		return meshxapi.Response{}, err
	}
	wait, err := h.service.SignalAutomationEventWait(r.Context(), r.WorkspaceID(), r.Subject(), r.Request.PathValue("run_id"), r.Request.PathValue("wait_id"), automationservice.AutomationWaitEventSignalMutation{EventType: r.Body.EventType, ResourceType: r.Body.ResourceType, ResourceID: r.Body.ResourceID, PayloadJSON: r.Body.PayloadJSON, ResultJSON: r.Body.ResultJSON})
	if err != nil {
		return meshxapi.Response{}, err
	}
	return meshxapi.OK(converter.ToAutomationWait(wait)), nil
}

func (h *AutomationsHandler) DeliverWaitSignal(r meshxapi.AuthenticatedJSON[dto.AutomationWaitSignalDeliverRequest]) (meshxapi.Response, error) {
	if err := h.ensureService(); err != nil {
		return meshxapi.Response{}, err
	}
	wait, err := h.service.DeliverAutomationWaitSignal(r.Context(), r.WorkspaceID(), r.Subject(), r.Body.Input())
	if err != nil {
		return meshxapi.Response{}, err
	}
	return meshxapi.OK(converter.ToAutomationWait(wait)), nil
}

func (h *AutomationsHandler) CancelRun(r meshxapi.AuthenticatedJSON[dto.AutomationRunCancelRequest]) (meshxapi.Response, error) {
	if err := h.ensureService(); err != nil {
		return meshxapi.Response{}, err
	}
	run, err := h.service.CancelAutomationRun(r.Context(), r.WorkspaceID(), r.Subject(), r.Request.PathValue("run_id"), r.Body.Reason)
	if err != nil {
		return meshxapi.Response{}, err
	}
	return meshxapi.OK(converter.ToAutomationRun(run)), nil
}

func (h *AutomationsHandler) RetryRun(r meshxapi.AuthenticatedRequest) (meshxapi.Response, error) {
	if err := h.ensureService(); err != nil {
		return meshxapi.Response{}, err
	}
	run, err := h.service.RetryAutomationRun(r.Context(), r.WorkspaceID(), r.Subject(), r.Request.PathValue("run_id"), h.authorizationHeader(r.Request))
	if err != nil {
		return meshxapi.Response{}, err
	}
	return meshxapi.Created(converter.ToAutomationRun(run)), nil
}

func (h *AutomationsHandler) RegisterRunner(r meshxapi.AuthenticatedJSON[dto.AutomationRunnerRegisterRequest]) (meshxapi.Response, error) {
	if err := h.ensureService(); err != nil {
		return meshxapi.Response{}, err
	}
	registration, err := h.service.RegisterAutomationRunner(r.Context(), r.WorkspaceID(), r.Subject(), r.Body.Input(""))
	if err != nil {
		return meshxapi.Response{}, err
	}
	return meshxapi.Created(converter.ToAutomationRunnerRegistration(registration)), nil
}

// CreateRunnerSession handles POST /api/v1/automations/runners/{runner_id}/sessions.
// It consumes a one-time runner registration token and returns a session token.
func (h *AutomationsHandler) CreateRunnerSession(r runnerJSON[dto.AutomationRunnerSessionRequest]) (meshxapi.Response, error) {
	if err := h.ensureService(); err != nil {
		return meshxapi.Response{}, err
	}
	session, err := h.service.CreateAutomationRunnerSession(r.Context(), r.WorkspaceID(), r.RunnerToken(), r.Body.Input(r.Request.PathValue("runner_id")))
	if err != nil {
		return meshxapi.Response{}, err
	}
	return meshxapi.Created(converter.ToAutomationRunnerSession(session)), nil
}

// HeartbeatRunnerSession handles POST /api/v1/automations/runners/{runner_id}/sessions/{session_id}/heartbeat.
// It renews an active runner session and reports whether the current job should cancel.
func (h *AutomationsHandler) HeartbeatRunnerSession(r runnerJSON[dto.AutomationRunnerHeartbeatRequest]) (meshxapi.Response, error) {
	if err := h.ensureService(); err != nil {
		return meshxapi.Response{}, err
	}
	heartbeat, err := h.service.HeartbeatAutomationRunnerSession(r.Context(), r.WorkspaceID(), r.RunnerToken(), r.Body.Input(r.Request.PathValue("runner_id"), r.Request.PathValue("session_id")))
	if err != nil {
		return meshxapi.Response{}, err
	}
	return meshxapi.OK(converter.ToAutomationRunnerHeartbeat(heartbeat)), nil
}

// AcquireRunnerJobs handles GET /api/v1/automations/runners/{runner_id}/jobs:acquire.
// It claims ready jobs for an active runner session identified by a session token.
func (h *AutomationsHandler) AcquireRunnerJobs(r runnerRequest) (meshxapi.Response, error) {
	if err := h.ensureService(); err != nil {
		return meshxapi.Response{}, err
	}
	input, err := runnerAcquireRequest(r.Request)
	if err != nil {
		return meshxapi.Response{}, err
	}
	input.RunnerID = r.Request.PathValue("runner_id")
	jobs, err := h.service.AcquireAutomationRunnerJobs(r.Context(), r.WorkspaceID(), r.RunnerToken(), input.Input())
	if err != nil {
		return meshxapi.Response{}, err
	}
	return meshxapi.OK(dto.AutomationRunnerAcquireResponse{Jobs: converter.ToAutomationRunnerJobs(jobs)}), nil
}

func (h *AutomationsHandler) AcceptRunnerJob(r runnerJSON[dto.AutomationRunnerJobAcceptedRequest]) (meshxapi.Response, error) {
	if err := h.ensureService(); err != nil {
		return meshxapi.Response{}, err
	}
	queueJobID, err := pathQueueJobID(r.Request.PathValue("job_id"))
	if err != nil {
		return meshxapi.Response{}, err
	}
	jobRun, err := h.service.AcceptAutomationRunnerJob(r.Context(), r.WorkspaceID(), r.RunnerToken(), r.Body.Input(r.Request.PathValue("runner_id"), queueJobID))
	if err != nil {
		return meshxapi.Response{}, err
	}
	return meshxapi.OK(converter.ToAutomationJobRun(jobRun)), nil
}

func (h *AutomationsHandler) CompleteRunnerJob(r runnerJSON[dto.AutomationRunnerJobCompletedRequest]) (meshxapi.Response, error) {
	if err := h.ensureService(); err != nil {
		return meshxapi.Response{}, err
	}
	queueJobID, err := pathQueueJobID(r.Request.PathValue("job_id"))
	if err != nil {
		return meshxapi.Response{}, err
	}
	jobRun, err := h.service.CompleteAutomationRunnerJob(r.Context(), r.WorkspaceID(), r.RunnerToken(), r.Body.Input(r.Request.PathValue("runner_id"), queueJobID))
	if err != nil {
		return meshxapi.Response{}, err
	}
	return meshxapi.OK(converter.ToAutomationJobRun(jobRun)), nil
}

func (h *AutomationsHandler) FailRunnerJob(r runnerJSON[dto.AutomationRunnerJobFailedRequest]) (meshxapi.Response, error) {
	if err := h.ensureService(); err != nil {
		return meshxapi.Response{}, err
	}
	queueJobID, err := pathQueueJobID(r.Request.PathValue("job_id"))
	if err != nil {
		return meshxapi.Response{}, err
	}
	jobRun, err := h.service.FailAutomationRunnerJob(r.Context(), r.WorkspaceID(), r.RunnerToken(), r.Body.Input(r.Request.PathValue("runner_id"), queueJobID))
	if err != nil {
		return meshxapi.Response{}, err
	}
	return meshxapi.OK(converter.ToAutomationJobRun(jobRun)), nil
}

// RenewRunnerJob handles POST /api/v1/automations/runners/{runner_id}/jobs/{job_id}/renew.
// It extends a single active job lease owned by the runner session.
func (h *AutomationsHandler) RenewRunnerJob(r runnerJSON[dto.AutomationRunnerJobRenewRequest]) (meshxapi.Response, error) {
	if err := h.ensureService(); err != nil {
		return meshxapi.Response{}, err
	}
	queueJobID, err := pathQueueJobID(r.Request.PathValue("job_id"))
	if err != nil {
		return meshxapi.Response{}, err
	}
	lease, err := h.service.RenewAutomationRunnerJob(r.Context(), r.WorkspaceID(), r.RunnerToken(), r.Body.Input(r.Request.PathValue("runner_id"), queueJobID))
	if err != nil {
		return meshxapi.Response{}, err
	}
	return meshxapi.OK(converter.ToAutomationRunnerJobLease(lease)), nil
}

func (h *AutomationsHandler) AppendRunnerJobLog(r runnerJSON[dto.AutomationRunnerJobLogRequest]) (meshxapi.Response, error) {
	if err := h.ensureService(); err != nil {
		return meshxapi.Response{}, err
	}
	queueJobID, err := pathQueueJobID(r.Request.PathValue("job_id"))
	if err != nil {
		return meshxapi.Response{}, err
	}
	event, err := h.service.AppendAutomationRunnerJobLog(r.Context(), r.WorkspaceID(), r.RunnerToken(), r.Body.Input(r.Request.PathValue("runner_id"), queueJobID))
	if err != nil {
		return meshxapi.Response{}, err
	}
	return meshxapi.Created(converter.ToAutomationEvent(event)), nil
}

func (h *AutomationsHandler) AppendRunnerJobArtifact(r runnerJSON[dto.AutomationRunnerJobArtifactRequest]) (meshxapi.Response, error) {
	if err := h.ensureService(); err != nil {
		return meshxapi.Response{}, err
	}
	queueJobID, err := pathQueueJobID(r.Request.PathValue("job_id"))
	if err != nil {
		return meshxapi.Response{}, err
	}
	event, err := h.service.AppendAutomationRunnerJobArtifact(r.Context(), r.WorkspaceID(), r.RunnerToken(), r.Body.Input(r.Request.PathValue("runner_id"), queueJobID))
	if err != nil {
		return meshxapi.Response{}, err
	}
	return meshxapi.Created(converter.ToAutomationEvent(event)), nil
}

func runnerAcquireRequest(r *http.Request) (dto.AutomationRunnerAcquireRequest, error) {
	query := r.URL.Query()
	input := dto.AutomationRunnerAcquireRequest{
		SessionID: strings.TrimSpace(query.Get("sessionId")),
		QueueName: strings.TrimSpace(query.Get("queueName")),
	}
	if parsed, ok, err := runnerAcquireInt(query.Get("limit"), "Automation runner acquire limit is invalid"); err != nil {
		return dto.AutomationRunnerAcquireRequest{}, err
	} else if ok {
		input.Limit = parsed
	}
	if parsed, ok, err := runnerAcquireInt(query.Get("leaseSeconds"), "Automation runner leaseSeconds is invalid"); err != nil {
		return dto.AutomationRunnerAcquireRequest{}, err
	} else if ok {
		input.LeaseSeconds = parsed
	}
	if parsed, ok, err := runnerAcquireInt(query.Get("waitSeconds"), "Automation runner waitSeconds is invalid"); err != nil {
		return dto.AutomationRunnerAcquireRequest{}, err
	} else if ok {
		input.WaitSeconds = parsed
	}
	if parsed, ok, err := runnerAcquireInt(query.Get("shardBucket"), "Automation runner shardBucket is invalid"); err != nil {
		return dto.AutomationRunnerAcquireRequest{}, err
	} else if ok {
		input.ShardBucket = &parsed
	}
	return input, nil
}

func runnerAcquireInt(raw string, message string) (int, bool, error) {
	raw = strings.TrimSpace(raw)
	if raw == "" {
		return 0, false, nil
	}
	parsed, err := strconv.Atoi(raw)
	if err != nil {
		return 0, false, meshxapi.NewHTTPError(http.StatusBadRequest, message)
	}
	return parsed, true, nil
}

func (h *AutomationsHandler) automationSourceQuery(r *http.Request, definitionPath string) automationservice.AutomationRepositoryAutomationQuery {
	return automationservice.AutomationRepositoryAutomationQuery{
		RepositoryOwner:     r.PathValue("owner"),
		RepositoryName:      r.PathValue("repo"),
		DefinitionPath:      definitionPath,
		GitRef:              strings.TrimSpace(r.URL.Query().Get("ref")),
		AuthorizationHeader: h.authorizationHeader(r),
	}
}

func (h *AutomationsHandler) authorizationHeader(r *http.Request) string {
	if r == nil {
		return ""
	}
	header := strings.TrimSpace(h.authHeader)
	if header == "" {
		header = "Authorization"
	}
	if value := strings.TrimSpace(r.Header.Get(header)); value != "" {
		return value
	}
	return strings.TrimSpace(r.Header.Get("Authorization"))
}

func pathQueueJobID(raw string) (int64, error) {
	id, err := strconv.ParseInt(strings.TrimSpace(raw), 10, 64)
	if err != nil || id <= 0 {
		return 0, meshxapi.NewHTTPError(http.StatusBadRequest, "Automation runner job id is invalid")
	}
	return id, nil
}

func pathLogSequence(raw string) (int64, error) {
	sequence, err := strconv.ParseInt(strings.TrimSpace(raw), 10, 64)
	if err != nil || sequence <= 0 {
		return 0, meshxapi.NewHTTPError(http.StatusBadRequest, "Automation log sequence is invalid")
	}
	return sequence, nil
}

func (h *AutomationsHandler) PinAutomationRepo(r meshxapi.AuthenticatedRequest) (meshxapi.Response, error) {
	if err := h.ensureService(); err != nil {
		return meshxapi.Response{}, err
	}
	owner := strings.TrimSpace(r.Request.PathValue("owner"))
	repo := strings.TrimSpace(r.Request.PathValue("repo"))
	pin, err := h.service.PinAutomationRepo(r.Context(), r.WorkspaceID(), r.Subject(), owner, repo)
	if err != nil {
		return meshxapi.Response{}, err
	}
	return meshxapi.OK(converter.ToAutomationPinnedRepo(pin)), nil
}

func (h *AutomationsHandler) UnpinAutomationRepo(r meshxapi.AuthenticatedRequest) (meshxapi.Response, error) {
	if err := h.ensureService(); err != nil {
		return meshxapi.Response{}, err
	}
	owner := strings.TrimSpace(r.Request.PathValue("owner"))
	repo := strings.TrimSpace(r.Request.PathValue("repo"))
	if err := h.service.UnpinAutomationRepo(r.Context(), r.WorkspaceID(), r.Subject(), owner, repo); err != nil {
		return meshxapi.Response{}, err
	}
	return meshxapi.NoContent(), nil
}

func (h *AutomationsHandler) ListPinnedAutomationRepos(r meshxapi.AuthenticatedRequest) (meshxapi.Response, error) {
	if err := h.ensureService(); err != nil {
		return meshxapi.Response{}, err
	}
	page, perPage := paginationWithMax(r.Request, maxPerPage)
	pins, err := h.service.ListPinnedAutomationRepos(r.Context(), r.WorkspaceID(), r.Subject(), page, perPage)
	if err != nil {
		return meshxapi.Response{}, err
	}
	return meshxapi.OK(converter.ToAutomationPinnedRepos(pins)), nil
}
