package roles

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

	"github.com/stretchr/testify/require"
)

type rolePackageTestFileEntry struct {
	content string
	sha     string
}

func TestAGSHTTPRolePackageContentStoreStoresAndResolvesAssets(t *testing.T) {
	repos := map[string]map[string]map[string]rolePackageTestFileEntry{}
	commitSnapshots := map[string]map[string]map[string]rolePackageTestFileEntry{}
	branchHeads := map[string]map[string]string{}
	repoCounter := 0
	commitCounter := 100
	shaCounter := 0
	handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		require.Equal(t, testInternalAuthHeader("meshx-internal"), r.Header.Get("Authorization"))
		switch {
		case r.Method == http.MethodGet && strings.HasPrefix(r.URL.Path, "/api/v3/repos/") && strings.Contains(r.URL.Path, "/branches/"):
			parts := strings.Split(strings.TrimPrefix(r.URL.Path, "/api/v3/repos/"), "/")
			repoKey := parts[0] + "/" + parts[1]
			branch := strings.Join(parts[3:], "/")
			sha := branchHeads[repoKey][branch]
			if sha == "" {
				http.NotFound(w, r)
				return
			}
			_ = json.NewEncoder(w).Encode(map[string]any{"name": branch, "commit": map[string]any{"sha": sha}})
		case r.Method == http.MethodGet && strings.HasPrefix(r.URL.Path, "/api/v3/repos/") && strings.Contains(r.URL.Path, "/contents/"):
			parts := strings.Split(strings.TrimPrefix(r.URL.Path, "/api/v3/repos/"), "/")
			repoKey := parts[0] + "/" + parts[1]
			filePath := strings.Join(parts[4:], "/")
			ref := r.URL.Query().Get("ref")
			branch := ref
			if filesByBranch, ok := repos[repoKey]; ok {
				if files, ok := filesByBranch[branch]; ok {
					if file, ok := files[filePath]; ok {
						_ = json.NewEncoder(w).Encode(map[string]any{"type": "file", "sha": file.sha, "content": base64.StdEncoding.EncodeToString([]byte(file.content)), "encoding": "base64"})
						return
					}
				}
				if filesByCommit, ok := commitSnapshots[repoKey]; ok {
					if files, ok := filesByCommit[ref]; ok {
						if file, ok := files[filePath]; ok {
							_ = json.NewEncoder(w).Encode(map[string]any{"type": "file", "sha": file.sha, "content": base64.StdEncoding.EncodeToString([]byte(file.content)), "encoding": "base64"})
							return
						}
					}
				}
				for branchName, files := range filesByBranch {
					if file, ok := files[filePath]; ok && ref == branchHeads[repoKey][branchName] {
						_ = json.NewEncoder(w).Encode(map[string]any{"type": "file", "sha": file.sha, "content": base64.StdEncoding.EncodeToString([]byte(file.content)), "encoding": "base64"})
						return
					}
				}
			}
			http.NotFound(w, r)
		case r.Method == http.MethodGet && strings.HasPrefix(r.URL.Path, "/api/v3/repos/"):
			http.NotFound(w, r)
		case r.Method == http.MethodGet && strings.HasPrefix(r.URL.Path, "/api/v3/orgs/"):
			owner := strings.TrimPrefix(r.URL.Path, "/api/v3/orgs/")
			_ = json.NewEncoder(w).Encode(map[string]any{"login": owner})
		case r.Method == http.MethodPost && strings.HasPrefix(r.URL.Path, "/api/v3/orgs/"):
			owner := strings.TrimSuffix(strings.TrimPrefix(r.URL.Path, "/api/v3/orgs/"), "/repos")
			var payload map[string]any
			require.NoError(t, json.NewDecoder(r.Body).Decode(&payload))
			name := payload["name"].(string)
			repoKey := owner + "/" + name
			repoCounter++
			repos[repoKey] = map[string]map[string]rolePackageTestFileEntry{"main": {}}
			commitSnapshots[repoKey] = map[string]map[string]rolePackageTestFileEntry{"commit-main": {}}
			branchHeads[repoKey] = map[string]string{"main": "commit-main"}
			w.WriteHeader(http.StatusCreated)
			_ = json.NewEncoder(w).Encode(map[string]any{"id": repoCounter, "name": name, "full_name": repoKey, "default_branch": "main"})
		case r.Method == http.MethodPost && strings.HasPrefix(r.URL.Path, "/api/v3/repos/") && strings.HasSuffix(r.URL.Path, "/git/refs"):
			parts := strings.Split(strings.TrimPrefix(r.URL.Path, "/api/v3/repos/"), "/")
			repoKey := parts[0] + "/" + parts[1]
			var payload struct{ Ref, SHA string }
			require.NoError(t, json.NewDecoder(r.Body).Decode(&payload))
			branch := strings.TrimPrefix(payload.Ref, "refs/heads/")
			if _, ok := branchHeads[repoKey][branch]; ok {
				w.WriteHeader(http.StatusUnprocessableEntity)
				_, _ = w.Write([]byte(`{"message":"Reference already exists"}`))
				return
			}
			branchHeads[repoKey][branch] = payload.SHA
			repos[repoKey][branch] = cloneRolePackageTestFiles(commitSnapshots[repoKey][payload.SHA])
			w.WriteHeader(http.StatusCreated)
			_ = json.NewEncoder(w).Encode(map[string]any{"ref": payload.Ref, "object": map[string]any{"sha": payload.SHA}})
		case r.Method == http.MethodPut && strings.HasPrefix(r.URL.Path, "/api/v3/repos/") && strings.Contains(r.URL.Path, "/contents/"):
			parts := strings.Split(strings.TrimPrefix(r.URL.Path, "/api/v3/repos/"), "/")
			repoKey := parts[0] + "/" + parts[1]
			filePath := strings.Join(parts[4:], "/")
			var payload struct {
				Message string `json:"message"`
				Content string `json:"content"`
				Branch  string `json:"branch"`
				SHA     string `json:"sha"`
			}
			require.NoError(t, json.NewDecoder(r.Body).Decode(&payload))
			decoded, err := base64.StdEncoding.DecodeString(payload.Content)
			require.NoError(t, err)
			files := repos[repoKey][payload.Branch]
			current, exists := files[filePath]
			if exists && payload.SHA == "" {
				w.WriteHeader(http.StatusUnprocessableEntity)
				return
			}
			if exists && payload.SHA != current.sha {
				w.WriteHeader(http.StatusConflict)
				return
			}
			shaCounter++
			newSHA := fmt.Sprintf("blob-sha-%d", shaCounter)
			files[filePath] = rolePackageTestFileEntry{content: string(decoded), sha: newSHA}
			commitCounter++
			commitSHA := fmt.Sprintf("commit-%d", commitCounter)
			branchHeads[repoKey][payload.Branch] = commitSHA
			commitSnapshots[repoKey][commitSHA] = cloneRolePackageTestFiles(files)
			w.WriteHeader(http.StatusCreated)
			_ = json.NewEncoder(w).Encode(map[string]any{"content": map[string]any{"sha": newSHA}, "commit": map[string]any{"sha": commitSHA}})
		default:
			http.NotFound(w, r)
		}
	})

	store := AGSHTTPRolePackageContentStore{
		Handler:             handler,
		BaseURL:             "http://127.0.0.1:3002",
		AuthHeader:          "Authorization",
		AuthorizationHeader: testInternalAuthHeader("meshx-internal"),
	}
	result, err := store.StoreRolePackage(context.Background(), StoreRolePackageInput{
		WorkspaceID: "wsp_1",
		RoleID:      "role_123",
		VersionID:   "rlv_456",
		Assets:      []RolePackageAsset{{ItemType: ManifestItemRoleDefinitionAsset, Name: "IDENTITY.md", Content: "role definition"}, {ItemType: AttachmentPortableKnowledge, Name: "Runbook", Content: "portable knowledge"}, {ItemType: AttachmentSkill, Name: "Reviewer", Content: "# skill"}},
	})
	require.NoError(t, err)
	require.Len(t, result.Assets, 3)
	def := result.Assets["IDENTITY.md"]
	require.Contains(t, def.ContentRef, "/api/v3/repos/wsp_1/123.rolepkg/contents/IDENTITY.md?ref=")
	require.NotEmpty(t, def.ContentHash)
	resolved, err := store.ResolveRolePackage(context.Background(), ResolveRolePackageInput{ContentRef: def.ContentRef})
	require.NoError(t, err)
	require.Equal(t, "role definition", resolved)
	knowledge, err := store.ResolveRolePackage(context.Background(), ResolveRolePackageInput{ContentRef: result.Assets[".agents/knowledge/runbook.md"].ContentRef})
	require.NoError(t, err)
	require.Equal(t, "portable knowledge", knowledge)
}

