package automation

import (
	"context"
	"encoding/base64"
	"encoding/json"
	"net/http"
	"net/http/httptest"
	"os"
	"path/filepath"
	"testing"

	"github.com/stretchr/testify/require"
	"github.com/tidbcloud/AgentTeam/internal/service/automation/runtime"
	"github.com/tidbcloud/AgentTeam/internal/service/kb"
)

func TestAGSAutomationTreePublisherPublishesRepositoryTreeThroughGitDatabase(t *testing.T) {
	root := t.TempDir()
	require.NoError(t, os.WriteFile(filepath.Join(root, "AGENTS.md"), []byte("# Agents\n"), 0o644))
	require.NoError(t, os.WriteFile(filepath.Join(root, ".codecov.yml"), []byte("coverage: {}\n"), 0o644))
	require.NoError(t, os.MkdirAll(filepath.Join(root, "br"), 0o755))
	require.NoError(t, os.WriteFile(filepath.Join(root, "br", "run_sh.md"), []byte("# Run\n"), 0o644))
	require.NoError(t, os.MkdirAll(filepath.Join(root, ".git"), 0o755))
	require.NoError(t, os.WriteFile(filepath.Join(root, ".git", "config"), []byte("[core]\n"), 0o644))
	store := &fakeAGSRepositoryStore{}
	var requests []string
	handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		require.NotContains(t, r.URL.Path, "/wiki/")
		requests = append(requests, r.Method+" "+r.URL.Path)
		switch {
		case r.Method == http.MethodGet && r.URL.Path == "/api/v3/repos/pingcap/tidb.insights/git/refs/heads/main":
			require.NoError(t, json.NewEncoder(w).Encode(map[string]any{"object": map[string]string{"sha": "head-sha"}}))
		case r.Method == http.MethodGet && r.URL.Path == "/api/v3/repos/pingcap/tidb.insights/git/commits/head-sha":
			require.NoError(t, json.NewEncoder(w).Encode(map[string]any{"sha": "head-sha", "tree": map[string]string{"sha": "old-tree"}}))
		case r.Method == http.MethodPost && r.URL.Path == "/api/v3/repos/pingcap/tidb.insights/git/trees":
			var body struct {
				Tree []struct {
					Path    string `json:"path"`
					Mode    string `json:"mode"`
					Type    string `json:"type"`
					Content string `json:"content"`
				} `json:"tree"`
			}
			require.NoError(t, json.NewDecoder(r.Body).Decode(&body))
			require.Len(t, body.Tree, 3)
			paths := make([]string, 0, len(body.Tree))
			for _, entry := range body.Tree {
				require.Equal(t, "100644", entry.Mode)
				require.Equal(t, "blob", entry.Type)
				paths = append(paths, entry.Path)
			}
			require.ElementsMatch(t, []string{".codecov.yml", "AGENTS.md", "br/run_sh.md"}, paths)
			w.WriteHeader(http.StatusCreated)
			require.NoError(t, json.NewEncoder(w).Encode(map[string]string{"sha": "new-tree"}))
		case r.Method == http.MethodPost && r.URL.Path == "/api/v3/repos/pingcap/tidb.insights/git/commits":
			var body map[string]any
			require.NoError(t, json.NewDecoder(r.Body).Decode(&body))
			require.Equal(t, "Publish tidb.insights tree", body["message"])
			require.Equal(t, "new-tree", body["tree"])
			require.Equal(t, []any{"head-sha"}, body["parents"])
			w.WriteHeader(http.StatusCreated)
			require.NoError(t, json.NewEncoder(w).Encode(map[string]string{"sha": "new-commit"}))
		case r.Method == http.MethodPatch && r.URL.Path == "/api/v3/repos/pingcap/tidb.insights/git/refs/heads/main":
			var body map[string]any
			require.NoError(t, json.NewDecoder(r.Body).Decode(&body))
			require.Equal(t, "new-commit", body["sha"])
			require.Equal(t, false, body["force"])
			require.NoError(t, json.NewEncoder(w).Encode(map[string]any{"object": map[string]string{"sha": "new-commit"}}))
		default:
			require.Failf(t, "unexpected request", "%s %s", r.Method, r.URL.Path)
		}
	})
	publisher := AGSAutomationTreePublisher{Store: store, Handler: handler}

	result, err := publisher.PublishRepositoryTree(t.Context(), runtime.TreePublishRequest{
		WorkspaceID:    "ws-1",
		RunID:          "wfr-1",
		Repository:     "pingcap/tidb.insights",
		SourcePath:     root,
		Description:    "Generated TiDB insights",
		Message:        "Publish tidb.insights tree",
		MaxFiles:       10,
		DefaultBranch:  "main",
		Visibility:     "private",
		RepositoryKind: "automation_preset_insights",
	})

	require.NoError(t, err)
	require.Equal(t, runtime.TreePublishResult{Repository: "pingcap/tidb.insights", Files: 3, Bytes: int64(len("# Agents\n") + len("coverage: {}\n") + len("# Run\n")), CommitSHA: "new-commit"}, result)
	require.Len(t, store.ensureRequests, 1)
	require.Equal(t, "tree", store.ensureRequests[0].RepoRole)
	require.Equal(t, "automation_preset_insights", store.ensureRequests[0].Kind)
	require.False(t, store.ensureRequests[0].Features.Wiki)
	require.Equal(t, []string{
		"GET /api/v3/repos/pingcap/tidb.insights/git/refs/heads/main",
		"GET /api/v3/repos/pingcap/tidb.insights/git/commits/head-sha",
		"POST /api/v3/repos/pingcap/tidb.insights/git/trees",
		"POST /api/v3/repos/pingcap/tidb.insights/git/commits",
		"PATCH /api/v3/repos/pingcap/tidb.insights/git/refs/heads/main",
	}, requests)
}

