package fidcli

import (
	"archive/zip"
	"bytes"
	"context"
	"encoding/json"
	"fmt"
	"net/http"
	"net/http/httptest"
	"net/url"
	"testing"
	"time"

	"github.com/google/go-github/v54/github"
	"github.com/tidbcloud/devops/pkg/infra/config"

	. "github.com/onsi/ginkgo/v2"
	. "github.com/onsi/gomega"
)

func TestFIDClient(t *testing.T) {
	RegisterFailHandler(Fail)
	RunSpecs(t, "FID Client Suite")
}

var _ = Describe("FID client", func() {
	It("builds the command accepted by the FID dispatcher", func() {
		command := BuildCommand(SubmitRequest{
			Environment: "dev",
			Images: []string{
				"registry.example/tikv:v8.5.4-nextgen.1",
				"registry.example/pd:v8.5.4-nextgen.1",
			},
			CorrelationID: "21d4e84f-275a-5b07-8314-76ffcc736969",
		})

		Expect(command).To(Equal(
			"/fid env=dev cid=21d4e84f-275a-5b07-8314-76ffcc736969\n" +
				"registry.example/tikv:v8.5.4-nextgen.1\n" +
				"registry.example/pd:v8.5.4-nextgen.1",
		))
	})

	It("treats FID reactions as running, succeeded, or failed", func() {
		bot := &github.User{Login: github.String(fidResultActor)}
		Expect(deliveryStatusFromReactions(nil)).To(Equal(DeliveryStatusRunning))
		Expect(deliveryStatusFromReactions([]*github.Reaction{{Content: github.String("rocket"), User: bot}})).To(Equal(DeliveryStatusRunning))
		Expect(deliveryStatusFromReactions([]*github.Reaction{{Content: github.String("hooray"), User: bot}})).To(Equal(DeliveryStatusSucceeded))
		Expect(deliveryStatusFromReactions([]*github.Reaction{
			{Content: github.String("hooray"), User: bot},
			{Content: github.String("confused"), User: bot},
		})).To(Equal(DeliveryStatusFailed))
		Expect(deliveryStatusFromReactions([]*github.Reaction{{
			Content: github.String("hooray"), User: &github.User{Login: github.String("untrusted-user")},
		}})).To(Equal(DeliveryStatusRunning))
	})

	It("recognizes the correlation ID in a FID result comment", func() {
		body := `## Fast Image Delivery Results
<!-- META={"correlation_id":"21d4e84f-275a-5b07-8314-76ffcc736969","stage":"dev","images":["us.gcr.io/pingcap-public/tidbx/tikv:v1"],"target_images":["385595570414.dkr.ecr.us-west-2.amazonaws.com/tidbcloud/tikv:v1"]} -->`

		Expect(resultCommentMatches(body, `"correlation_id":"21d4e84f-275a-5b07-8314-76ffcc736969"`)).To(BeTrue())
		Expect(resultCommentMatches(body, `"correlation_id":"another"`)).To(BeFalse())
		metadata, err := parseResultMetadata(body)
		Expect(err).NotTo(HaveOccurred())
		Expect(metadata.Images).To(Equal([]string{"us.gcr.io/pingcap-public/tidbx/tikv:v1"}))
		Expect(metadata.TargetImages).To(Equal([]string{
			"385595570414.dkr.ecr.us-west-2.amazonaws.com/tidbcloud/tikv:v1",
		}))
	})

	It("waits for the result comment after a successful reaction", func() {
		commentListCalls := 0
		resultBody := `## Fast Image Delivery Results
<!-- META={"correlation_id":"21d4e84f-275a-5b07-8314-76ffcc736969","stage":"dev","images":["registry.example/tikv:v8.5.4-nextgen.1"],"target_images":["registry.example-delivery/tikv:v8.5.4-nextgen.1"]} -->`
		handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
			switch {
			case r.Method == http.MethodGet && r.URL.Path == "/repos/owner/repo/issues/comments/456/reactions":
				writeTestJSON(w, http.StatusOK, `[{"content":"hooray","user":{"login":"github-actions[bot]"}}]`)
			case r.Method == http.MethodGet && r.URL.Path == "/repos/owner/repo/issues/778/comments":
				commentListCalls++
				if commentListCalls == 1 {
					writeTestJSON(w, http.StatusOK, `[]`)
					return
				}
				body, err := json.Marshal([]map[string]any{{
					"id":         789,
					"body":       resultBody,
					"html_url":   "https://github.test/issues/778#issuecomment-789",
					"created_at": "2026-08-06T05:01:01Z",
				}})
				Expect(err).NotTo(HaveOccurred())
				writeTestJSON(w, http.StatusOK, string(body))
			default:
				Fail(fmt.Sprintf("unexpected GitHub request: %s %s", r.Method, r.URL.String()))
			}
		})
		client, server := newTestFIDClient(handler)
		DeferCleanup(server.Close)
		submission := Submission{
			IssueNumber: 778,
			CommentID:   456,
			CreatedAt:   time.Date(2026, 8, 6, 5, 0, 0, 0, time.UTC),
		}

		result, err := client.GetResult(
			context.Background(), submission, "21d4e84f-275a-5b07-8314-76ffcc736969",
		)

		Expect(err).NotTo(HaveOccurred())
		Expect(result.Status).To(Equal(DeliveryStatusRunning))
		Expect(result.Images).To(BeEmpty())

		result, err = client.GetResult(
			context.Background(), submission, "21d4e84f-275a-5b07-8314-76ffcc736969",
		)

		Expect(err).NotTo(HaveOccurred())
		Expect(result.Status).To(Equal(DeliveryStatusSucceeded))
		Expect(result.ResultCommentURL).To(Equal("https://github.test/issues/778#issuecomment-789"))
		Expect(result.Images).To(Equal([]string{"registry.example/tikv:v8.5.4-nextgen.1"}))
		Expect(result.TargetImages).To(Equal([]string{"registry.example-delivery/tikv:v8.5.4-nextgen.1"}))
	})

	DescribeTable("identifies a cloud provider from a target image",
		func(imageRef, provider string) {
			Expect(CloudProviderFromImageRef(imageRef)).To(Equal(provider))
		},
		Entry("AWS ECR", "385595570414.dkr.ecr.us-west-2.amazonaws.com/tidbcloud/tikv:v1", "AWS"),
		Entry("Aliyun CR", "tidbcloud-dev-registry.ap-southeast-1.cr.aliyuncs.com/tidbcloud/tikv:v1", "ALIYUN"),
		Entry("Azure ACR", "tidbclouddev.azurecr.io/tidbcloud/tikv:v1", "AZURE"),
	)

	It("dispatches the existing query workflow and reads its result artifact", func() {
		oldLookupInterval, oldPollInterval := queryRunLookupInterval, queryRunPollInterval
		queryRunLookupInterval, queryRunPollInterval = time.Millisecond, time.Millisecond
		DeferCleanup(func() {
			queryRunLookupInterval, queryRunPollInterval = oldLookupInterval, oldPollInterval
		})

		artifactArchive := queryResultArchive(`{
			"image_ref":"385595570414.dkr.ecr.us-west-2.amazonaws.com/tidbcloud/tikv:v1",
			"created_at":"2026-08-06T08:00:00Z",
			"digest":"sha256:abc",
			"multi_arch":true,
			"platforms":["linux/amd64","linux/arm64"]
		}`)
		var serverURL string
		handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
			switch {
			case r.Method == http.MethodGet && r.URL.Path == "/repos/owner/repo/actions/workflows/query-image-tag.yml/runs" && r.URL.Query().Get("event") == "":
				writeTestJSON(w, http.StatusOK, `{"workflow_runs":[{"id":100}]}`)
			case r.Method == http.MethodPost && r.URL.Path == "/repos/owner/repo/actions/workflows/query-image-tag.yml/dispatches":
				w.WriteHeader(http.StatusNoContent)
			case r.Method == http.MethodGet && r.URL.Path == "/repos/owner/repo/actions/workflows/query-image-tag.yml/runs":
				writeTestJSON(w, http.StatusOK, `{"workflow_runs":[{"id":101,"display_title":"query-image-tag 385595570414.dkr.ecr.us-west-2.amazonaws.com/tidbcloud/tikv:v1"}]}`)
			case r.Method == http.MethodGet && r.URL.Path == "/repos/owner/repo/actions/runs/101":
				writeTestJSON(w, http.StatusOK, `{"id":101,"status":"completed","conclusion":"success","html_url":"https://github.test/actions/runs/101"}`)
			case r.Method == http.MethodGet && r.URL.Path == "/repos/owner/repo/actions/runs/101/artifacts":
				writeTestJSON(w, http.StatusOK, `{"artifacts":[{"id":55,"name":"result.json","expired":false}]}`)
			case r.Method == http.MethodGet && r.URL.Path == "/repos/owner/repo/actions/artifacts/55/zip":
				w.Header().Set("Location", serverURL+"/artifact.zip")
				w.WriteHeader(http.StatusFound)
			case r.Method == http.MethodGet && r.URL.Path == "/artifact.zip":
				w.WriteHeader(http.StatusOK)
				_, err := w.Write(artifactArchive)
				Expect(err).NotTo(HaveOccurred())
			default:
				Fail(fmt.Sprintf("unexpected GitHub request: %s %s", r.Method, r.URL.String()))
			}
		})
		client, server := newTestFIDClient(handler)
		serverURL = server.URL
		DeferCleanup(server.Close)

		result, err := client.QueryImageAvailability(
			context.Background(),
			"385595570414.dkr.ecr.us-west-2.amazonaws.com/tidbcloud/tikv:v1",
		)

		Expect(err).NotTo(HaveOccurred())
		Expect(result.Status).To(Equal(ImageAvailabilityStatusAvailable))
		Expect(result.CloudProvider).To(Equal("AWS"))
		Expect(result.Digest).To(Equal("sha256:abc"))
		Expect(result.OCIPlatforms).To(ConsistOf("linux/amd64", "linux/arm64"))
		Expect(result.WorkflowRunID).To(Equal(int64(101)))
	})

	It("returns a persistent handle while an image query workflow is running", func() {
		oldLookupInterval := queryRunLookupInterval
		queryRunLookupInterval = time.Millisecond
		DeferCleanup(func() { queryRunLookupInterval = oldLookupInterval })

		handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
			switch {
			case r.Method == http.MethodGet && r.URL.Path == "/repos/owner/repo/actions/workflows/query-image-tag.yml/runs" && r.URL.Query().Get("event") == "":
				writeTestJSON(w, http.StatusOK, `{"workflow_runs":[{"id":100}]}`)
			case r.Method == http.MethodPost && r.URL.Path == "/repos/owner/repo/actions/workflows/query-image-tag.yml/dispatches":
				w.WriteHeader(http.StatusNoContent)
			case r.Method == http.MethodGet && r.URL.Path == "/repos/owner/repo/actions/workflows/query-image-tag.yml/runs":
				writeTestJSON(w, http.StatusOK, `{"workflow_runs":[{"id":101,"display_title":"query-image-tag us.gcr.io/pingcap-public/tidbx/tikv:v1","html_url":"https://github.test/actions/runs/101"}]}`)
			case r.Method == http.MethodGet && r.URL.Path == "/repos/owner/repo/actions/runs/101":
				writeTestJSON(w, http.StatusOK, `{"id":101,"status":"in_progress"}`)
			default:
				Fail(fmt.Sprintf("unexpected GitHub request: %s %s", r.Method, r.URL.String()))
			}
		})
		client, server := newTestFIDClient(handler)
		DeferCleanup(server.Close)

		query, err := client.StartImageAvailabilityQuery(
			context.Background(),
			"us.gcr.io/pingcap-public/tidbx/tikv:v1",
		)
		Expect(err).NotTo(HaveOccurred())
		Expect(query.WorkflowRunID).To(Equal(int64(101)))
		Expect(query.WorkflowRunURL).To(Equal("https://github.test/actions/runs/101"))

		result, completed, err := client.GetImageAvailabilityQueryResult(context.Background(), *query)
		Expect(err).NotTo(HaveOccurred())
		Expect(completed).To(BeFalse())
		Expect(result).To(BeNil())
	})

	It("does not require FID settings while Kernel Image is disabled", func() {
		client, err := NewClient(&config.RootConfig{})

		Expect(err).NotTo(HaveOccurred())
		Expect(client).NotTo(BeNil())
	})

	It("requires a GitHub token when FID delivery is enabled", func() {
		conf := &config.RootConfig{}
		conf.Services.KernelImage.Enabled = true
		conf.Services.KernelImage.FID = config.KernelImageFIDConfig{
			RepositoryOwner: "tidbcloud", RepositoryName: "docker-image-controller",
			QueueLabelPrefix: "delivery-queue-tidbx", QueueCommentLimit: 2000,
		}

		_, err := NewClient(conf)

		Expect(err).To(MatchError(ContainSubstring("GitHub token is required")))
	})

	It("does not send the GitHub token when fetching a signed download URL", func() {
		var authorization string
		server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
			authorization = r.Header.Get("Authorization")
			w.WriteHeader(http.StatusOK)
		}))
		DeferCleanup(server.Close)

		conf := &config.RootConfig{}
		conf.Services.KernelImage.Enabled = true
		conf.Services.KernelImage.FID = config.KernelImageFIDConfig{
			RepositoryOwner: "tidbcloud", RepositoryName: "docker-image-controller",
			QueueLabelPrefix: "delivery-queue-tidbx", QueueCommentLimit: 2000,
		}
		conf.Clients.Github.Token = "github-token"
		fidClient, err := NewClient(conf)
		Expect(err).NotTo(HaveOccurred())

		concreteClient, ok := fidClient.(*client)
		Expect(ok).To(BeTrue())
		resp, err := concreteClient.downloadHTTPClient.Get(server.URL)
		Expect(err).NotTo(HaveOccurred())
		Expect(resp.Body.Close()).To(Succeed())
		Expect(authorization).To(BeEmpty())
	})

	It("reuses the active queue and scans its comments only once", func() {
		var commentListCalls int
		handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
			switch {
			case r.Method == http.MethodGet && r.URL.Path == "/repos/owner/repo/issues":
				Expect(r.URL.Query().Get("state")).To(Equal("open"))
				writeTestJSON(w, http.StatusOK, `[{"number":778,"comments":1707,"created_at":"2026-06-11T15:23:55Z"}]`)
			case r.Method == http.MethodGet && r.URL.Path == "/repos/owner/repo/issues/778/comments":
				commentListCalls++
				Expect(r.URL.Query().Get("since")).To(Equal("2026-08-06T04:59:59Z"))
				writeTestJSON(w, http.StatusOK, `[]`)
			case r.Method == http.MethodPost && r.URL.Path == "/repos/owner/repo/issues/778/comments":
				var body github.IssueComment
				Expect(json.NewDecoder(r.Body).Decode(&body)).To(Succeed())
				Expect(body.GetBody()).To(ContainSubstring("cid=21d4e84f-275a-5b07-8314-76ffcc736969"))
				writeTestJSON(w, http.StatusCreated, `{"id":456,"html_url":"https://github.test/issues/778#issuecomment-456","created_at":"2026-08-06T05:00:01Z"}`)
			default:
				Fail(fmt.Sprintf("unexpected GitHub request: %s %s", r.Method, r.URL.String()))
			}
		})
		client, server := newTestFIDClient(handler)
		DeferCleanup(server.Close)

		submission, err := client.Submit(context.Background(), testSubmitRequest())

		Expect(err).NotTo(HaveOccurred())
		Expect(submission.IssueNumber).To(Equal(778))
		Expect(submission.CommentID).To(Equal(int64(456)))
		Expect(commentListCalls).To(Equal(1))
	})

	It("reuses a matching request from the previous queue after rotation", func() {
		handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
			switch {
			case r.Method == http.MethodGet && r.URL.Path == "/repos/owner/repo/issues" && r.URL.Query().Get("state") == "open":
				writeTestJSON(w, http.StatusOK, `[{"number":900,"comments":2,"created_at":"2026-08-06T06:00:00Z"}]`)
			case r.Method == http.MethodGet && r.URL.Path == "/repos/owner/repo/issues" && r.URL.Query().Get("state") == "closed":
				Expect(r.URL.Query().Get("per_page")).To(Equal("10"))
				writeTestJSON(w, http.StatusOK, `[{"number":778,"comments":2001,"created_at":"2026-06-11T15:23:55Z"}]`)
			case r.Method == http.MethodGet && r.URL.Path == "/repos/owner/repo/issues/900/comments":
				writeTestJSON(w, http.StatusOK, `[]`)
			case r.Method == http.MethodGet && r.URL.Path == "/repos/owner/repo/issues/778/comments":
				Expect(r.URL.Query().Get("since")).To(Equal("2026-08-06T04:59:59Z"))
				writeTestJSON(w, http.StatusOK, `[{
					"id":456,
					"body":"/fid env=dev cid=21d4e84f-275a-5b07-8314-76ffcc736969\nregistry.example/tikv:v8.5.4-nextgen.1",
					"html_url":"https://github.test/issues/778#issuecomment-456",
					"created_at":"2026-08-06T05:00:01Z"
				}]`)
			default:
				Fail(fmt.Sprintf("unexpected GitHub request: %s %s", r.Method, r.URL.String()))
			}
		})
		client, server := newTestFIDClient(handler)
		DeferCleanup(server.Close)

		submission, err := client.Submit(context.Background(), testSubmitRequest())

		Expect(err).NotTo(HaveOccurred())
		Expect(submission.IssueNumber).To(Equal(778))
		Expect(submission.CommentID).To(Equal(int64(456)))
	})

	It("rotates a full queue, keeps its label, and converges on the newest active issue", func() {
		var (
			issueListCalls int
			closedIssues   []int
			commentIssue   int
			labelGets      int
		)
		handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
			switch {
			case r.Method == http.MethodGet && r.URL.Path == "/repos/owner/repo/issues":
				issueListCalls++
				Expect(r.URL.Query().Get("state")).To(Equal("open"))
				if issueListCalls == 1 {
					writeTestJSON(w, http.StatusOK, `[{"number":778,"comments":2000,"created_at":"2026-06-11T15:23:55Z"}]`)
					return
				}
				writeTestJSON(w, http.StatusOK, `[
					{"number":780,"comments":0,"created_at":"2026-08-06T05:00:02Z"},
					{"number":779,"comments":0,"created_at":"2026-08-06T05:00:01Z"}
				]`)
			case r.Method == http.MethodGet && r.URL.Path == "/repos/owner/repo/issues/778/comments":
				writeTestJSON(w, http.StatusOK, `[]`)
			case r.Method == http.MethodPatch && (r.URL.Path == "/repos/owner/repo/issues/778" || r.URL.Path == "/repos/owner/repo/issues/779"):
				var body github.IssueRequest
				Expect(json.NewDecoder(r.Body).Decode(&body)).To(Succeed())
				Expect(body.GetState()).To(Equal("closed"))
				var issueNumber int
				_, err := fmt.Sscanf(r.URL.Path, "/repos/owner/repo/issues/%d", &issueNumber)
				Expect(err).NotTo(HaveOccurred())
				closedIssues = append(closedIssues, issueNumber)
				writeTestJSON(w, http.StatusOK, fmt.Sprintf(`{"number":%d,"state":"closed"}`, issueNumber))
			case r.Method == http.MethodGet && r.URL.Path == "/repos/owner/repo/labels/delivery-queue-tidbx/dev":
				labelGets++
				writeTestJSON(w, http.StatusOK, `{"name":"delivery-queue-tidbx/dev"}`)
			case r.Method == http.MethodPost && r.URL.Path == "/repos/owner/repo/issues":
				writeTestJSON(w, http.StatusCreated, `{"number":779,"comments":0,"created_at":"2026-08-06T05:00:01Z"}`)
			case r.Method == http.MethodGet && (r.URL.Path == "/repos/owner/repo/issues/779/comments" || r.URL.Path == "/repos/owner/repo/issues/780/comments"):
				writeTestJSON(w, http.StatusOK, `[]`)
			case r.Method == http.MethodPost && r.URL.Path == "/repos/owner/repo/issues/780/comments":
				commentIssue = 780
				writeTestJSON(w, http.StatusCreated, `{"id":457,"html_url":"https://github.test/issues/780#issuecomment-457","created_at":"2026-08-06T05:00:03Z"}`)
			default:
				Fail(fmt.Sprintf("unexpected GitHub request: %s %s", r.Method, r.URL.String()))
			}
		})
		client, server := newTestFIDClient(handler)
		DeferCleanup(server.Close)

		submission, err := client.Submit(context.Background(), testSubmitRequest())

		Expect(err).NotTo(HaveOccurred())
		Expect(submission.IssueNumber).To(Equal(780))
		Expect(closedIssues).To(ConsistOf(778, 779))
		Expect(commentIssue).To(Equal(780))
		Expect(labelGets).To(Equal(1))
	})

	It("creates the queue label only when no queue exists", func() {
		var (
			openIssueLists int
			labelCreates   int
		)
		handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
			switch {
			case r.Method == http.MethodGet && r.URL.Path == "/repos/owner/repo/issues" && r.URL.Query().Get("state") == "open":
				openIssueLists++
				if openIssueLists == 1 {
					writeTestJSON(w, http.StatusOK, `[]`)
					return
				}
				writeTestJSON(w, http.StatusOK, `[{"number":1,"comments":0,"created_at":"2026-08-06T05:00:01Z"}]`)
			case r.Method == http.MethodGet && r.URL.Path == "/repos/owner/repo/issues" && r.URL.Query().Get("state") == "closed":
				writeTestJSON(w, http.StatusOK, `[]`)
			case r.Method == http.MethodGet && r.URL.Path == "/repos/owner/repo/labels/delivery-queue-tidbx/dev":
				writeTestJSON(w, http.StatusNotFound, `{"message":"Not Found"}`)
			case r.Method == http.MethodPost && r.URL.Path == "/repos/owner/repo/labels":
				labelCreates++
				writeTestJSON(w, http.StatusCreated, `{"name":"delivery-queue-tidbx/dev"}`)
			case r.Method == http.MethodPost && r.URL.Path == "/repos/owner/repo/issues":
				writeTestJSON(w, http.StatusCreated, `{"number":1,"comments":0,"created_at":"2026-08-06T05:00:01Z"}`)
			case r.Method == http.MethodGet && r.URL.Path == "/repos/owner/repo/issues/1/comments":
				writeTestJSON(w, http.StatusOK, `[]`)
			case r.Method == http.MethodPost && r.URL.Path == "/repos/owner/repo/issues/1/comments":
				writeTestJSON(w, http.StatusCreated, `{"id":2,"html_url":"https://github.test/issues/1#issuecomment-2","created_at":"2026-08-06T05:00:02Z"}`)
			default:
				Fail(fmt.Sprintf("unexpected GitHub request: %s %s", r.Method, r.URL.String()))
			}
		})
		client, server := newTestFIDClient(handler)
		DeferCleanup(server.Close)

		_, err := client.Submit(context.Background(), testSubmitRequest())

		Expect(err).NotTo(HaveOccurred())
		Expect(labelCreates).To(Equal(1))
	})
})

