package task_test

import (
	"net/http"
	"net/http/httptest"
	"testing"

	. "github.com/tidbcloud/AgentTeam/internal/api/testkit"
	"github.com/tidbcloud/AgentTeam/internal/store/message"
	"github.com/tidbcloud/AgentTeam/internal/store/taskboard"

	"github.com/gin-gonic/gin"
	"github.com/stretchr/testify/require"
)

func TestTaskCollaboratorAPIManagesTaskScopedCollaborators(t *testing.T) {
	t.Parallel()

	runtime := StartRuntime(t)

	router := gin.New()
	runtime.RegisterRoutes(router)
	server := httptest.NewServer(router)
	defer server.Close()

	client := server.Client()
	owner, workspaceID, allChannelID := SetupTaskHTTPActor(
		t,
		client,
		server.URL,
		"task-collaborator-api-owner@example.com",
		"task-collaborator-api-owner",
	)
	member := AcceptTaskHTTPInvite(
		t,
		runtime,
		client,
		server.URL,
		owner.User.ID,
		workspaceID,
		"task-collaborator-api-member@example.com",
		"task-collaborator-api-member",
	)
	memberID := TaskWorkspaceMemberIDForUser(t, runtime, workspaceID, member.User.ID)

	var created taskboard.TaskPage
	response := DoJSON(t, client, http.MethodPost, server.URL+"/api/v1/workspaces/"+workspaceID+"/channels/"+allChannelID+"/tasks", map[string]any{
		"tasks": []map[string]any{{"title": "Expose collaborator API"}},
	}, WorkspaceHeaders(owner.AccessToken, workspaceID), &created)
	require.Equal(t, http.StatusOK, response.StatusCode)
	require.Len(t, created.Tasks, 1)
	taskID := created.Tasks[0].ID

	var preGrantCapability taskboard.ResolvePrivateAgentContextContributionCapabilityResponse
	response = DoJSON(t, client, http.MethodGet, server.URL+"/api/v1/workspaces/"+workspaceID+"/tasks/"+taskID+"/private-agent-contribution-capability", nil, WorkspaceHeaders(member.AccessToken, workspaceID), &preGrantCapability)
	require.Equal(t, http.StatusOK, response.StatusCode)
	require.Equal(t, "none", preGrantCapability.Relation)
	require.False(t, preGrantCapability.CanContributeContext)
	require.False(t, preGrantCapability.CanWake)
	require.False(t, preGrantCapability.CanShare)

	var ownerCapability taskboard.ResolvePrivateAgentContextContributionCapabilityResponse
	response = DoJSON(t, client, http.MethodGet, server.URL+"/api/v1/workspaces/"+workspaceID+"/tasks/"+taskID+"/private-agent-contribution-capability", nil, WorkspaceHeaders(owner.AccessToken, workspaceID), &ownerCapability)
	require.Equal(t, http.StatusOK, response.StatusCode)
	require.Equal(t, "owner", ownerCapability.Relation)
	require.True(t, ownerCapability.CanContributeContext)
	require.True(t, ownerCapability.CanWake)
	require.True(t, ownerCapability.CanShare)

	var added taskboard.AddTaskCollaboratorResponse
	response = DoJSON(t, client, http.MethodPost, server.URL+"/api/v1/workspaces/"+workspaceID+"/tasks/"+taskID+"/collaborators", map[string]any{
		"memberId": memberID,
		"role":     "contributor",
	}, WorkspaceHeaders(owner.AccessToken, workspaceID), &added)
	require.Equal(t, http.StatusOK, response.StatusCode)
	require.Equal(t, memberID, added.Collaborator.MemberID)
	require.True(t, added.Collaborator.Active)
	require.Equal(t, "pending", added.Collaborator.NotificationState)
	require.NotNil(t, added.Collaborator.NotificationQueuedAt)
	require.Nil(t, added.Collaborator.NotifiedAt)

	var collaboratorCapability taskboard.ResolvePrivateAgentContextContributionCapabilityResponse
	response = DoJSON(t, client, http.MethodGet, server.URL+"/api/v1/workspaces/"+workspaceID+"/tasks/"+taskID+"/private-agent-contribution-capability", nil, WorkspaceHeaders(member.AccessToken, workspaceID), &collaboratorCapability)
	require.Equal(t, http.StatusOK, response.StatusCode)
	require.Equal(t, "collaborator", collaboratorCapability.Relation)
	require.True(t, collaboratorCapability.CanContributeContext)
	require.True(t, collaboratorCapability.CanWake)
	require.False(t, collaboratorCapability.CanShare)

	var list taskboard.ListTaskCollaboratorsResponse
	response = DoJSON(t, client, http.MethodGet, server.URL+"/api/v1/workspaces/"+workspaceID+"/tasks/"+taskID+"/collaborators", nil, WorkspaceHeaders(owner.AccessToken, workspaceID), &list)
	require.Equal(t, http.StatusOK, response.StatusCode)
	require.Len(t, list.Collaborators, 1)
	require.Equal(t, added.Collaborator.ID, list.Collaborators[0].ID)
	require.Equal(t, "pending", list.Collaborators[0].NotificationState)

	var effective taskboard.ListEffectiveTaskCollaboratorsResponse
	effective = taskboard.ListEffectiveTaskCollaboratorsResponse{}
	response = DoJSON(t, client, http.MethodGet, server.URL+"/api/v1/workspaces/"+workspaceID+"/tasks/"+taskID+"/effective-collaborators", nil, WorkspaceHeaders(owner.AccessToken, workspaceID), &effective)
	require.Equal(t, http.StatusOK, response.StatusCode)
	require.Len(t, effective.Collaborators, 1)
	require.Equal(t, effective.People, effective.Collaborators, "collaborators is a deprecated People alias")
	require.Equal(t, memberID, effective.Collaborators[0].MemberID)
	require.Len(t, effective.Collaborators[0].Sources, 1)
	require.Equal(t, "task", effective.Collaborators[0].Sources[0].ScopeType)

	var ownerMessage message.AppMessage
	response = DoJSON(t, client, http.MethodPost, server.URL+WorkspaceScopeMessagesPath(workspaceID, allChannelID), map[string]any{
		"scopeId": allChannelID,
		"content": "owner private context source",
	}, WorkspaceHeaders(owner.AccessToken, workspaceID), &ownerMessage)
	require.Equal(t, http.StatusOK, response.StatusCode)
	var peopleMessage message.AppMessage
	response = DoJSON(t, client, http.MethodPost, server.URL+WorkspaceScopeMessagesPath(workspaceID, allChannelID), map[string]any{
		"scopeId": allChannelID,
		"content": "people private context source",
	}, WorkspaceHeaders(member.AccessToken, workspaceID), &peopleMessage)
	require.Equal(t, http.StatusOK, response.StatusCode)

	var contextStates taskboard.ResolvePrivateAgentContextStateResponse
	response = DoJSON(t, client, http.MethodPost, server.URL+"/api/v1/workspaces/"+workspaceID+"/tasks/"+taskID+"/private-agent-context-state", map[string]any{
		"messageIds": []string{ownerMessage.ID, peopleMessage.ID},
	}, WorkspaceHeaders(owner.AccessToken, workspaceID), &contextStates)
	require.Equal(t, http.StatusOK, response.StatusCode)
	require.Len(t, contextStates.States, 2)
	require.Equal(t, "context-eligible", string(contextStates.States[0].State))
	require.NotNil(t, contextStates.States[0].Relation)
	require.Equal(t, "owner", contextStates.States[0].Relation.Kind)
	require.Equal(t, "context-eligible", string(contextStates.States[1].State))
	require.NotNil(t, contextStates.States[1].Relation)
	require.Equal(t, "people", contextStates.States[1].Relation.Kind)

	var removed taskboard.RemoveTaskCollaboratorResponse
	response = DoJSON(t, client, http.MethodDelete, server.URL+"/api/v1/workspaces/"+workspaceID+"/tasks/"+taskID+"/collaborators/"+memberID, nil, WorkspaceHeaders(owner.AccessToken, workspaceID), &removed)
	require.Equal(t, http.StatusOK, response.StatusCode)
	require.False(t, removed.Collaborator.Active)
	require.NotNil(t, removed.Collaborator.RemovedAt)

	effective = taskboard.ListEffectiveTaskCollaboratorsResponse{}
	response = DoJSON(t, client, http.MethodGet, server.URL+"/api/v1/workspaces/"+workspaceID+"/tasks/"+taskID+"/effective-collaborators", nil, WorkspaceHeaders(owner.AccessToken, workspaceID), &effective)
	require.Equal(t, http.StatusOK, response.StatusCode)
	require.Empty(t, effective.Collaborators)
	require.Empty(t, effective.People)
}