func cloneRolePackageTestFiles(in map[string]rolePackageTestFileEntry) map[string]rolePackageTestFileEntry {
	out := make(map[string]rolePackageTestFileEntry, len(in))
	for path, file := range in {
		out[path] = file
	}
	return out
}

func TestAGSHTTPRolePackageContentStoreFailsClosedWithoutAGS(t *testing.T) {
	store := AGSHTTPRolePackageContentStore{}
	_, err := store.StoreRolePackage(context.Background(), StoreRolePackageInput{WorkspaceID: "wsp_1", RoleID: "role_1", VersionID: "rlv_1", Assets: []RolePackageAsset{{ItemType: ManifestItemRoleDefinitionAsset, Name: "IDENTITY.md", Content: "role"}}})
	require.Error(t, err)
	require.True(t, isRolePackageContentRequired(err))
	_, err = store.ResolveRolePackage(context.Background(), ResolveRolePackageInput{ContentRef: "http://example.test"})
	require.Error(t, err)
}

func TestAGSHTTPRolePackageContentStoreMarksAGSFailuresUnavailable(t *testing.T) {
	store := AGSHTTPRolePackageContentStore{
		Handler:             failingRolePackageBranchHandler(t),
		AuthorizationHeader: testInternalAuthHeader("meshx-internal"),
	}
	_, err := store.StoreRolePackage(context.Background(), StoreRolePackageInput{
		WorkspaceID: "wsp_1",
		RoleID:      "role_1",
		VersionID:   "rlv_1",
		Assets:      []RolePackageAsset{{ItemType: ManifestItemRoleDefinitionAsset, Name: "IDENTITY.md", Content: "role"}},
	})

	require.Error(t, err)
	require.True(t, IsRolePackageDependencyUnavailable(err), "StoreRolePackage error = %v", err)
	require.Contains(t, err.Error(), "AGS is restarting")
}

