package byocimagesynccli

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

	"github.com/google/go-github/v54/github"

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

func TestBYOCImageSyncClient(t *testing.T) {
	RegisterFailHandler(Fail)
	RunSpecs(t, "BYOC Image Sync Client Suite")
}

var _ = Describe("BYOC image sync client", func() {
	const correlationID = "21d4e84f-275a-5b07-8314-76ffcc736969"

	It("dispatches the dev workflow and finds its run by correlation ID", func() {
		listCalls := 0
		dispatchCalls := 0
		handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
			switch {
			case r.Method == http.MethodGet && r.URL.Path == "/repos/owner/repo/actions/workflows/image-sync-byoc-on-demand-dev.yml/runs":
				Expect(r.URL.Query().Get("branch")).To(Equal("main"))
				Expect(r.URL.Query().Get("event")).To(Equal("workflow_dispatch"))
				listCalls++
				if listCalls == 1 {
					writeBYOCTestJSON(w, http.StatusOK, `{"workflow_runs":[]}`)
					return
				}
				writeBYOCTestJSON(w, http.StatusOK, `{"workflow_runs":[{"id":123,"display_title":"BYOC dev sync `+correlationID+`","html_url":"https://github.test/actions/runs/123","status":"queued"}]}`)
			case r.Method == http.MethodPost && r.URL.Path == "/repos/owner/repo/actions/workflows/image-sync-byoc-on-demand-dev.yml/dispatches":
				dispatchCalls++
				var body struct {
					Ref    string         `json:"ref"`
					Inputs map[string]any `json:"inputs"`
				}
				Expect(json.NewDecoder(r.Body).Decode(&body)).To(Succeed())
				Expect(body.Ref).To(Equal("main"))
				Expect(body.Inputs["correlation-id"]).To(Equal(correlationID))
				Expect(body.Inputs["images"]).To(Equal(
					`[{"source":"385595570414.dkr.ecr.us-west-2.amazonaws.com/tidbcloud/tikv:v1","digest":"sha256:abc"}]`,
				))
				w.WriteHeader(http.StatusNoContent)
			default:
				Fail(fmt.Sprintf("unexpected GitHub request: %s %s", r.Method, r.URL.String()))
			}
		})
		client, server := newTestBYOCClient(handler, "dev", "image-sync-byoc-on-demand-dev.yml")
		DeferCleanup(server.Close)

		run, err := client.Dispatch(context.Background(), DispatchRequest{
			Environment: "dev",
			Images: []Image{{
				Source: "385595570414.dkr.ecr.us-west-2.amazonaws.com/tidbcloud/tikv:v1",
				Digest: "sha256:abc",
			}},
			CorrelationID: correlationID,
			RequestedAt:   time.Date(2026, 9, 7, 5, 0, 0, 0, time.UTC),
		})

		Expect(err).NotTo(HaveOccurred())
		Expect(run.ID).To(Equal(int64(123)))
		Expect(run.URL).To(Equal("https://github.test/actions/runs/123"))
		Expect(dispatchCalls).To(Equal(1))
	})

	It("reuses an existing workflow run instead of dispatching a duplicate", func() {
		handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
			Expect(r.Method).To(Equal(http.MethodGet))
			writeBYOCTestJSON(w, http.StatusOK, `{"workflow_runs":[{"id":456,"display_title":"BYOC prod sync `+correlationID+`","html_url":"https://github.test/actions/runs/456","status":"in_progress","created_at":"2026-09-07T05:00:01Z"}]}`)
		})
		client, server := newTestBYOCClient(handler, "prod", "image-sync-byoc-on-demand-prod.yml")
		DeferCleanup(server.Close)

		run, err := client.Dispatch(context.Background(), DispatchRequest{
			Environment:   "prod",
			Images:        []Image{{Source: "source/tikv:v1", Digest: "sha256:abc"}},
			CorrelationID: correlationID,
			RequestedAt:   time.Date(2026, 9, 7, 5, 0, 0, 0, time.UTC),
		})

		Expect(err).NotTo(HaveOccurred())
		Expect(run.ID).To(Equal(int64(456)))
	})

	It("dispatches a new workflow run when the previous attempt failed", func() {
		listCalls := 0
		dispatchCalls := 0
		handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
			switch r.Method {
			case http.MethodGet:
				listCalls++
				if listCalls == 1 {
					writeBYOCTestJSON(w, http.StatusOK, `{"workflow_runs":[{"id":456,"display_title":"BYOC prod sync `+correlationID+`","status":"completed","conclusion":"failure","created_at":"2026-09-07T05:00:01Z"}]}`)
					return
				}
				writeBYOCTestJSON(w, http.StatusOK, `{"workflow_runs":[{"id":457,"display_title":"BYOC prod sync `+correlationID+`","status":"queued"}]}`)
			case http.MethodPost:
				dispatchCalls++
				w.WriteHeader(http.StatusNoContent)
			default:
				Fail(fmt.Sprintf("unexpected GitHub request: %s %s", r.Method, r.URL.String()))
			}
		})
		client, server := newTestBYOCClient(handler, "prod", "image-sync-byoc-on-demand-prod.yml")
		DeferCleanup(server.Close)

		run, err := client.Dispatch(context.Background(), DispatchRequest{
			Environment:   "prod",
			Images:        []Image{{Source: "source/tikv:v1", Digest: "sha256:abc"}},
			CorrelationID: correlationID,
			RequestedAt:   time.Date(2026, 9, 7, 5, 0, 0, 0, time.UTC),
		})

		Expect(err).NotTo(HaveOccurred())
		Expect(run.ID).To(Equal(int64(457)))
		Expect(dispatchCalls).To(Equal(1))
	})

	It("reads the terminal workflow conclusion", func() {
		handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
			Expect(r.Method).To(Equal(http.MethodGet))
			Expect(r.URL.Path).To(Equal("/repos/owner/repo/actions/runs/789"))
			writeBYOCTestJSON(w, http.StatusOK, `{"id":789,"html_url":"https://github.test/actions/runs/789","status":"completed","conclusion":"success"}`)
		})
		client, server := newTestBYOCClient(handler, "dev", "image-sync-byoc-on-demand-dev.yml")
		DeferCleanup(server.Close)

		run, err := client.GetRun(context.Background(), 789)

		Expect(err).NotTo(HaveOccurred())
		Expect(run.Status).To(Equal("completed"))
		Expect(run.Conclusion).To(Equal("success"))
	})

	It("rejects a request for a different deployment environment", func() {
		client, server := newTestBYOCClient(
			http.HandlerFunc(func(http.ResponseWriter, *http.Request) {
				Fail("GitHub must not be called for a mismatched environment")
			}),
			"dev",
			"image-sync-byoc-on-demand-dev.yml",
		)
		DeferCleanup(server.Close)

		_, err := client.Dispatch(context.Background(), DispatchRequest{
			Environment:   "prod",
			Images:        []Image{{Source: "source/tikv:v1", Digest: "sha256:abc"}},
			CorrelationID: correlationID,
		})

		Expect(err).To(MatchError(ContainSubstring("does not match configured environment")))
	})
})

func newTestBYOCClient(handler http.Handler, environment, workflow string) (*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,
		owner:       "owner",
		repo:        "repo",
		ref:         "main",
		environment: environment,
		workflow:    workflow,
	}, server
}

func writeBYOCTestJSON(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())
}
