package spindle

import (
	"bytes"
	"encoding/json"
	"net/http"
	"net/http/httptest"
	"strings"
	"testing"
	"time"

	"github.com/bluesky-social/indigo/atproto/atcrypto"
	"github.com/bluesky-social/indigo/atproto/auth"
	"github.com/bluesky-social/indigo/atproto/identity"
	"github.com/bluesky-social/indigo/atproto/syntax"

	"tangled.org/core/api/org_tangled"
	"tangled.org/core/idresolver"
	"tangled.org/core/spindle/models"
)

// calls the spindle's own xrpc router as the repo owner, signed like the web signs
func (e *prPipelineEnv) asOwner(t *testing.T) func(nsid string, pipeline models.PipelineId) (int, string) {
	t.Helper()
	priv, err := atcrypto.GeneratePrivateKeyP256()
	if err != nil {
		t.Fatal(err)
	}
	pub, err := priv.PublicKey()
	if err != nil {
		t.Fatal(err)
	}
	resolver := e.n.s.res
	e.n.s.res = idresolver.NewMockResolver(idresolver.MockDirectory{Ident: &identity.Identity{
		DID:  testOwner,
		Keys: map[string]identity.VerificationMethod{"atproto": {Type: "Multikey", PublicKeyMultibase: pub.Multibase()}},
	}})
	router := e.n.s.XrpcRouter()
	e.n.s.res = resolver
	return func(nsid string, pipeline models.PipelineId) (int, string) {
		t.Helper()
		lxm := syntax.NSID(nsid)
		token, err := auth.SignServiceAuth(testOwner, e.n.s.cfg.Server.Did().String(), time.Minute, &lxm, priv)
		if err != nil {
			t.Fatal(err)
		}
		body, _ := json.Marshal(map[string]string{"repo": testRepoDid.String(), "pipeline": string(pipeline)})
		req := httptest.NewRequest(http.MethodPost, "/"+nsid, bytes.NewReader(body))
		req.Header.Set("Authorization", "Bearer "+token)
		req.Header.Set("Content-Type", "application/json")
		w := httptest.NewRecorder()
		router.ServeHTTP(w, req)
		return w.Code, w.Body.String()
	}
}

func TestRetryNeedsTheRunToHaveBeenApproved(t *testing.T) {
	e := newPRPipelineEnv(t)
	call := e.asOwner(t)
	refused := func(what string, id models.PipelineId) {
		t.Helper()
		if code, body := call(org_tangled.CiRetryPipelineNSID, id); code != http.StatusBadRequest || !strings.Contains(body, "PipelineNotRetryable") {
			t.Errorf("retrying %s = %d %s, want 400 PipelineNotRetryable", what, code, body)
		}
	}
	ok := func(what, nsid string, id models.PipelineId) {
		t.Helper()
		if code, body := call(nsid, id); code != http.StatusOK {
			t.Fatalf("%s = %d %s, want 200", what, code, body)
		}
	}

	e.ticketAs(otherPull, "open", testDave, opCreate)
	e.patchAs("bafydaveone", otherPull, e.head(t), testDave)
	blocked := expectPRPipelines(t, e.n, 1)[0]
	refused("a blocked run", blocked.ID)
	ok("cancelling the blocked run", org_tangled.CiCancelPipelineNSID, blocked.ID)
	refused("a blocked run cancelled before anyone approved it", blocked.ID)

	e.patchAs("bafydavetwo", otherPull, e.head(t), testDave)
	approved := expectPRPipelines(t, e.n, 2)[1]
	ok("approving", org_tangled.CiApprovePipelineNSID, approved.ID)
	ok("cancelling the approved run", org_tangled.CiCancelPipelineNSID, approved.ID)
	ok("retrying an approved run that was cancelled", org_tangled.CiRetryPipelineNSID, approved.ID)

	// a running pipeline can be retried. the new run goes beside it
	e.ticketAs(pullRkey, "open", testBob, opCreate)
	e.patchAs(patchOne, pullRkey, e.head(t), testBob)
	running := expectPRPipelines(t, e.n, 4)[3]
	if err := e.n.s.db.StatusRunning(models.WorkflowId{PipelineId: running.ID, Name: "pr.yaml"}, nil); err != nil {
		t.Fatal(err)
	}
	ok("retrying a running run", org_tangled.CiRetryPipelineNSID, running.ID)
}
