package roadmapsync

import (
	"context"
	"errors"
	"strings"
	"testing"

	"github.com/nomadcode/nomadcode-core/internal/workitem"
)

type fakeProvider struct {
	calls       []string
	commentBody string

	commentErr error
	bodyErr    error
	statusErr  error

	bodyProjection workitem.BodyProjection
	statusState    string
}

func (f *fakeProvider) AppendComment(_ context.Context, in workitem.CommentInput) error {
	f.calls = append(f.calls, "comment")
	f.commentBody = in.Body
	return f.commentErr
}

func (f *fakeProvider) ProjectBody(_ context.Context, in workitem.BodyProjection) error {
	f.calls = append(f.calls, "body")
	f.bodyProjection = in
	return f.bodyErr
}

func (f *fakeProvider) ProjectStatus(_ context.Context, in workitem.StatusProjection) error {
	f.calls = append(f.calls, "status")
	f.statusState = in.ProviderState
	return f.statusErr
}

func readyInput() ProjectPlaneTodoInput {
	return ProjectPlaneTodoInput{
		Candidate: ProjectionCandidate{
			Ready: true,
			Identity: Identity{
				RoadmapMilestonePath: "agent-roadmap/phase/p/milestones/m-foo.md",
			},
		},
		Ref:               workitem.Ref{Provider: "plane", Tenant: "general", Project: "p", ID: "work-1"},
		OriginalBody:      "<p>original request</p>",
		TodoStateID:       "todo-state",
		MilestoneMarkdown: "# Foo Milestone\n\n## 목표\n\n- 목표\n",
		ExternalSource:    "nomadcode",
		ExternalID:        "task-1",
	}
}

func TestProjectPlaneTodoSuccessOrderAndPreservation(t *testing.T) {
	f := &fakeProvider{}
	if err := ProjectPlaneTodo(context.Background(), f, readyInput()); err != nil {
		t.Fatalf("ProjectPlaneTodo returned error: %v", err)
	}
	if got := strings.Join(f.calls, ","); got != "comment,body,status" {
		t.Fatalf("call order: got %q", got)
	}
	if !strings.HasPrefix(f.commentBody, preserveCommentPrefix) {
		t.Errorf("comment must preserve original body with prefix: %q", f.commentBody)
	}
	if !strings.Contains(f.commentBody, "original request") {
		t.Errorf("comment must contain original body: %q", f.commentBody)
	}
	if f.statusState != "todo-state" {
		t.Errorf("status: got %q", f.statusState)
	}
	if !strings.HasPrefix(f.bodyProjection.Title, "[m-foo]") {
		t.Errorf("body title should use slug prefix: %q", f.bodyProjection.Title)
	}
}

func TestProjectPlaneTodoAllowsEmptyOriginalBody(t *testing.T) {
	f := &fakeProvider{}
	in := readyInput()
	in.OriginalBody = "  "

	if err := ProjectPlaneTodo(context.Background(), f, in); err != nil {
		t.Fatalf("ProjectPlaneTodo returned error: %v", err)
	}
	if got := strings.Join(f.calls, ","); got != "comment,body,status" {
		t.Fatalf("call order: got %q", got)
	}
	if f.commentBody != preserveCommentPrefix {
		t.Fatalf("comment body = %q, want %q", f.commentBody, preserveCommentPrefix)
	}
}

func TestProjectPlaneTodoStopsWhenCommentFails(t *testing.T) {
	f := &fakeProvider{commentErr: errors.New("comment boom")}
	err := ProjectPlaneTodo(context.Background(), f, readyInput())
	if err == nil || !strings.Contains(err.Error(), "comment boom") {
		t.Fatalf("expected comment error, got %v", err)
	}
	if got := strings.Join(f.calls, ","); got != "comment" {
		t.Fatalf("only comment should run: got %q", got)
	}
}

func TestProjectPlaneTodoStopsWhenBodyFails(t *testing.T) {
	f := &fakeProvider{bodyErr: errors.New("body boom")}
	err := ProjectPlaneTodo(context.Background(), f, readyInput())
	if err == nil || !strings.Contains(err.Error(), "body boom") {
		t.Fatalf("expected body error, got %v", err)
	}
	if got := strings.Join(f.calls, ","); got != "comment,body" {
		t.Fatalf("status must not run after body failure: got %q", got)
	}
}

func TestProjectPlaneTodoNotReadyDoesNotCallProvider(t *testing.T) {
	f := &fakeProvider{}
	in := readyInput()
	in.Candidate.Ready = false
	err := ProjectPlaneTodo(context.Background(), f, in)
	if !errors.Is(err, ErrNotReady) {
		t.Fatalf("expected ErrNotReady, got %v", err)
	}
	if len(f.calls) != 0 {
		t.Fatalf("provider must not be called when not ready: %v", f.calls)
	}
}

func TestProjectPlaneTodoRejectsMissingInput(t *testing.T) {
	f := &fakeProvider{}
	in := readyInput()
	in.TodoStateID = "  "
	err := ProjectPlaneTodo(context.Background(), f, in)
	if !errors.Is(err, ErrInvalidProjectionInput) {
		t.Fatalf("expected ErrInvalidProjectionInput, got %v", err)
	}
	if len(f.calls) != 0 {
		t.Fatalf("provider must not be called on invalid input: %v", f.calls)
	}
}
