package webagent

import (
	"context"
	"encoding/json"
	"net/http"
	"net/http/httptest"
	"testing"

	"github.com/stretchr/testify/require"

	"github.com/tidbcloud/AgentTeam/internal/forwarder"
	"github.com/tidbcloud/AgentTeam/internal/store/identity"
)

type recordingResourceCopyForwarder struct {
	requests []forwarder.JSONRequest
}

func (fake *recordingResourceCopyForwarder) ForwardJSON(_ context.Context, request forwarder.JSONRequest, response any) error {
	fake.requests = append(fake.requests, request)
	var payload any
	switch request.Path {
	case "/api/v1/skills/agents/agt_source/bindings":
		payload = []any{}
	case "/api/v1/memory/agents/agt_source/cloud-memory":
		payload = map[string]any{"enabled": false}
	default:
		payload = map[string]any{}
	}
	encoded, err := json.Marshal(payload)
	if err != nil {
		return err
	}
	if response == nil {
		return nil
	}
	return json.Unmarshal(encoded, response)
}

func fakeResourceCopyUser(id string) identity.User {
	return identity.User{
		ID:          id,
		Email:       "alice@example.com",
		Name:        "alice",
		DisplayName: "Alice",
		AvatarURL:   "https://example.com/alice.png",
	}
}

func TestAgentResourceCopyIdentityConstructors(t *testing.T) {
	t.Run("web identity preserves the persisted user and real session", func(t *testing.T) {
		user := fakeResourceCopyUser("usr_1")
		resourceIdentity := NewAgentResourceCopyIdentityForWeb(identity.AuthenticatedUser{
			User:    user,
			Session: identity.UserSession{ID: "sess_real", UserID: user.ID},
		})

		require.Equal(t, user, resourceIdentity.User)
		require.Equal(t, "sess_real", resourceIdentity.SessionID)
		require.Empty(t, resourceIdentity.InternalPurpose)
	})

	t.Run("approved agent management identity fixes purpose without a session", func(t *testing.T) {
		user := fakeResourceCopyUser("usr_1")
		resourceIdentity := NewApprovedAgentManagementResourceCopyIdentity(user)

		require.Equal(t, user, resourceIdentity.User)
		require.Empty(t, resourceIdentity.SessionID)
		require.Equal(t, "approved_agent_management_operation", resourceIdentity.InternalPurpose)
	})
}

func TestForwardedAgentResourceCopierUsesExplicitIdentity(t *testing.T) {
	testCases := []struct {
		name          string
		identity      AgentResourceCopyIdentity
		wantSessionID string
		wantPurpose   string
	}{
		{
			name: "web session",
			identity: NewAgentResourceCopyIdentityForWeb(identity.AuthenticatedUser{
				User:    fakeResourceCopyUser("usr_web"),
				Session: identity.UserSession{ID: "sess_web", UserID: "usr_web"},
			}),
			wantSessionID: "sess_web",
		},
		{
			name:        "approved internal operation",
			identity:    NewApprovedAgentManagementResourceCopyIdentity(fakeResourceCopyUser("usr_internal")),
			wantPurpose: "approved_agent_management_operation",
		},
	}
	for _, testCase := range testCases {
		t.Run(testCase.name, func(t *testing.T) {
			forwarderFake := &recordingResourceCopyForwarder{}
			copier := &forwardedAgentResourceCopier{forwarder: forwarderFake}

			_, err := copier.CopyAgentResources(context.Background(), AgentResourceCopyRequest{
				WorkspaceID: "wsp_1",
				Identity:    testCase.identity,
				Source:      AgentResourceCopyAgent{ID: "agt_source", Name: "source", DisplayName: "Source"},
				Target:      AgentResourceCopyAgent{ID: "agt_target", Name: "target", DisplayName: "Target"},
			})

			require.NoError(t, err)
			require.Len(t, forwarderFake.requests, 2)
			for _, request := range forwarderFake.requests {
				require.Equal(t, testCase.identity.User.ID, request.Identity.UserID)
				require.Equal(t, testCase.identity.User.ID, request.Identity.AgentTeamUserID)
				require.Equal(t, testCase.identity.User.Email, request.Identity.Email)
				require.Equal(t, testCase.identity.User.Name, request.Identity.Name)
				require.Equal(t, testCase.identity.User.DisplayName, request.Identity.DisplayName)
				require.Equal(t, testCase.identity.User.AvatarURL, request.Identity.AvatarURL)
				require.Equal(t, testCase.wantSessionID, request.Identity.SessionID)
				require.Equal(t, testCase.wantPurpose, request.InternalPurpose)
				require.Equal(t, "wsp_1", request.AgentOwnershipWorkspaceID)
				require.Equal(t, []forwarder.TrustedOwnedAgent{
					{ID: "agt_source", Name: "source", DisplayName: "Source"},
					{ID: "agt_target", Name: "target", DisplayName: "Target"},
				}, request.OwnedAgents)
			}
		})
	}
}

func TestForwardedAgentResourceCopierAcceptsPermanentMemoryCleanupNoContent(t *testing.T) {
	cleanupRequests := 0
	upstream := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, request *http.Request) {
		w.Header().Set("Content-Type", "application/json")
		switch {
		case request.Method == http.MethodGet && request.URL.Path == "/api/v1/skills/agents/agt_source/bindings":
			_, _ = w.Write([]byte(`[]`))
		case request.Method == http.MethodGet && request.URL.Path == "/api/v1/memory/agents/agt_source/cloud-memory":
			_, _ = w.Write([]byte(`{"enabled":true,"memoryId":"mem_source","name":"Source memory","branch":"main"}`))
		case request.Method == http.MethodPost && request.URL.Path == "/api/v1/memory/mem_source/fork":
			_, _ = w.Write([]byte(`{"id":"mem_forked"}`))
		case request.Method == http.MethodPut && request.URL.Path == "/api/v1/memory/agents/agt_target/binding":
			w.WriteHeader(http.StatusInternalServerError)
			_, _ = w.Write([]byte(`{"message":"bind failed"}`))
		case request.Method == http.MethodDelete && request.URL.Path == "/api/v1/memory/mem_forked/permanent":
			cleanupRequests++
			w.WriteHeader(http.StatusNoContent)
		default:
			w.WriteHeader(http.StatusNotFound)
			_, _ = w.Write([]byte(`{"message":"unexpected request"}`))
		}
	}))
	defer upstream.Close()

	copier, err := NewForwardedAgentResourceCopier(forwarder.Dependencies{
		BaseURL:            upstream.URL,
		HTTPClient:         upstream.Client(),
		TrustedJWTAudience: "meshx",
	})
	require.NoError(t, err)

	result, err := copier.CopyAgentResources(context.Background(), AgentResourceCopyRequest{
		WorkspaceID: "wsp_1",
		Identity:    NewApprovedAgentManagementResourceCopyIdentity(fakeResourceCopyUser("usr_1")),
		Source:      AgentResourceCopyAgent{ID: "agt_source", Name: "source", DisplayName: "Source"},
		Target:      AgentResourceCopyAgent{ID: "agt_target", Name: "target", DisplayName: "Target"},
	})
	require.Error(t, err)
	require.Contains(t, err.Error(), "bind forked memory to target agent: bind failed")
	require.NotContains(t, err.Error(), "cleanup forked memory")
	require.Equal(t, 1, cleanupRequests)
	require.False(t, result.Memory)
}