func TestAgentCollaboratorAPIAggregatesOnlyCurrentOwnersCollaborators(t *testing.T) {
	t.Parallel()

	runtime := StartRuntime(t)

	router := gin.New()
	runtime.RegisterRoutes(router)
	server := httptest.NewServer(router)
	defer server.Close()

	client := server.Client()
	owner, workspaceID, allChannelID := SetupTaskHTTPActor(
		t,
		client,
		server.URL,
		"agent-collaborator-owner@example.com",
		"agent-collaborator-owner",
	)
	member := AcceptTaskHTTPInvite(
		t,
		runtime,
		client,
		server.URL,
		owner.User.ID,
		workspaceID,
		"agent-collaborator-member@example.com",
		"agent-collaborator-member",
	)
	memberID := TaskWorkspaceMemberIDForUser(t, runtime, workspaceID, member.User.ID)

	var created taskboard.TaskPage
	response := DoJSON(t, client, http.MethodPost, server.URL+"/api/v1/workspaces/"+workspaceID+"/channels/"+allChannelID+"/tasks", map[string]any{
		"tasks": []map[string]any{{"title": "Aggregate current owner collaborators"}},
	}, WorkspaceHeaders(owner.AccessToken, workspaceID), &created)
	require.Equal(t, http.StatusOK, response.StatusCode)
	require.Len(t, created.Tasks, 1)

	var added taskboard.AddTaskCollaboratorResponse
	response = DoJSON(t, client, http.MethodPost, server.URL+"/api/v1/workspaces/"+workspaceID+"/channels/"+allChannelID+"/task-collaborators", map[string]any{
		"memberId": memberID,
		"role":     "contributor",
	}, WorkspaceHeaders(owner.AccessToken, workspaceID), &added)
	require.Equal(t, http.StatusOK, response.StatusCode)
	require.True(t, added.Collaborator.Active)

	var ownerList taskboard.ListAgentCollaboratorsResponse
	response = DoJSON(t, client, http.MethodGet, server.URL+"/api/v1/workspaces/"+workspaceID+"/agent-collaborators?sourceScopeType=channel&sourceScopeId="+allChannelID, nil, WorkspaceHeaders(owner.AccessToken, workspaceID), &ownerList)
	require.Equal(t, http.StatusOK, response.StatusCode)
	require.Len(t, ownerList.Collaborators, 1)
	require.Equal(t, ownerList.People, ownerList.Collaborators, "collaborators is a deprecated People alias")
	require.Equal(t, memberID, ownerList.Collaborators[0].MemberID)
	require.Len(t, ownerList.Collaborators[0].Sources, 1)
	require.Equal(t, "channel", ownerList.Collaborators[0].Sources[0].ScopeType)
	require.Equal(t, allChannelID, ownerList.Collaborators[0].Sources[0].ScopeID)
	require.Equal(t, "all", ownerList.SourceScopeDisplayName)
	require.Equal(t, "all", ownerList.SourceChannelDisplayName)
	require.Equal(t, "all", ownerList.Collaborators[0].Sources[0].ScopeDisplayName)
	require.Equal(t, "all", ownerList.Collaborators[0].Sources[0].ChannelDisplayName)

	var memberView taskboard.ListAgentCollaboratorsResponse
	response = DoJSON(t, client, http.MethodGet, server.URL+"/api/v1/workspaces/"+workspaceID+"/agent-collaborators", nil, WorkspaceHeaders(member.AccessToken, workspaceID), &memberView)
	require.Equal(t, http.StatusOK, response.StatusCode)
	require.Empty(t, memberView.Collaborators, "the aggregate endpoint must not expose another owner's collaborator grants")
}

