package task

import (
	"bytes"
	"encoding/json"
	"fmt"
	"net/http"
	"net/http/httptest"
	"testing"
	"time"

	"github.com/stretchr/testify/require"

	"github.com/tidbcloud/aisupport/apps/cli/ticket-manager/common"
	baseapi "github.com/tidbcloud/aisupport/packages/sdk-go/contracts/api/base"
	wsapi "github.com/tidbcloud/aisupport/packages/sdk-go/contracts/api/ticket-manager/workspace"
	"github.com/tidbcloud/aisupport/packages/sdk-go/contracts/entity/base"
	taskent "github.com/tidbcloud/aisupport/packages/sdk-go/contracts/entity/ticket-manager/task"
	"github.com/tidbcloud/aisupport/packages/sdk-go/infra/auth/consts"
)

func TestListBuildsWorkspaceTaskRequest(t *testing.T) {
	var listCalled bool
	server := setupServer(t, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		switch r.Method + " " + r.URL.Path {
		case http.MethodGet + " /api/v1/ticketmanager/workspace/workspaces/1001/tasks":
			listCalled = true
			require.Empty(t, r.URL.Query().Get("task_list_id"))
			require.Equal(t, "3001", r.URL.Query().Get("parent_task_id"))
			require.Equal(t, "creator@example.com", r.URL.Query().Get("creator"))
			require.Equal(t, "incident", r.URL.Query().Get("group"))
			require.Equal(t, []string{"p0", "p1"}, r.URL.Query()["priority"])
			require.Equal(t, "2", r.URL.Query().Get("page"))
			require.Equal(t, "50", r.URL.Query().Get("page_size"))
			writeResponse(t, w, &wsapi.ListTasksResponse{
				BaseResponse: &baseapi.BaseResponse{ErrCode: http.StatusOK},
				Tasks:        []*taskent.Task{testTask(4001, 2001)},
				Total:        1,
			})
		default:
			require.FailNow(t, "unexpected request", r.Method+" "+r.URL.String())
		}
	}))

	setupClient(t, server.URL)
	var stdout bytes.Buffer
	setupPrinter(t, &stdout)
	cmd := NewCommand()
	cmd.SetArgs([]string{
		"list",
		"--workspace-id",
		"1001",
		"--parent-task-id",
		"3001",
		"--creator",
		"creator@example.com",
		"--priority",
		"p0",
		"--priority",
		"p1",
		"--group",
		"incident",
		"--page",
		"2",
		"--page-size",
		"50",
	})

	err := cmd.Execute()
	require.NoError(t, err)
	require.True(t, listCalled)
	require.Contains(t, stdout.String(), `"id": 4001`)
	require.Contains(t, stdout.String(), `"task_list_id": 2001`)
}

func TestCreateBuildsWorkspaceTaskRequest(t *testing.T) {
	var req wsapi.CreateTaskRequest
	server := setupServer(t, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		switch r.Method + " " + r.URL.Path {
		case http.MethodPost + " /api/v1/ticketmanager/workspace/workspaces/1001/tasks":
			require.Equal(t, "application/json", r.Header.Get("Content-Type"))
			require.NoError(t, json.NewDecoder(r.Body).Decode(&req))
			writeResponse(t, w, &wsapi.CreateTaskResponse{
				BaseResponse: &baseapi.BaseResponse{ErrCode: http.StatusOK},
				Task:         testTask(4001, 2001),
			})
		default:
			require.FailNow(t, "unexpected request", r.Method+" "+r.URL.String())
		}
	}))

	setupClient(t, server.URL)
	var stdout bytes.Buffer
	setupPrinter(t, &stdout)
	cmd := NewCommand()
	cmd.SetArgs([]string{
		"create",
		"--workspace-id",
		"1001",
		"--title",
		"Investigate alert",
		"--description",
		"Check the incident",
		"--priority",
		"p1",
		"--creator",
		"creator@example.com",
		"--due-at",
		"1710000000",
		"--started-at",
		"1709990000",
		"--parent-task-id",
		"3001",
		"--group",
		"incident",
		"--member",
		"a@example.com:operator",
		"--member",
		"b@example.com:backup",
	})

	err := cmd.Execute()
	require.NoError(t, err)
	require.Equal(t, "Investigate alert", req.Title)
	require.Equal(t, "Check the incident", req.Description)
	require.Equal(t, taskent.TaskPriority(taskent.TaskPriorityP1), req.Priority)
	require.Equal(t, "creator@example.com", req.Creator)
	require.NotNil(t, req.DueAt)
	require.Equal(t, int64(1710000000), *req.DueAt)
	require.NotNil(t, req.StartedAt)
	require.Equal(t, int64(1709990000), *req.StartedAt)
	require.Equal(t, uint64(3001), req.ParentTaskID)
	require.Equal(t, taskent.TaskGroup("incident"), req.Group)
	require.Equal(t, []taskent.TaskMember{
		{MemberEmail: "a@example.com", Role: taskent.TaskMemberRoleOperator},
		{MemberEmail: "b@example.com", Role: taskent.TaskMemberRoleBackup},
	}, req.Members)
	require.Contains(t, stdout.String(), `"id": 4001`)
}