func TestAGSAutomationTreePublisherPublishesThroughRealAGS(t *testing.T) {
	ags, token := newAGSContractServer(t)
	root := t.TempDir()
	require.NoError(t, os.WriteFile(filepath.Join(root, "AGENTS.md"), []byte("# Agents\n"), 0o644))
	require.NoError(t, os.MkdirAll(filepath.Join(root, "br"), 0o755))
	require.NoError(t, os.WriteFile(filepath.Join(root, "br", "run_sh.md"), []byte("# Run\n"), 0o644))
	require.NoError(t, os.MkdirAll(filepath.Join(root, ".git"), 0o755))
	require.NoError(t, os.WriteFile(filepath.Join(root, ".git", "config"), []byte("[core]\n"), 0o644))
	auth := "token " + token
	store := kb.AGSHTTPRepositoryStore{Handler: ags.Handler(), AuthorizationHeader: auth}
	publisher := AGSAutomationTreePublisher{Store: store, Handler: ags.Handler(), AuthorizationHeader: auth}

	result, err := publisher.PublishRepositoryTree(t.Context(), runtime.TreePublishRequest{
		WorkspaceID:    "ws-tree",
		RunID:          "wfr-tree",
		Repository:     "ws_tree/tidb.kb",
		SourcePath:     root,
		Message:        "publish insights tree",
		RepositoryKind: "automation_preset_insights",
	})

	require.NoError(t, err)
	require.Equal(t, "ws_tree/tidb.kb", result.Repository)
	require.Equal(t, 2, result.Files)
	require.Len(t, result.CommitSHA, 40)
	content := agsJSON(t, ags.Handler(), http.MethodGet, "/api/v3/repos/ws_tree/tidb.kb/contents/br/run_sh.md", token, nil, http.StatusOK)
	encoded, _ := content["content"].(string)
	decoded, err := base64.StdEncoding.DecodeString(removeASCIIWhitespace(encoded))
	require.NoError(t, err)
	require.Equal(t, "# Run\n", string(decoded))

	req := httptest.NewRequest(http.MethodGet, "/api/v3/repos/ws_tree/tidb.kb/contents/.git/config", nil)
	req.Header.Set("Authorization", auth)
	rec := httptest.NewRecorder()
	ags.Handler().ServeHTTP(rec, req)
	require.Equal(t, http.StatusNotFound, rec.Code)
}

func TestAGSAutomationTreePublisherRejectsConfiguredFileLimit(t *testing.T) {
	root := t.TempDir()
	require.NoError(t, os.WriteFile(filepath.Join(root, "one.md"), []byte("# One\n"), 0o644))
	require.NoError(t, os.WriteFile(filepath.Join(root, "two.md"), []byte("# Two\n"), 0o644))
	publisher := AGSAutomationTreePublisher{Store: &fakeAGSRepositoryStore{}, Handler: http.NotFoundHandler(), MaxFiles: 1}

	_, err := publisher.PublishRepositoryTree(context.Background(), runtime.TreePublishRequest{
		Repository: "pingcap/tidb.insights",
		SourcePath: root,
	})

	require.Error(t, err)
	require.Contains(t, err.Error(), "exceeds 1 files")
}

func TestAGSAutomationTreePublisherRejectsNonUTF8Files(t *testing.T) {
	root := t.TempDir()
	require.NoError(t, os.WriteFile(filepath.Join(root, "binary.bin"), []byte{0xff, 0xfe, 0x00}, 0o644))
	publisher := AGSAutomationTreePublisher{Store: &fakeAGSRepositoryStore{}, Handler: http.NotFoundHandler()}

	_, err := publisher.PublishRepositoryTree(context.Background(), runtime.TreePublishRequest{
		Repository: "pingcap/tidb.insights",
		SourcePath: root,
	})

	require.Error(t, err)
	require.Contains(t, err.Error(), "is not valid UTF-8")
}
