package rest_test

import (
	"encoding/json"
	"fmt"
	"strings"
	"testing"

	"github.com/pingcap/agent-git-service/internal/db"
	"github.com/pingcap/agent-git-service/internal/service"
	"github.com/pingcap/agent-git-service/internal/testharness"
)

func TestDispatchWorkflow_InvalidRef_Returns422(t *testing.T) {
	h := testharness.New(t)

	ctx := h.Tenant.Context()
	_, err := h.Svc.CreateRepo(ctx, service.CreateRepoInput{
		OwnerLogin: h.User.Login,
		Name:       "wf-invalid-ref-repo",
		AutoInit:   true,
	})
	if err != nil {
		t.Fatalf("CreateRepo: %v", err)
	}

	repo, err := h.Svc.GetRepo(ctx, h.User.Login+"/wf-invalid-ref-repo")
	if err != nil {
		t.Fatalf("GetRepo: %v", err)
	}

	// Seed a workflow directly in the DB.
	wf := db.Workflow{
		RepositoryID: repo.ID,
		Name:         "CI",
		Path:         ".github/workflows/ci.yml",
		State:        db.WorkflowActive,
	}
	if err := h.DB.Create(&wf).Error; err != nil {
		t.Fatalf("create workflow: %v", err)
	}
	if _, err := h.Tenant.Git().WriteFile(ctx, repo.FullName, repo.DefaultBranch, wf.Path, "add dispatch workflow", []byte("name: CI\non: workflow_dispatch\njobs: {}\n")); err != nil {
		t.Fatalf("seed default-branch workflow: %v", err)
	}

	// POST dispatch with a non-existent ref should return 422.
	path := fmt.Sprintf("/api/v3/repos/%s/wf-invalid-ref-repo/actions/workflows/%d/dispatches", h.User.Login, wf.ID)
	w := h.DoRESTJSON(t, "POST", path, map[string]any{"ref": "non-existent-branch"})

	if w.Code != 422 {
		t.Errorf("expected 422, got %d: %s", w.Code, w.Body.String())
	}
	var body map[string]any
	if err := json.Unmarshal(w.Body.Bytes(), &body); err != nil {
		t.Fatalf("decode response: %v", err)
	}
	message, _ := body["message"].(string)
	if !strings.Contains(message, "ref") || !strings.Contains(message, "not found") {
		t.Fatalf("expected invalid ref message, got %q", message)
	}

	// Verify no run was created.
	var count int64
	h.DB.Model(&db.WorkflowRun{}).Where("workflow_id = ?", wf.ID).Count(&count)
	if count != 0 {
		t.Errorf("expected 0 runs for invalid ref, got %d", count)
	}
}

func TestDispatchWorkflow_DefaultBranchIneligibleReturns422(t *testing.T) {
	h := testharness.New(t)
	ctx := h.Tenant.Context()
	_, err := h.Svc.CreateRepo(ctx, service.CreateRepoInput{
		OwnerLogin: h.User.Login,
		Name:       "wf-ineligible-repo",
		AutoInit:   true,
	})
	if err != nil {
		t.Fatal(err)
	}
	repo, err := h.Svc.GetRepo(ctx, h.User.Login+"/wf-ineligible-repo")
	if err != nil {
		t.Fatal(err)
	}
	wf := db.Workflow{
		RepositoryID: repo.ID,
		Name:         "Queue only",
		Path:         ".github/workflows/queue-only.yml",
		State:        db.WorkflowActive,
	}
	if err := h.DB.Create(&wf).Error; err != nil {
		t.Fatal(err)
	}

	path := fmt.Sprintf("/api/v3/repos/%s/wf-ineligible-repo/actions/workflows/%d/dispatches", h.User.Login, wf.ID)
	w := h.DoRESTJSON(t, "POST", path, map[string]any{"ref": "main"})
	if w.Code != 422 {
		t.Fatalf("expected 422, got %d: %s", w.Code, w.Body.String())
	}
	if !strings.Contains(w.Body.String(), "default branch") {
		t.Fatalf("expected default-branch eligibility message, got %s", w.Body.String())
	}
	var count int64
	if err := h.DB.Model(&db.WorkflowRun{}).Where("workflow_id = ?", wf.ID).Count(&count).Error; err != nil {
		t.Fatal(err)
	}
	if count != 0 {
		t.Fatalf("ineligible workflow created %d runs", count)
	}
}