func TestTaskCRUDAndCompleteBuildExpectedRequests(t *testing.T) {
	var updateReq wsapi.UpdateTaskRequest
	requests := make([]string, 0, 4)
	server := setupServer(t, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		requests = append(requests, r.Method+" "+r.URL.Path)
		switch r.Method + " " + r.URL.Path {
		case http.MethodGet + " /api/v1/ticketmanager/workspace/workspaces/1001/tasks/4001":
			writeResponse(t, w, &wsapi.GetTaskResponse{
				BaseResponse: &baseapi.BaseResponse{ErrCode: http.StatusOK},
				Task:         testTask(4001, 2001),
			})
		case http.MethodPut + " /api/v1/ticketmanager/workspace/workspaces/1001/tasks/4001":
			require.NoError(t, json.NewDecoder(r.Body).Decode(&updateReq))
			writeResponse(t, w, &wsapi.UpdateTaskResponse{
				BaseResponse: &baseapi.BaseResponse{ErrCode: http.StatusOK},
				Task:         testTask(4001, 2001),
			})
		case http.MethodPost + " /api/v1/ticketmanager/workspace/workspaces/1001/tasks/4001/complete":
			completed := testTask(4001, 2001)
			completedAt := int64(1710001000)
			completed.CompletedAt = &completedAt
			writeResponse(t, w, &wsapi.CompleteTaskResponse{
				BaseResponse: &baseapi.BaseResponse{ErrCode: http.StatusOK},
				Task:         completed,
			})
		case http.MethodDelete + " /api/v1/ticketmanager/workspace/workspaces/1001/tasks/4001":
			writeResponse(t, w, &baseapi.BaseResponse{ErrCode: http.StatusOK})
		default:
			require.FailNow(t, "unexpected request", r.Method+" "+r.URL.String())
		}
	}))

	setupClient(t, server.URL)
	var stdout bytes.Buffer
	setupPrinter(t, &stdout)

	getCmd := NewCommand()
	getCmd.SetArgs([]string{"get", "4001", "--workspace-id", "1001"})
	require.NoError(t, getCmd.Execute())

	dueAt := int64(1710000000)
	updateCmd := NewCommand()
	updateCmd.SetArgs([]string{
		"update",
		"4001",
		"--workspace-id",
		"1001",
		"--title",
		"Updated alert",
		"--description",
		"Updated description",
		"--priority",
		"p2",
		"--due-at",
		"1710000000",
		"--creator",
		"updated@example.com",
		"--started-at",
		"1709990000",
		"--group",
		"followup",
		"--member",
		"a@example.com:operator",
		"--member",
		"b@example.com:backup",
	})
	require.NoError(t, updateCmd.Execute())
	require.NotNil(t, updateReq.Title)
	require.Equal(t, "Updated alert", *updateReq.Title)
	require.NotNil(t, updateReq.Description)
	require.Equal(t, "Updated description", *updateReq.Description)
	require.NotNil(t, updateReq.Priority)
	require.Equal(t, taskent.TaskPriority(taskent.TaskPriorityP2), *updateReq.Priority)
	require.NotNil(t, updateReq.DueAt)
	require.Equal(t, dueAt, *updateReq.DueAt)
	require.NotNil(t, updateReq.Creator)
	require.Equal(t, "updated@example.com", *updateReq.Creator)
	require.NotNil(t, updateReq.StartedAt)
	require.Equal(t, int64(1709990000), *updateReq.StartedAt)
	require.NotNil(t, updateReq.Group)
	require.Equal(t, taskent.TaskGroup("followup"), *updateReq.Group)
	require.NotNil(t, updateReq.Members)
	require.Equal(t, []taskent.TaskMember{
		{MemberEmail: "a@example.com", Role: taskent.TaskMemberRoleOperator},
		{MemberEmail: "b@example.com", Role: taskent.TaskMemberRoleBackup},
	}, *updateReq.Members)

	completeCmd := NewCommand()
	completeCmd.SetArgs([]string{"complete", "4001", "--workspace-id", "1001"})
	require.NoError(t, completeCmd.Execute())

	stdout.Reset()
	deleteCmd := NewCommand()
	deleteCmd.SetArgs([]string{"delete", "4001", "--workspace-id", "1001"})
	require.NoError(t, deleteCmd.Execute())
	require.Empty(t, stdout.String())

	require.Equal(t, []string{
		http.MethodGet + " /api/v1/ticketmanager/workspace/workspaces/1001/tasks/4001",
		http.MethodPut + " /api/v1/ticketmanager/workspace/workspaces/1001/tasks/4001",
		http.MethodPost + " /api/v1/ticketmanager/workspace/workspaces/1001/tasks/4001/complete",
		http.MethodDelete + " /api/v1/ticketmanager/workspace/workspaces/1001/tasks/4001",
	}, requests)
}