func TestTaskCollaboratorAPIListsThreadEffectiveCollaboratorsInheritedFromChannel(t *testing.T) {
	t.Parallel()

	runtime := StartRuntime(t)

	router := gin.New()
	runtime.RegisterRoutes(router)
	server := httptest.NewServer(router)
	defer server.Close()

	client := server.Client()
	owner, workspaceID, allChannelID := SetupTaskHTTPActor(
		t,
		client,
		server.URL,
		"thread-effective-collaborator-owner@example.com",
		"thread-effective-collaborator-owner",
	)
	member := AcceptTaskHTTPInvite(
		t,
		runtime,
		client,
		server.URL,
		owner.User.ID,
		workspaceID,
		"thread-effective-collaborator-member@example.com",
		"thread-effective-collaborator-member",
	)
	memberID := TaskWorkspaceMemberIDForUser(t, runtime, workspaceID, member.User.ID)

	var parent message.AppMessage
	response := DoJSON(t, client, http.MethodPost, server.URL+WorkspaceScopeMessagesPath(workspaceID, allChannelID), map[string]any{
		"scopeId": allChannelID,
		"content": "thread effective collaborators parent",
	}, WorkspaceHeaders(owner.AccessToken, workspaceID), &parent)
	require.Equal(t, http.StatusOK, response.StatusCode)
	require.NotEmpty(t, parent.ID)

	var thread message.ReplyThreadSummary
	response = DoJSON(t, client, http.MethodPost, server.URL+"/api/v1/workspaces/"+workspaceID+"/channels/"+allChannelID+"/threads", map[string]any{
		"parentMessageId": parent.ID,
	}, WorkspaceHeaders(owner.AccessToken, workspaceID), &thread)
	require.Equal(t, http.StatusOK, response.StatusCode)
	require.NotEmpty(t, thread.ThreadChannelID)

	var addedChannelCollaborator taskboard.AddTaskCollaboratorResponse
	response = DoJSON(t, client, http.MethodPost, server.URL+"/api/v1/workspaces/"+workspaceID+"/channels/"+allChannelID+"/task-collaborators", map[string]any{
		"memberId": memberID,
		"role":     "contributor",
	}, WorkspaceHeaders(owner.AccessToken, workspaceID), &addedChannelCollaborator)
	require.Equal(t, http.StatusOK, response.StatusCode)
	require.True(t, addedChannelCollaborator.Collaborator.Active)

	var localThreadCollaborators taskboard.ListTaskCollaboratorsResponse
	response = DoJSON(t, client, http.MethodGet, server.URL+"/api/v1/workspaces/"+workspaceID+"/threads/"+thread.ThreadChannelID+"/task-collaborators", nil, WorkspaceHeaders(owner.AccessToken, workspaceID), &localThreadCollaborators)
	require.Equal(t, http.StatusOK, response.StatusCode)
	require.Empty(t, localThreadCollaborators.Collaborators)

	var effective taskboard.ListEffectiveTaskCollaboratorsResponse
	response = DoJSON(t, client, http.MethodGet, server.URL+"/api/v1/workspaces/"+workspaceID+"/threads/"+thread.ThreadChannelID+"/task-collaborators/effective", nil, WorkspaceHeaders(owner.AccessToken, workspaceID), &effective)
	require.Equal(t, http.StatusOK, response.StatusCode)
	require.Equal(t, effective.People, effective.Collaborators, "collaborators is a deprecated People alias")
	require.Len(t, effective.Collaborators, 1)
	require.Equal(t, memberID, effective.Collaborators[0].MemberID)
	require.Len(t, effective.Collaborators[0].Sources, 1)
	require.Equal(t, "channel", effective.Collaborators[0].Sources[0].ScopeType)
	require.Equal(t, allChannelID, effective.Collaborators[0].Sources[0].ScopeID)
}
