package xrpc

import (
	"encoding/json"
	"fmt"
	"net/http"

	"github.com/bluesky-social/indigo/atproto/syntax"
	"tangled.org/core/api/tangled"
	"tangled.org/core/spindle/engine"
	"tangled.org/core/spindle/models"
	xrpcerr "tangled.org/core/xrpc/errors"
)

func (x *Xrpc) CancelPipeline(w http.ResponseWriter, r *http.Request) {
	l := x.Logger
	fail := func(e xrpcerr.XrpcError) {
		l.Error("failed", "kind", e.Tag, "error", e.Message)
		writeError(w, e, http.StatusBadRequest)
	}
	l.Debug("cancel pipeline")

	actorDid, ok := r.Context().Value(ActorDid).(syntax.DID)
	if !ok {
		fail(xrpcerr.MissingActorDidError)
		return
	}

	var input tangled.CiCancelPipeline_Input
	if err := json.NewDecoder(r.Body).Decode(&input); err != nil {
		fail(xrpcerr.GenericError(err))
		return
	}

	pipelineTid, err := syntax.ParseTID(input.Pipeline)
	if err != nil {
		fail(xrpcerr.GenericError(fmt.Errorf("invalid pipeline TID %q: %w", input.Pipeline, err)))
		return
	}

	repoDid, xerr, ok := x.resolveOwnedRepo(r.Context(), actorDid, input.Repo)
	if !ok {
		fail(xerr)
		return
	}
	repo, err := x.Db.GetRepoByDid(repoDid)
	if err != nil {
		fail(xrpcerr.GenericError(fmt.Errorf("failed to get repo: %w", err)))
		return
	}

	// the actor is only authorized against input.Repo, so make sure the
	// pipeline actually belongs to it before cancelling anything
	p, err := x.Db.GetPipeline(r.Context(), pipelineTid.String())
	if err != nil {
		fail(xrpcerr.GenericError(fmt.Errorf("failed to get pipeline: %w", err)))
		return
	}
	if p.Repo == nil || *p.Repo != repoDid.String() {
		fail(xrpcerr.AccessControlError(actorDid.String()))
		return
	}

	pipelineId := models.PipelineId{
		Knot: repo.Knot,
		Rkey: pipelineTid.String(),
	}
	l = l.With("input.pipeline", pipelineTid, "input.workflows", input.Workflows)

	workflows := input.Workflows
	if len(workflows) == 0 {
		// cancel every workflow when none are specified
		for _, w := range p.Workflows {
			workflows = append(workflows, w.Name)
		}
	}

	canceled := false
	defer func() {
		l.Debug("canceled pipeline", "canceled", canceled)
	}()

	for _, wName := range workflows {
		wid := models.WorkflowId{
			PipelineId: pipelineId,
			Name:       wName,
		}
		l.Debug("cancel pipeline", "wid", wid)

		// dont cancel a workflow that already finished
		st, err := x.Db.GetStatus(wid)
		if err == nil && models.StatusKind(st.Status).IsFinish() {
			continue
		}

		if err := x.Db.StatusCancelled(wid, "User canceled the workflow", -1, x.Notifier); err != nil {
			fail(xrpcerr.GenericError(fmt.Errorf("failed to emit status cancelled: %w", err)))
			return
		}

		engine.CancelWorkflow(wid)

		for _, eng := range x.Engines {
			l.Debug("destroying workflow", "wid", wid)
			if err := eng.DestroyWorkflow(r.Context(), wid); err != nil {
				fail(xrpcerr.GenericError(fmt.Errorf("failed to destroy workflow: %w", err)))
				return
			}
		}
	}

	canceled = true

	w.WriteHeader(http.StatusOK)
}
