package task_test

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

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

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

func TestWorkspaceTaskBrowseHonorsChannelVisibility(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,
		"workspace-task-browse-owner@example.com",
		"workspace-task-browse-owner",
	)
	member := AcceptTaskHTTPInvite(
		t,
		runtime,
		client,
		server.URL,
		owner.User.ID,
		workspaceID,
		"workspace-task-browse-member@example.com",
		"workspace-task-browse-member",
	)

	var privateChannel ChannelResponse
	response := DoJSON(t, client, http.MethodPost, server.URL+"/api/v1/workspaces/"+workspaceID+"/channels", map[string]any{
		"name":        "exec-room",
		"description": "private execution room",
	}, WorkspaceHeaders(owner.AccessToken, workspaceID), &privateChannel)
	require.Equal(t, http.StatusOK, response.StatusCode)

	response = DoJSON(t, client, http.MethodPost, server.URL+"/api/v1/workspaces/"+workspaceID+"/channels/"+allChannelID+"/tasks", map[string]any{
		"tasks": []map[string]any{{"title": "Visible browse task"}},
	}, WorkspaceHeaders(owner.AccessToken, workspaceID), nil)
	require.Equal(t, http.StatusOK, response.StatusCode)

	response = DoJSON(t, client, http.MethodPost, server.URL+"/api/v1/workspaces/"+workspaceID+"/channels/"+privateChannel.ID+"/tasks", map[string]any{
		"tasks": []map[string]any{{"title": "Hidden browse task"}},
	}, WorkspaceHeaders(owner.AccessToken, workspaceID), nil)
	require.Equal(t, http.StatusOK, response.StatusCode)

	var page dto.TaskBrowseResponse
	response = DoJSON(t, client, http.MethodPost, server.URL+"/api/v1/workspaces/"+workspaceID+"/tasks/workspace/browse", map[string]any{
		"sortMode": "manual",
	}, WorkspaceHeaders(member.AccessToken, workspaceID), &page)
	require.Equal(t, http.StatusOK, response.StatusCode)
	require.Equal(t, int64(1), page.TotalCount)
	require.Len(t, page.Rows, 1)
	require.Equal(t, "Visible browse task", page.Rows[0].Title)
	require.False(t, page.HasMore)
}