func TestDispatchWorkflow_AliasedDefaultBranchTriggerAccepted(t *testing.T) {
	h := testharness.New(t)
	ctx := h.Tenant.Context()
	_, err := h.Svc.CreateRepo(ctx, service.CreateRepoInput{
		OwnerLogin: h.User.Login,
		Name:       "wf-aliased-dispatch-repo",
		AutoInit:   true,
	})
	if err != nil {
		t.Fatal(err)
	}
	repo, err := h.Svc.GetRepo(ctx, h.User.Login+"/wf-aliased-dispatch-repo")
	if err != nil {
		t.Fatal(err)
	}
	wf := db.Workflow{
		RepositoryID: repo.ID,
		Name:         "CI",
		Path:         ".github/workflows/ci.yml",
		State:        db.WorkflowActive,
	}
	if err := h.DB.Create(&wf).Error; err != nil {
		t.Fatal(err)
	}
	definition := []byte("name: CI\non:\n  workflow_call: &manual {}\n  workflow_dispatch: *manual\njobs: {}\n")
	if _, err := h.Tenant.Git().WriteFile(ctx, repo.FullName, repo.DefaultBranch, wf.Path, "add aliased dispatch workflow", definition); err != nil {
		t.Fatal(err)
	}

	path := fmt.Sprintf("/api/v3/repos/%s/wf-aliased-dispatch-repo/actions/workflows/%d/dispatches", h.User.Login, wf.ID)
	w := h.DoRESTJSON(t, "POST", path, map[string]any{"ref": repo.DefaultBranch})
	if w.Code != 204 {
		t.Fatalf("expected 204, got %d: %s", w.Code, w.Body.String())
	}
	var count int64
	if err := h.DB.Model(&db.WorkflowRun{}).Where("workflow_id = ?", wf.ID).Count(&count).Error; err != nil {
		t.Fatal(err)
	}
	if count != 1 {
		t.Fatalf("aliased workflow_dispatch created %d runs, want 1", count)
	}
}

func TestDispatchWorkflow_DisabledReturns422(t *testing.T) {
	h := testharness.New(t)

	ctx := h.Tenant.Context()
	_, err := h.Svc.CreateRepo(ctx, service.CreateRepoInput{
		OwnerLogin: h.User.Login,
		Name:       "wf-dispatch-repo",
		AutoInit:   true,
	})
	if err != nil {
		t.Fatalf("CreateRepo: %v", err)
	}

	repo, err := h.Svc.GetRepo(ctx, h.User.Login+"/wf-dispatch-repo")
	if err != nil {
		t.Fatalf("GetRepo: %v", err)
	}

	// Seed a disabled workflow.
	wf := db.Workflow{
		RepositoryID: repo.ID,
		Name:         "CI",
		Path:         ".github/workflows/ci.yml",
		State:        db.WorkflowDisabled,
	}
	if err := h.DB.Create(&wf).Error; err != nil {
		t.Fatalf("create workflow: %v", err)
	}

	// POST dispatch on a disabled workflow should return 422.
	path := fmt.Sprintf("/api/v3/repos/%s/wf-dispatch-repo/actions/workflows/%d/dispatches", h.User.Login, wf.ID)
	w := h.DoRESTJSON(t, "POST", path, map[string]any{"ref": "main"})

	if w.Code != 422 {
		t.Errorf("expected 422, got %d: %s", w.Code, w.Body.String())
	}
	var body map[string]any
	if err := json.Unmarshal(w.Body.Bytes(), &body); err != nil {
		t.Fatalf("decode response: %v", err)
	}
	message, _ := body["message"].(string)
	if !strings.Contains(message, "disabled") {
		t.Fatalf("expected disabled message, got %q", message)
	}
}

