package runtime

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

	agsserver "github.com/pingcap/agent-git-service/server"
	"github.com/stretchr/testify/require"
)

type fakeAGSSnapshotServer struct {
	grepRequest agsserver.RepositorySnapshotGrepRequest
	readRequest agsserver.RepositorySnapshotReadRequest
	grepError   error
	readError   error
	refRequest  agsserver.RepositorySnapshotRefRequest
	refSHA      string
	refError    error
}

func (server *fakeAGSSnapshotServer) ResolveRepositorySnapshotRef(_ context.Context, request agsserver.RepositorySnapshotRefRequest) (string, error) {
	server.refRequest = request
	if server.refError != nil {
		return "", server.refError
	}
	if server.refSHA != "" {
		return server.refSHA, nil
	}
	return strings.Repeat("a", 40), nil
}

func (server *fakeAGSSnapshotServer) ListRepositorySnapshot(_ context.Context, request agsserver.RepositorySnapshotListRequest, emit func(agsserver.RepositorySnapshotListEntry) error) error {
	return emit(agsserver.RepositorySnapshotListEntry{Path: request.Path + "/policy.md", Kind: "file"})
}

func (server *fakeAGSSnapshotServer) GrepRepositorySnapshot(_ context.Context, request agsserver.RepositorySnapshotGrepRequest, emit func(agsserver.RepositorySnapshotGrepMatch) error) error {
	server.grepRequest = request
	if server.grepError != nil {
		return server.grepError
	}
	return emit(agsserver.RepositorySnapshotGrepMatch{Path: "docs/policy.md", Line: 7, Preview: "managed"})
}

func (server *fakeAGSSnapshotServer) ReadRepositorySnapshot(_ context.Context, request agsserver.RepositorySnapshotReadRequest, emit func(agsserver.RepositorySnapshotReadChunk) error) error {
	server.readRequest = request
	if server.readError != nil {
		return server.readError
	}
	return emit(agsserver.RepositorySnapshotReadChunk{Content: []byte("managed\n"), LineStart: 7, LineEnd: 7})
}

func TestAGSRepositoryPrimitiveKeepsRepositoryAddressServerOnly(t *testing.T) {
	reference, err := NewAGSRepositoryRef("workspace/runbooks.kb", RootKindInsights, RepoRoleInsights)
	require.NoError(t, err)
	require.Equal(t, "ags-wiki:workspace/runbooks.kb", reference)

	server := &fakeAGSSnapshotServer{}
	primitive := AGSRepositoryPrimitive{Server: server}
	var match RepositoryGrepMatch
	err = primitive.Grep(context.Background(), RepositorySnapshot{
		RepositoryRef: reference,
		Revision:      "0123456789012345678901234567890123456789",
	}, RepositoryGrepRequest{Pattern: "managed", PathGlobs: []string{"docs/*.md"}}, func(value RepositoryGrepMatch) error {
		match = value
		return nil
	})
	require.NoError(t, err)
	require.Equal(t, "workspace/runbooks.kb.wiki", server.grepRequest.RepositoryFullName)
	require.Equal(t, "0123456789012345678901234567890123456789", server.grepRequest.CommitSHA)
	require.Equal(t, "docs/policy.md", match.Path)

	var chunk RepositoryReadChunk
	err = primitive.Read(context.Background(), RepositorySnapshot{
		RepositoryRef: "ags-repo:workspace/source",
		Revision:      "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
	}, RepositoryReadRequest{Path: "docs/policy.md", LineStart: 7, LineEnd: 7}, func(value RepositoryReadChunk) error {
		chunk = value
		return nil
	})
	require.NoError(t, err)
	require.Equal(t, "workspace/source", server.readRequest.RepositoryFullName)
	require.Equal(t, []byte("managed\n"), chunk.Content)

	var entry RepositoryListEntry
	err = primitive.List(context.Background(), RepositorySnapshot{
		RepositoryRef: "ags-repo:workspace/source",
		Revision:      "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
	}, RepositoryListRequest{Path: "docs"}, func(value RepositoryListEntry) error {
		entry = value
		return nil
	})
	require.NoError(t, err)
	require.Equal(t, RepositoryListEntry{Path: "docs/policy.md", Kind: "file"}, entry)
}

func TestAGSRepositoryPrimitiveResolvesTypedRefInsideServerRepository(t *testing.T) {
	server := &fakeAGSSnapshotServer{refSHA: strings.Repeat("b", 40)}
	primitive := AGSRepositoryPrimitive{Server: server}

	sha, err := primitive.ResolveRef(context.Background(), RepositorySnapshot{
		RepositoryRef: "ags-repo:pingcap/tidb",
	}, "v7.1.9-0", RefKindTag)

	require.NoError(t, err)
	require.Equal(t, strings.Repeat("b", 40), sha)
	require.Equal(t, agsserver.RepositorySnapshotRefRequest{
		RepositoryFullName: "pingcap/tidb", RefName: "v7.1.9-0", RefType: RefKindTag,
	}, server.refRequest)
}

func TestAGSRepositoryPrimitiveMapsRefErrors(t *testing.T) {
	for _, test := range []struct {
		upstream error
		want     error
	}{
		{agsserver.ErrRepositorySnapshotRefNotFound, ErrRepositoryRefNotFound},
		{agsserver.ErrRepositorySnapshotRefInvalid, ErrRepositoryRefInvalid},
	} {
		primitive := AGSRepositoryPrimitive{Server: &fakeAGSSnapshotServer{refError: test.upstream}}
		_, err := primitive.ResolveRef(context.Background(), RepositorySnapshot{
			RepositoryRef: "ags-repo:pingcap/tidb",
		}, "release", RefKindBranch)
		require.ErrorIs(t, err, test.want)
	}
}

func TestAGSRepositoryPrimitiveRejectsForgedAddressAndMapsEscape(t *testing.T) {
	server := &fakeAGSSnapshotServer{}
	primitive := AGSRepositoryPrimitive{Server: server}
	err := primitive.Read(context.Background(), RepositorySnapshot{
		RepositoryRef: "ags-repo:../../secret",
		Revision:      "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
	}, RepositoryReadRequest{Path: "secret", LineStart: 1, LineEnd: 1}, func(RepositoryReadChunk) error {
		return nil
	})
	require.ErrorIs(t, err, ErrRepositoryEscape)

	server.readError = agsserver.ErrRepositorySnapshotEscape
	err = primitive.Read(context.Background(), RepositorySnapshot{
		RepositoryRef: "ags-repo:workspace/source",
		Revision:      "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
	}, RepositoryReadRequest{Path: "link", LineStart: 1, LineEnd: 1}, func(RepositoryReadChunk) error {
		return nil
	})
	require.True(t, errors.Is(err, ErrRepositoryEscape))
}

func TestAGSRepositoryPrimitiveMapsHostResourceExhaustion(t *testing.T) {
	server := &fakeAGSSnapshotServer{
		grepError: agsserver.ErrRepositorySnapshotResourceExhausted,
	}
	primitive := AGSRepositoryPrimitive{Server: server}
	err := primitive.Grep(context.Background(), RepositorySnapshot{
		RepositoryRef: "ags-repo:workspace/source",
		Revision:      "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
	}, RepositoryGrepRequest{Pattern: "policy"}, func(RepositoryGrepMatch) error {
		return nil
	})
	require.ErrorIs(t, err, ErrRepositoryExhausted)
}