func newTestFIDClient(handler http.Handler) (*client, *httptest.Server) {
	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		defer GinkgoRecover()
		handler.ServeHTTP(w, r)
	}))
	baseURL, err := url.Parse(server.URL + "/")
	Expect(err).NotTo(HaveOccurred())
	githubClient := github.NewClient(server.Client())
	githubClient.BaseURL = baseURL
	return &client{
		github:             githubClient,
		downloadHTTPClient: server.Client(),
		owner:              "owner",
		repo:               "repo",
		queueLabelPrefix:   "delivery-queue-tidbx",
		queueCommentLimit:  2000,
	}, server
}

func queryResultArchive(resultJSON string) []byte {
	buffer := &bytes.Buffer{}
	archive := zip.NewWriter(buffer)
	file, err := archive.Create(queryArtifactName)
	Expect(err).NotTo(HaveOccurred())
	_, err = file.Write([]byte(resultJSON))
	Expect(err).NotTo(HaveOccurred())
	Expect(archive.Close()).To(Succeed())
	return buffer.Bytes()
}

func testSubmitRequest() SubmitRequest {
	return SubmitRequest{
		Environment:   "dev",
		Images:        []string{"registry.example/tikv:v8.5.4-nextgen.1"},
		CorrelationID: "21d4e84f-275a-5b07-8314-76ffcc736969",
		RequestedAt:   time.Date(2026, 8, 6, 5, 0, 0, 0, time.UTC),
	}
}

func writeTestJSON(w http.ResponseWriter, status int, body string) {
	w.Header().Set("Content-Type", "application/json")
	w.WriteHeader(status)
	_, err := w.Write([]byte(body))
	Expect(err).NotTo(HaveOccurred())
}
