package memory

import (
	"context"
	"encoding/base64"
	"encoding/json"
	"net/http"
	"strings"
	"testing"

	gitplumbing "github.com/go-git/go-git/v5/plumbing"
	"github.com/stretchr/testify/require"
	"github.com/tidbcloud/AgentTeam/internal/service/resource/repos"
)

func TestAGSPreflightMemoryMigrationReadsExactBoundedTrees(t *testing.T) {
	root := migrationTestSHA(10)
	head := migrationTestSHA(11)
	rootTree := migrationTestSHA(20)
	headTree := migrationTestSHA(21)
	rootData := []byte("root\n")
	headData := []byte("head\n")
	rootBlob := gitplumbing.ComputeHash(gitplumbing.BlobObject, rootData).String()
	headBlob := gitplumbing.ComputeHash(gitplumbing.BlobObject, headData).String()

	handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		switch r.URL.Path {
		case "/api/v3/repos/owner/memory/git/refs/heads/main":
			writeMemoryMigrationJSON(t, w, map[string]any{"object": map[string]any{"sha": head, "type": "commit"}})
		case "/api/v3/repos/owner/memory/releases":
			require.Equal(t, "65", r.URL.Query().Get("per_page"))
			writeMemoryMigrationJSON(t, w, []map[string]any{{"tag_name": "backup-root"}})
		case "/api/v3/repos/owner/memory/git/refs/tags/backup-root":
			writeMemoryMigrationJSON(t, w, map[string]any{"object": map[string]any{"sha": root, "type": "commit"}})
		case "/api/v3/repos/owner/memory/git/tags/" + root:
			http.NotFound(w, r)
		case "/api/v3/repos/owner/memory/git/commits/" + head:
			writeMemoryMigrationJSON(t, w, map[string]any{
				"sha": head, "message": "head", "tree": map[string]any{"sha": headTree},
				"parents": []map[string]any{{"sha": root}}, "author": map[string]any{"name": "A", "email": "a@example.com", "date": "2026-08-22T00:00:00Z"},
			})
		case "/api/v3/repos/owner/memory/git/commits/" + root:
			writeMemoryMigrationJSON(t, w, map[string]any{
				"sha": root, "message": "root", "tree": map[string]any{"sha": rootTree}, "parents": []any{},
				"author": map[string]any{"name": "B", "email": "b@example.com", "date": "2026-08-21T00:00:00Z"},
			})
		case "/api/v3/repos/owner/memory/git/trees/" + headTree:
			require.Equal(t, "1", r.URL.Query().Get("recursive"))
			writeMemoryMigrationJSON(t, w, map[string]any{
				"sha": headTree, "truncated": false,
				"tree": []map[string]any{{"path": "notes", "mode": "040000", "type": "tree", "sha": migrationTestSHA(30)}, {"path": "MEMORY.md", "mode": "100644", "type": "blob", "sha": headBlob, "size": len(headData)}},
			})
		case "/api/v3/repos/owner/memory/git/trees/" + rootTree:
			writeMemoryMigrationJSON(t, w, map[string]any{
				"sha": rootTree, "truncated": false,
				"tree": []map[string]any{{"path": "MEMORY.md", "mode": "100644", "type": "blob", "sha": rootBlob, "size": len(rootData)}},
			})
		case "/api/v3/repos/owner/memory/git/blobs/" + headBlob:
			writeMemoryMigrationJSON(t, w, map[string]any{"sha": headBlob, "encoding": "base64", "content": base64.StdEncoding.EncodeToString(headData)})
		case "/api/v3/repos/owner/memory/git/blobs/" + rootBlob:
			writeMemoryMigrationJSON(t, w, map[string]any{"sha": rootBlob, "encoding": "base64", "content": base64.StdEncoding.EncodeToString(rootData)})
		default:
			t.Fatalf("unexpected AGS request %s %s", r.Method, r.URL.String())
		}
	})
	source := AGSHTTPRepositoryStore{Handler: handler}
	plan, err := PreflightMemoryMigration(context.Background(), source, MemoryMigrationPreflightRequest{
		Repository: repos.RepositoryIdentity{ID: "repo_1", Owner: "owner", Name: "memory", DefaultBranch: "main"},
	})
	require.NoError(t, err)
	require.Equal(t, []string{root, head}, migrationPlanSHAs(plan.Versions))
	require.EqualValues(t, 1, plan.PinCount)
	require.Equal(t, root, plan.References[0].CommitSHA)
	require.Equal(t, int64(len(rootData)+len(headData)), plan.PayloadBytes)
}

func TestAGSReadMemoryMigrationSnapshotRejectsSpecialEntries(t *testing.T) {
	commit := migrationTestSHA(40)
	tree := migrationTestSHA(41)
	handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		switch r.URL.Path {
		case "/api/v3/repos/owner/memory/git/commits/" + commit:
			writeMemoryMigrationJSON(t, w, map[string]any{"sha": commit, "tree": map[string]any{"sha": tree}, "parents": []any{}})
		case "/api/v3/repos/owner/memory/git/trees/" + tree:
			writeMemoryMigrationJSON(t, w, map[string]any{
				"sha": tree, "truncated": false,
				"tree": []map[string]any{{"path": "link", "mode": "120000", "type": "blob", "sha": migrationTestSHA(42), "size": 4}},
			})
		default:
			t.Fatalf("unexpected AGS request %s", r.URL.String())
		}
	})
	_, err := (AGSHTTPRepositoryStore{Handler: handler}).ReadMemoryMigrationSnapshot(
		context.Background(),
		repos.RepositoryIdentity{ID: "repo_1", Owner: "owner", Name: "memory"},
		commit,
		memoryContentTreeLimits,
	)
	require.ErrorContains(t, err, "symlink or special")
}

func TestAGSInProcessResponseIsBoundedWhileHandlerWrites(t *testing.T) {
	source := AGSHTTPRepositoryStore{Handler: http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
		_, _ = w.Write([]byte(strings.Repeat("x", 128)))
	})}
	_, body, err := source.doAGSAPIWithLimit(context.Background(), http.MethodGet, "/large", nil, 16)
	require.ErrorContains(t, err, "exceeds 16 bytes")
	require.Nil(t, body)
}

func writeMemoryMigrationJSON(t *testing.T, w http.ResponseWriter, value any) {
	t.Helper()
	w.Header().Set("Content-Type", "application/json")
	require.NoError(t, json.NewEncoder(w).Encode(value))
}