func TestWorkspaceTaskFilterOptionsAggregateAndHonorChannelVisibility(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,
		"workspace-task-filter-owner@example.com",
		"workspace-task-filter-owner",
	)
	member := AcceptTaskHTTPInvite(
		t,
		runtime,
		client,
		server.URL,
		owner.User.ID,
		workspaceID,
		"workspace-task-filter-member@example.com",
		"workspace-task-filter-member",
	)

	var privateChannel ChannelResponse
	response := DoJSON(t, client, http.MethodPost, server.URL+"/api/v1/workspaces/"+workspaceID+"/channels", map[string]any{
		"name": "private-filter-room",
	}, WorkspaceHeaders(owner.AccessToken, workspaceID), &privateChannel)
	require.Equal(t, http.StatusOK, response.StatusCode)

	for channelID, title := range map[string]string{
		allChannelID:      "Visible option task",
		privateChannel.ID: "Hidden option task",
	} {
		response = DoJSON(t, client, http.MethodPost, server.URL+"/api/v1/workspaces/"+workspaceID+"/channels/"+channelID+"/tasks", map[string]any{
			"tasks": []map[string]any{{"title": title}},
		}, WorkspaceHeaders(owner.AccessToken, workspaceID), nil)
		require.Equal(t, http.StatusOK, response.StatusCode)
	}

	var settings struct {
		PriorityOptions []taskboard.TaskFieldOption `json:"priorityOptions"`
		KindOptions     []taskboard.TaskFieldOption `json:"kindOptions"`
	}
	response = DoJSON(t, client, http.MethodGet, server.URL+"/api/v1/workspaces/"+workspaceID+"/task-board/options", nil, WorkspaceHeaders(owner.AccessToken, workspaceID), &settings)
	require.Equal(t, http.StatusOK, response.StatusCode)
	feature := FindTaskFieldOptionByKey(t, settings.KindOptions, "feature")
	p1 := FindTaskFieldOptionByKey(t, settings.PriorityOptions, "p1")
	ownerMemberID := TaskWorkspaceMemberIDForUser(t, runtime, workspaceID, owner.User.ID)
	response = DoJSON(t, client, http.MethodPost, server.URL+"/api/v1/workspaces/"+workspaceID+"/channels/"+allChannelID+"/tasks", map[string]any{
		"task": map[string]any{
			"title":            "Structured option task",
			"kindOptionId":     feature.ID,
			"priorityOptionId": p1.ID,
			"assigneeRef":      ownerMemberID,
		},
	}, WorkspaceHeaders(owner.AccessToken, workspaceID), nil)
	require.Equal(t, http.StatusOK, response.StatusCode)
	response = DoJSON(t, client, http.MethodPost, server.URL+"/api/v1/workspaces/"+workspaceID+"/task-board/options/"+feature.ID+"/deactivate", map[string]any{}, WorkspaceHeaders(owner.AccessToken, workspaceID), nil)
	require.Equal(t, http.StatusOK, response.StatusCode)

	var memberOptions dto.TaskFilterOptionsResponse
	response = DoJSON(t, client, http.MethodGet, server.URL+"/api/v1/workspaces/"+workspaceID+"/tasks/filter-options", nil, WorkspaceHeaders(member.AccessToken, workspaceID), &memberOptions)
	require.Equal(t, http.StatusOK, response.StatusCode)
	require.Len(t, memberOptions.Channel.Options, 1)
	require.Equal(t, allChannelID, memberOptions.Channel.Options[0].Value)
	require.Contains(t, taskFilterOptionValues(memberOptions.Assignee.Options), owner.User.ID)
	require.Contains(t, taskFilterOptionValues(memberOptions.Assignee.Options), "none")
	require.Contains(t, taskFilterOptionValues(memberOptions.Kind.Options), "feature")
	require.Contains(t, taskFilterOptionValues(memberOptions.Kind.Options), "none")
	require.Equal(t, "inactive", taskFilterOptionByValue(t, memberOptions.Kind.Options, "feature").Tone)
	require.Contains(t, taskFilterOptionValues(memberOptions.Priority.Options), "p1")
	require.Contains(t, taskFilterOptionValues(memberOptions.Priority.Options), "none")

	var ownerOptions dto.TaskFilterOptionsResponse
	response = DoJSON(t, client, http.MethodGet, server.URL+"/api/v1/workspaces/"+workspaceID+"/tasks/filter-options?dimension=channel&q=private", nil, WorkspaceHeaders(owner.AccessToken, workspaceID), &ownerOptions)
	require.Equal(t, http.StatusOK, response.StatusCode)
	require.Len(t, ownerOptions.Channel.Options, 1)
	require.Equal(t, privateChannel.ID, ownerOptions.Channel.Options[0].Value)

	response = DoJSON(t, client, http.MethodGet, server.URL+"/api/v1/workspaces/"+workspaceID+"/tasks/filter-options?dimension=channel&cursor=invalid", nil, WorkspaceHeaders(owner.AccessToken, workspaceID), nil)
	require.Equal(t, http.StatusBadRequest, response.StatusCode)
}

func taskFilterOptionValues(options []dto.TaskFilterOption) []string {
	values := make([]string, 0, len(options))
	for _, option := range options {
		values = append(values, option.Value)
	}
	return values
}

func taskFilterOptionByValue(t *testing.T, options []dto.TaskFilterOption, value string) dto.TaskFilterOption {
	t.Helper()
	for _, option := range options {
		if option.Value == value {
			return option
		}
	}
	t.Fatalf("task filter option %q not found", value)
	return dto.TaskFilterOption{}
}
