package docs

import (
	"context"
	"encoding/json"
	"errors"
	"testing"

	"github.com/google/uuid"
	"github.com/stretchr/testify/require"

	"github.com/yan1h/agent-coding-workflow/internal/infra/git"
	"github.com/yan1h/agent-coding-workflow/internal/jobs"
)

type fakeDocsRepo struct {
	summaries map[string]*Summary // keyed by commitSHA|kind
	upserts   int
}

func key(commit, kind string) string { return commit + "|" + kind }

func newFakeDocsRepo() *fakeDocsRepo {
	return &fakeDocsRepo{summaries: map[string]*Summary{}}
}

func (r *fakeDocsRepo) Get(_ context.Context, _ uuid.UUID, commit, kind string) (*Summary, error) {
	return r.summaries[key(commit, kind)], nil
}
func (r *fakeDocsRepo) Upsert(_ context.Context, in UpsertInput) (*Summary, error) {
	r.upserts++
	s := &Summary{
		ID: uuid.New(), WorkspaceID: in.WorkspaceID, WorktreeID: in.WorktreeID,
		Branch: in.Branch, CommitSHA: in.CommitSHA, Kind: in.Kind, Title: in.Title,
		BodyMD: in.BodyMD, Model: in.Model, Status: in.Status, Error: in.Error,
	}
	r.summaries[key(in.CommitSHA, in.Kind)] = s
	return s, nil
}

type fakeLocator struct{}

func (fakeLocator) LocateWorkspace(context.Context, uuid.UUID) (string, error) { return "/tmp/x", nil }
func (fakeLocator) LocateWorktree(context.Context, uuid.UUID) (string, error)  { return "/tmp/x", nil }

// gitShim embeds git.Runner (nil) and overrides only DiffStat, the single method
// the SummaryRunner invokes. Any other call would panic, which never happens here.
type gitShim struct {
	git.Runner
	diff string
}

func (g gitShim) DiffStat(context.Context, string, string) (string, error) { return g.diff, nil }

type fakeSummarizer struct {
	title, body, model string
	err                error
}

func (s fakeSummarizer) Summarize(context.Context, string, string, string) (string, string, string, error) {
	return s.title, s.body, s.model, s.err
}

func newRunner(repo Repository, summ Summarizer, diff string) *SummaryRunner {
	return NewSummaryRunner(repo, fakeLocator{}, gitShim{diff: diff}, summ, nil)
}

func TestSummaryRunner_Success(t *testing.T) {
	repo := newFakeDocsRepo()
	wsID := uuid.New()
	r := newRunner(repo, fakeSummarizer{title: "Add feature X", body: "# Add feature X\n\n- did stuff", model: "m1"}, "f.go | 2 +-")

	payload, _ := json.Marshal(summaryPayload{WorkspaceID: wsID, Branch: "main", CommitSHA: "abc"})
	require.NoError(t, r.Handle(context.Background(), jobs.Job{Payload: payload}))

	s := repo.summaries[key("abc", KindCommit)]
	require.NotNil(t, s)
	require.Equal(t, StatusCompleted, s.Status)
	require.Equal(t, "# Add feature X\n\n- did stuff", s.BodyMD)
	require.NotNil(t, s.Title)
	require.Equal(t, "Add feature X", *s.Title)
	require.NotNil(t, s.Model)
	require.Equal(t, "m1", *s.Model)
}

func TestSummaryRunner_IdempotentWhenCompleted(t *testing.T) {
	repo := newFakeDocsRepo()
	wsID := uuid.New()
	repo.summaries[key("abc", KindCommit)] = &Summary{CommitSHA: "abc", Kind: KindCommit, Status: StatusCompleted}

	r := newRunner(repo, fakeSummarizer{title: "x", body: "y", model: "m"}, "stat")
	payload, _ := json.Marshal(summaryPayload{WorkspaceID: wsID, Branch: "main", CommitSHA: "abc"})
	require.NoError(t, r.Handle(context.Background(), jobs.Job{Payload: payload}))
	require.Equal(t, 0, repo.upserts, "completed summary must not be regenerated")
}

func TestSummaryRunner_FailurePersistsFailedRow(t *testing.T) {
	repo := newFakeDocsRepo()
	wsID := uuid.New()
	r := newRunner(repo, fakeSummarizer{err: errors.New("llm down")}, "stat")
	payload, _ := json.Marshal(summaryPayload{WorkspaceID: wsID, Branch: "main", CommitSHA: "abc"})
	err := r.Handle(context.Background(), jobs.Job{Payload: payload})
	require.Error(t, err)
	s := repo.summaries[key("abc", KindCommit)]
	require.NotNil(t, s)
	require.Equal(t, StatusFailed, s.Status)
	require.NotNil(t, s.Error)
}

var _ Repository = (*fakeDocsRepo)(nil)