func TestWorkspaceIDFlagIsRequired(t *testing.T) {
	for _, args := range [][]string{
		{"list"},
		{"get", "4001"},
		{"update", "4001", "--title", "Updated title"},
		{"complete", "4001"},
		{"delete", "4001"},
	} {
		cmd := NewCommand()
		cmd.SetArgs(args)

		err := cmd.Execute()
		require.ErrorContains(t, err, `required flag(s) "workspace-id" not set`)
	}
}

func TestInvalidTaskIDReturnsError(t *testing.T) {
	cmd := NewCommand()
	cmd.SetArgs([]string{"get", "invalid", "--workspace-id", "1001"})

	err := cmd.Execute()
	require.ErrorContains(t, err, `invalid task id "invalid"`)
}

func TestCreateRequiresRequiredFields(t *testing.T) {
	cmd := NewCommand()
	cmd.SetArgs([]string{"create", "--workspace-id", "1001", "--title", "title"})

	err := cmd.Execute()
	require.ErrorContains(t, err, `required flag(s)`)
}

func TestInvalidMemberReturnsError(t *testing.T) {
	for _, role := range []string{"assignee", "follower", "owner"} {
		t.Run(role, func(t *testing.T) {
			cmd := NewCommand()
			cmd.SetArgs([]string{
				"create",
				"--workspace-id",
				"1001",
				"--title",
				"title",
				"--description",
				"description",
				"--priority",
				"p1",
				"--creator",
				"creator@example.com",
				"--member",
				"a@example.com:" + role,
			})

			err := cmd.Execute()
			require.ErrorContains(t, err, fmt.Sprintf(`invalid member role %q`, role))
		})
	}
}

func TestUpdateRequiresAtLeastOneField(t *testing.T) {
	cmd := NewCommand()
	cmd.SetArgs([]string{"update", "4001", "--workspace-id", "1001"})

	err := cmd.Execute()
	require.ErrorContains(t, err, "at least one update field is required")
}

func setupServer(t *testing.T, handler http.Handler) *httptest.Server {
	t.Helper()

	server := httptest.NewServer(handler)
	t.Cleanup(server.Close)
	return server
}

func setupClient(t *testing.T, serverURL string) {
	t.Helper()

	t.Setenv(consts.EnvDevOpsAPIToken, "test-token")
	require.NoError(t, common.SetupClient(serverURL, time.Second))
}

func setupPrinter(t *testing.T, output *bytes.Buffer) {
	t.Helper()

	require.NoError(t, common.SetupPrinter("json", output))
}

func writeResponse(t *testing.T, w http.ResponseWriter, value any) {
	t.Helper()

	w.Header().Set("Content-Type", "application/json")
	require.NoError(t, json.NewEncoder(w).Encode(value))
}

func testTask(id uint64, taskListID uint64) *taskent.Task {
	return &taskent.Task{
		Updatable:    base.Updatable{Base: base.Base{ID: id, CreatedAt: 1709990000}},
		Title:        "Investigate alert",
		Description:  "Check the incident",
		Priority:     taskent.TaskPriorityP1,
		Creator:      "creator@example.com",
		ParentTaskID: 3001,
		TaskListID:   taskListID,
		Group:        taskent.TaskGroup("incident"),
	}
}