func TestOrganizationWorkflowRunsPaginateAcrossRepositories(t *testing.T) {
	h := testharness.New(t)
	ctx := h.Tenant.Context()

	org, err := h.Svc.EnsureOrg(ctx, "org-workflow-runs")
	if err != nil {
		t.Fatalf("EnsureOrg: %v", err)
	}
	repo, err := h.Svc.CreateRepo(ctx, service.CreateRepoInput{
		OwnerLogin: org.Login,
		Name:       "automation",
		AutoInit:   true,
	})
	if err != nil {
		t.Fatalf("CreateRepo: %v", err)
	}
	wf := db.Workflow{
		RepositoryID: repo.ID,
		Name:         "CI",
		Path:         ".github/workflows/ci.yml",
		State:        db.WorkflowActive,
	}
	if err := h.DB.Create(&wf).Error; err != nil {
		t.Fatalf("create workflow: %v", err)
	}
	for number := 1; number <= 2; number++ {
		run := db.WorkflowRun{
			RepositoryID: repo.ID,
			WorkflowID:   wf.ID,
			Name:         "CI",
			HeadBranch:   repo.DefaultBranch,
			HeadSHA:      fmt.Sprintf("org-workflow-%d", number),
			RunNumber:    number,
			RunAttempt:   1,
			Event:        "push",
			Status:       db.RunCompleted,
			Conclusion:   db.ConclusionSuccess,
		}
		if err := h.DB.Create(&run).Error; err != nil {
			t.Fatalf("create workflow run %d: %v", number, err)
		}
	}

	w := h.DoREST(t, "GET", "/api/ext/v1/orgs/org-workflow-runs/actions/runs?per_page=1", nil)
	assertStatusCode(t, w, 200)
	body := testharness.DecodeJSON(t, w)
	if got, ok := body["total_count"].(float64); !ok || got != 2 {
		t.Fatalf("total_count = %#v, want 2", body["total_count"])
	}
	runs, ok := body["workflow_runs"].([]any)
	if !ok || len(runs) != 1 {
		t.Fatalf("workflow_runs = %#v, want one paginated run", body["workflow_runs"])
	}
	first, ok := runs[0].(map[string]any)
	if !ok {
		t.Fatalf("workflow run is not an object: %#v", runs[0])
	}
	repository, ok := first["repository"].(map[string]any)
	if !ok || repository["full_name"] != repo.FullName {
		t.Fatalf("organization run repository = %#v, want %q", first["repository"], repo.FullName)
	}
	if link := w.Header().Get("Link"); !strings.Contains(link, "rel=\"next\"") {
		t.Fatalf("expected next-page Link header, got %q", link)
	}

	pageTwo := h.DoREST(t, "GET", "/api/ext/v1/orgs/org-workflow-runs/actions/runs?page=2&per_page=1", nil)
	assertStatusCode(t, pageTwo, 200)
	secondBody := testharness.DecodeJSON(t, pageTwo)
	secondRuns, ok := secondBody["workflow_runs"].([]any)
	if !ok || len(secondRuns) != 1 {
		t.Fatalf("page two workflow_runs = %#v, want one run", secondBody["workflow_runs"])
	}
}

func TestCreateRepositoryDispatch_AcceptsStringClientPayload(t *testing.T) {
	h := testharness.New(t)

	ctx := h.Tenant.Context()
	_, err := h.Svc.CreateRepo(ctx, service.CreateRepoInput{
		OwnerLogin: h.User.Login,
		Name:       "wf-repo-dispatch-repo",
		AutoInit:   true,
	})
	if err != nil {
		t.Fatalf("CreateRepo: %v", err)
	}

	path := fmt.Sprintf("/api/v3/repos/%s/wf-repo-dispatch-repo/dispatches", h.User.Login)
	w := h.DoREST(t, "POST", path, strings.NewReader(`{"event_type":"test_event","client_payload":"{\"key\":\"value\"}"}`))

	if w.Code != 204 {
		t.Fatalf("expected 204, got %d: %s", w.Code, w.Body.String())
	}
}
