package http

import (
	stdhttp "net/http"
	"time"

	"github.com/go-chi/chi/v5"
	"github.com/oklog/ulid/v2"

	"gitea.dcglab.co.uk/steve/restic-manager/internal/api"
	"gitea.dcglab.co.uk/steve/restic-manager/internal/store"
)

// handleCancelJob is POST /api/jobs/{id}/cancel. Sends a command.cancel
// envelope to the host that owns the job; the agent kills the running
// restic subprocess, and the resulting job.finished envelope (status =
// canceled) is what actually transitions the job row — this handler
// does not touch the jobs table directly. Returning 202 makes that
// asynchronicity explicit.
//
// 4xx cases:
//   - job not found (404)
//   - job already in a terminal state (409 — nothing to cancel)
//   - host offline (503 — same code path the run-now endpoint uses)
//
// Audit-logged as job.cancel with the job ID as target.
func (s *Server) handleCancelJob(w stdhttp.ResponseWriter, r *stdhttp.Request) {
	user, ok := s.requireUser(r)
	if !ok {
		writeJSONError(w, stdhttp.StatusUnauthorized, "unauthorised", "")
		return
	}
	jobID := chi.URLParam(r, "id")
	if jobID == "" {
		writeJSONError(w, stdhttp.StatusBadRequest, "missing_job_id", "")
		return
	}

	job, err := s.deps.Store.GetJob(r.Context(), jobID)
	if err != nil {
		writeJSONError(w, stdhttp.StatusNotFound, "job_not_found", "")
		return
	}
	switch api.JobStatus(job.Status) {
	case api.JobSucceeded, api.JobFailed, api.JobCancelled:
		writeJSONError(w, stdhttp.StatusConflict, "job_terminal",
			"job is already in a terminal state ("+job.Status+")")
		return
	}

	if !s.deps.Hub.Connected(job.HostID) {
		writeJSONError(w, stdhttp.StatusServiceUnavailable, "host_offline",
			"agent is not connected; can't deliver cancel signal")
		return
	}

	env, err := api.Marshal(api.MsgCommandCancel, jobID, api.CommandCancelPayload{
		JobID: jobID,
	})
	if err != nil {
		writeJSONError(w, stdhttp.StatusInternalServerError, "internal", "")
		return
	}
	if err := s.deps.Hub.Send(r.Context(), job.HostID, env); err != nil {
		writeJSONError(w, stdhttp.StatusServiceUnavailable, "host_offline", err.Error())
		return
	}

	var actorID *string
	actor := "system"
	if user != nil {
		actor = "user"
		actorID = &user.ID
	}
	_ = s.deps.Store.AppendAudit(r.Context(), store.AuditEntry{
		ID:         ulid.Make().String(),
		UserID:     actorID,
		Actor:      actor,
		Action:     "job.cancel",
		TargetKind: ptr("job"),
		TargetID:   &jobID,
		TS:         time.Now().UTC(),
	})

	w.WriteHeader(stdhttp.StatusAccepted)
}