func TestAGSHTTPRolePackageContentStoreResolveUsesRelativePath(t *testing.T) {
	handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		require.Equal(t, "/api/v3/repos/wsp_1/123.rolepkg/contents/IDENTITY.md", r.URL.Path)
		require.Equal(t, "commit-1", r.URL.Query().Get("ref"))
		_ = json.NewEncoder(w).Encode(map[string]any{"type": "file", "encoding": "base64", "content": base64.StdEncoding.EncodeToString([]byte("ok"))})
	})
	store := AGSHTTPRolePackageContentStore{Handler: handler, AuthorizationHeader: testInternalAuthHeader("meshx-internal")}
	content, err := store.ResolveRolePackage(context.Background(), ResolveRolePackageInput{ContentRef: "/api/v3/repos/wsp_1/123.rolepkg/contents/IDENTITY.md?ref=commit-1"})
	require.NoError(t, err)
	require.Equal(t, "ok", content)
}

func testInternalAuthHeader(login string) string {
	return "Bearer test-" + strings.TrimSpace(login)
}

func failingRolePackageBranchHandler(t *testing.T) http.Handler {
	t.Helper()
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		require.Equal(t, testInternalAuthHeader("meshx-internal"), r.Header.Get("Authorization"))
		switch {
		case r.Method == http.MethodGet && strings.HasPrefix(r.URL.Path, "/api/v3/repos/") && strings.Contains(r.URL.Path, "/branches/"):
			http.Error(w, "AGS is restarting", http.StatusServiceUnavailable)
		case r.Method == http.MethodGet && strings.HasPrefix(r.URL.Path, "/api/v3/repos/"):
			http.NotFound(w, r)
		case r.Method == http.MethodGet && strings.HasPrefix(r.URL.Path, "/api/v3/orgs/"):
			owner := strings.TrimPrefix(r.URL.Path, "/api/v3/orgs/")
			_ = json.NewEncoder(w).Encode(map[string]any{"login": owner})
		case r.Method == http.MethodPost && strings.HasPrefix(r.URL.Path, "/api/v3/orgs/"):
			owner := strings.TrimSuffix(strings.TrimPrefix(r.URL.Path, "/api/v3/orgs/"), "/repos")
			var payload map[string]any
			require.NoError(t, json.NewDecoder(r.Body).Decode(&payload))
			name := payload["name"].(string)
			w.WriteHeader(http.StatusCreated)
			_ = json.NewEncoder(w).Encode(map[string]any{"id": 1, "name": name, "full_name": owner + "/" + name, "default_branch": "main"})
		default:
			http.NotFound(w, r)
		}
	})
}
