package pierre

import (
	"bytes"
	"context"
	"io"
	"strings"
	"testing"

	"git.schreifuchs.ch/schreifuchs/pierre-bot/internal/chatter"
	"github.com/google/go-cmp/cmp"
)

// mockChat implements the ChatAdapter interface for testing.
type mockChat struct{ callCount int }

func (m *mockChat) GenerateStructured(ctx context.Context, msgs []chatter.Message, target interface{}) error {
	m.callCount++
	if cSlice, ok := target.(*[]Comment); ok {
		*cSlice = []Comment{{File: "file.go", Line: 1, Message: "test comment"}}
		return nil
	}
	return nil
}

func (m *mockChat) GetProviderName() string { return "mock" }

// mockGit implements the GitAdapter interface for testing.
type mockGit struct{}

func (g *mockGit) GetDiff(ctx context.Context, owner, repo string, prID int) (io.ReadCloser, error) {
	diff := "diff --git a/file1.go b/file1.go\n+line1\n" + "diff --git a/file2.go b/file2.go\n+line2\n"
	return io.NopCloser(bytes.NewReader([]byte(diff))), nil
}

func (g *mockGit) AddComment(ctx context.Context, owner, repo string, prID int, comment Comment) error {
	return nil
}

func (g *mockGit) GetFileContent(ctx context.Context, owner, repo, path, ref string) (string, error) {
	// For tests, return a simple placeholder content.
	return "package main\n\nfunc placeholder() {}", nil
}

func (g *mockGit) GetPRHeadSHA(ctx context.Context, owner, repo string, prID int) (string, error) {
	return "dummysha", nil
}

func TestSplitDiffIntoChunks(t *testing.T) {
	cases := []struct {
		name           string
		diff           string
		maxSize        int
		wantChunks     int // 0 means we don't assert exact count
		wantPrefixes   []string
		checkRecombine bool
	}{
		{
			name:           "small diff",
			diff:           "diff --git a/file1.txt b/file1.txt\n+added line\n",
			maxSize:        1000,
			wantChunks:     1,
			wantPrefixes:   []string{"diff --git a/file1.txt"},
			checkRecombine: true,
		},
		{
			name: "multiple files",
			diff: "diff --git a/file1.txt b/file1.txt\n+added line 1\n" +
				"diff --git a/file2.txt b/file2.txt\n+added line 2\n",
			maxSize:        50,
			wantChunks:     2,
			wantPrefixes:   []string{"diff --git a/file1.txt", "diff --git a/file2.txt"},
			checkRecombine: false,
		},
		{
			name: "large single file",
			diff: func() string {
				line := "+very long added line that will be repeated many times to exceed the chunk size\n"
				return "diff --git a/large.txt b/large.txt\n" + strings.Repeat(line, 200)
			}(),
			maxSize:        500,
			wantChunks:     0,
			wantPrefixes:   nil,
			checkRecombine: true,
		},
	}

	for _, tc := range cases {
		t.Run(tc.name, func(t *testing.T) {
			chunks := splitDiffIntoChunks([]byte(tc.diff), tc.maxSize)
			if tc.wantChunks > 0 && len(chunks) != tc.wantChunks {
				t.Fatalf("expected %d chunks, got %d", tc.wantChunks, len(chunks))
			}
			for i, prefix := range tc.wantPrefixes {
				if i >= len(chunks) {
					t.Fatalf("missing chunk %d for prefix check", i)
				}
				trimmed := strings.TrimPrefix(chunks[i], "\n")
				if !strings.HasPrefix(trimmed, prefix) {
					t.Fatalf("chunk %d does not start with expected prefix %q: %s", i, prefix, chunks[i])
				}
			}
			for i, c := range chunks {
				if tc.maxSize > 0 && len(c) > tc.maxSize {
					t.Fatalf("chunk %d exceeds max size %d: %d", i, tc.maxSize, len(c))
				}
			}
			if tc.checkRecombine {
				recombined := strings.Join(chunks, "")
				if diff := cmp.Diff(tc.diff, recombined); diff != "" {
					t.Fatalf("recombined diff differs:\n%s", diff)
				}
			}
		})
	}
}

func TestJudgePR_ChunkAggregationAndDeduplication(t *testing.T) {
	chatMock := &mockChat{}
	svc := &Service{
		maxChunkSize: 50,
		guidelines:   nil,
		git:          &mockGit{},
		chat:         chatMock,
	}
	diffReader, err := svc.git.GetDiff(context.Background(), "", "", 0)
	if err != nil {
		t.Fatalf("failed to get diff: %v", err)
	}
	defer diffReader.Close()
	comments, err := svc.judgePR(context.Background(), diffReader)
	if err != nil {
		t.Fatalf("judgePR error: %v", err)
	}
	if got, want := len(comments), 1; got != want {
		t.Fatalf("expected %d comment after deduplication, got %d", want, got)
	}
	if chatMock.callCount != 2 {
		t.Fatalf("expected mockChat to be called for each chunk (2), got %d", chatMock.callCount)
	}
}
