package api

import (
	"net/http"
	"testing"

	"github.com/alexei/tinyforge/internal/store"
	"github.com/alexei/tinyforge/internal/workload/plugin"
)

// createImageWorkload creates an image-source workload through the API so
// source_kind is persisted exactly as production does, returning its id.
func createImageWorkload(t *testing.T, e *apiTestEnv, name string) string {
	t.Helper()
	resp := e.do(t, http.MethodPost, "/api/workloads", pluginWorkloadRequest{
		Name: name, SourceKind: "image", SourceConfig: validImageSourceConfig(),
	})
	if resp.StatusCode != http.StatusCreated {
		_ = decodeEnvelope(t, resp, nil)
		t.Fatalf("create workload: status %d", resp.StatusCode)
	}
	var got plugin.Workload
	if errMsg := decodeEnvelope(t, resp, &got); errMsg != "" {
		t.Fatalf("create workload envelope error: %q", errMsg)
	}
	return got.ID
}

func TestListWorkloadDeploys_ComputesRollbackable(t *testing.T) {
	e := newAPITestEnv(t)
	id := createImageWorkload(t, e, "app")

	// success + reference + image  => rollbackable
	e.store.InsertDeployHistory(store.DeployHistoryEntry{
		WorkloadID: id, SourceKind: "image", Reference: "v1", Outcome: "success",
	})
	// failure                      => not rollbackable
	e.store.InsertDeployHistory(store.DeployHistoryEntry{
		WorkloadID: id, SourceKind: "image", Reference: "v2", Outcome: "failure",
	})
	// success but empty reference  => not rollbackable
	e.store.InsertDeployHistory(store.DeployHistoryEntry{
		WorkloadID: id, SourceKind: "image", Reference: "", Outcome: "success",
	})

	resp := e.do(t, http.MethodGet, "/api/workloads/"+id+"/deploys", nil)
	if resp.StatusCode != http.StatusOK {
		t.Fatalf("status = %d, want 200", resp.StatusCode)
	}
	var rows []store.DeployHistoryEntry
	if errMsg := decodeEnvelope(t, resp, &rows); errMsg != "" {
		t.Fatalf("envelope error: %q", errMsg)
	}
	if len(rows) != 3 {
		t.Fatalf("expected 3 rows, got %d", len(rows))
	}
	// Newest-first: empty-ref success, failure, then v1 success.
	if !rows[2].Rollbackable {
		t.Fatalf("v1 success row should be rollbackable: %+v", rows[2])
	}
	if rows[1].Rollbackable || rows[0].Rollbackable {
		t.Fatalf("failure / empty-ref rows must not be rollbackable")
	}
}

func TestRollback_HappyPath_DispatchesRollbackIntent(t *testing.T) {
	e := newAPITestEnv(t)
	id := createImageWorkload(t, e, "app")
	entry, _ := e.store.InsertDeployHistory(store.DeployHistoryEntry{
		WorkloadID: id, SourceKind: "image", Reference: "v1", Outcome: "success",
	})

	before := e.dispatcher.deployCount.Load()
	resp := e.do(t, http.MethodPost, "/api/workloads/"+id+"/rollback",
		map[string]any{"deploy_id": entry.ID})
	if resp.StatusCode != http.StatusAccepted {
		errMsg := decodeEnvelope(t, resp, nil)
		t.Fatalf("status = %d, want 202 (err=%q)", resp.StatusCode, errMsg)
	}
	if got := e.dispatcher.deployCount.Load(); got != before+1 {
		t.Fatalf("expected one dispatch, got delta %d", got-before)
	}
	intent := e.dispatcher.lastIntent.Load()
	if intent == nil || intent.Reason != "rollback" || intent.Reference != "v1" {
		t.Fatalf("expected rollback intent for v1, got %+v", intent)
	}
}

func TestRollback_Guards(t *testing.T) {
	e := newAPITestEnv(t)
	imageID := createImageWorkload(t, e, "img")
	otherID := createImageWorkload(t, e, "other")

	success, _ := e.store.InsertDeployHistory(store.DeployHistoryEntry{
		WorkloadID: imageID, SourceKind: "image", Reference: "v1", Outcome: "success",
	})
	failed, _ := e.store.InsertDeployHistory(store.DeployHistoryEntry{
		WorkloadID: imageID, SourceKind: "image", Reference: "v2", Outcome: "failure",
	})
	otherWL, _ := e.store.InsertDeployHistory(store.DeployHistoryEntry{
		WorkloadID: otherID, SourceKind: "image", Reference: "v1", Outcome: "success",
	})

	cases := []struct {
		name     string
		workload string
		body     any
		wantCode int
	}{
		{"missing deploy_id", imageID, map[string]any{}, http.StatusBadRequest},
		{"zero deploy_id", imageID, map[string]any{"deploy_id": 0}, http.StatusBadRequest},
		{"unknown deploy_id", imageID, map[string]any{"deploy_id": 999999}, http.StatusNotFound},
		{"unknown workload", "nope", map[string]any{"deploy_id": success.ID}, http.StatusNotFound},
		{"failed deploy", imageID, map[string]any{"deploy_id": failed.ID}, http.StatusBadRequest},
		{"cross-workload entry", imageID, map[string]any{"deploy_id": otherWL.ID}, http.StatusBadRequest},
	}
	for _, c := range cases {
		t.Run(c.name, func(t *testing.T) {
			resp := e.do(t, http.MethodPost, "/api/workloads/"+c.workload+"/rollback", c.body)
			if resp.StatusCode != c.wantCode {
				errMsg := decodeEnvelope(t, resp, nil)
				t.Fatalf("status = %d, want %d (err=%q)", resp.StatusCode, c.wantCode, errMsg)
			}
		})
	}
}
