package collab

import (
	"bytes"
	"encoding/json"
	"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"
	collabent "github.com/tidbcloud/aisupport/packages/sdk-go/contracts/entity/ticket-manager/collaboration"
	wsent "github.com/tidbcloud/aisupport/packages/sdk-go/contracts/entity/ticket-manager/workspace"
	"github.com/tidbcloud/aisupport/packages/sdk-go/infra/auth/consts"
)

func TestListBuildsWorkspaceCollabRequestAndLoadsResources(t *testing.T) {
	var listCalled bool
	server := setupServer(t, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		switch r.URL.Path {
		case "/api/v1/ticketmanager/workspace/collaboration-resource":
			listCalled = true
			require.Equal(t, http.MethodGet, r.Method)
			require.Equal(t, "1001", r.URL.Query().Get("workspace_id"))
			require.Equal(t, "2", r.URL.Query().Get("page"))
			require.Equal(t, "50", r.URL.Query().Get("page_size"))
			require.Equal(t, "true", r.URL.Query().Get("with_resource_detail"))
			require.Equal(t, []string{"5001", "5002"}, r.URL.Query()["resource_id"])
			writeResponse(t, w, &wsapi.ListCollaborationResourcesResponse{
				BaseResponse: &baseapi.BaseResponse{ErrCode: http.StatusOK},
				Resources: []*wsent.CollaborationResource{
					{
						CollaborationResourceBinding: wsent.CollaborationResourceBinding{
							Base:        base.Base{ID: 2001, CreatedAt: 100},
							WorkspaceID: 1001,
							ResourceID:  3001,
							BoundBy:     "owner@example.com",
						},
						CollaborationResource: &collabent.Resource{
							Base:       base.Base{ID: 3001, CreatedAt: 100},
							Type:       collabent.TypeChat,
							Provider:   collabent.ProviderLark,
							ResourceID: "chat-id",
							Name:       "Lark Group",
							Link:       "https://example.com/chat",
						},
					},
				},
				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", "--resource-id", "5001", "--resource-id", "5002", "--page", "2", "--page-size", "50"})

	err := cmd.Execute()
	require.NoError(t, err)
	require.True(t, listCalled)
	require.Contains(t, stdout.String(), `"workspace_id": 1001`)
	require.Contains(t, stdout.String(), `"collaboration_resource"`)
	require.Contains(t, stdout.String(), `"resource_id": "chat-id"`)
}

func TestBindLarkGroupBuildsLinkGroupRequest(t *testing.T) {
	var postCalled bool
	var req wsapi.BindCollaborationResourceRequest
	server := setupServer(t, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		switch r.URL.Path {
		case "/api/v1/ticketmanager/workspace/collaboration-resource":
			postCalled = true
			require.Equal(t, http.MethodPost, r.Method)
			require.Equal(t, "application/json", r.Header.Get("Content-Type"))
			require.NoError(t, json.NewDecoder(r.Body).Decode(&req))
			writeResponse(t, w, &wsapi.BindCollaborationResourceResponse{
				BaseResponse: &baseapi.BaseResponse{ErrCode: http.StatusOK},
				Resource: &wsent.CollaborationResource{
					CollaborationResourceBinding: wsent.CollaborationResourceBinding{
						Base:        base.Base{ID: 2001, CreatedAt: 100},
						WorkspaceID: 1001,
						ResourceID:  3001,
						BoundBy:     "owner@example.com",
					},
					CollaborationResource: testCollaborationResource(),
				},
			})
		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{"bind-lark-group", "--workspace-id", "1001", "--group-id", "oc_chat"})

	err := cmd.Execute()
	require.NoError(t, err)
	require.True(t, postCalled)
	require.Equal(t, uint64(1001), req.WorkspaceID)
	require.NotNil(t, req.LarkGroup)
	require.False(t, *req.LarkGroup.CreateLarkGroup)
	require.Equal(t, "oc_chat", req.LarkGroup.LinkLarkGroupID)
	require.True(t, *req.LarkGroup.SetPrimaryIfEmpty)
	require.Nil(t, req.LarkMeeting)
	require.Contains(t, stdout.String(), `"collaboration_resource"`)
}

func TestCreateLarkGroupBuildsCreateGroupRequest(t *testing.T) {
	var req wsapi.BindCollaborationResourceRequest
	server := setupServer(t, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		switch r.URL.Path {
		case "/api/v1/ticketmanager/workspace/collaboration-resource":
			require.Equal(t, http.MethodPost, r.Method)
			require.NoError(t, json.NewDecoder(r.Body).Decode(&req))
			writeResponse(t, w, &wsapi.BindCollaborationResourceResponse{
				BaseResponse: &baseapi.BaseResponse{ErrCode: http.StatusOK},
				Resource: &wsent.CollaborationResource{
					CollaborationResourceBinding: wsent.CollaborationResourceBinding{
						Base:        base.Base{ID: 2001, CreatedAt: 100},
						WorkspaceID: 1001,
						ResourceID:  3001,
						BoundBy:     "owner@example.com",
					},
					CollaborationResource: testCollaborationResource(),
				},
			})
		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-lark-group",
		"--workspace-id",
		"1001",
		"--set-primary-if-empty=false",
	})

	err := cmd.Execute()
	require.NoError(t, err)
	require.Equal(t, uint64(1001), req.WorkspaceID)
	require.NotNil(t, req.LarkGroup)
	require.True(t, *req.LarkGroup.CreateLarkGroup)
	require.Empty(t, req.LarkGroup.LinkLarkGroupID)
	require.False(t, *req.LarkGroup.SetPrimaryIfEmpty)
	require.Nil(t, req.LarkMeeting)
	require.Contains(t, stdout.String(), `"workspace_id": 1001`)
}

func TestBindLarkMeetingBuildsMeetingRequest(t *testing.T) {
	var postCalled bool
	var req wsapi.BindCollaborationResourceRequest
	server := setupServer(t, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		switch r.URL.Path {
		case "/api/v1/ticketmanager/workspace/collaboration-resource":
			postCalled = true
			require.Equal(t, http.MethodPost, r.Method)
			require.Equal(t, "application/json", r.Header.Get("Content-Type"))
			require.NoError(t, json.NewDecoder(r.Body).Decode(&req))
			writeResponse(t, w, &wsapi.BindCollaborationResourceResponse{
				BaseResponse: &baseapi.BaseResponse{ErrCode: http.StatusOK},
				Resource: &wsent.CollaborationResource{
					CollaborationResourceBinding: wsent.CollaborationResourceBinding{
						Base:        base.Base{ID: 2001, CreatedAt: 100},
						WorkspaceID: 1001,
						ResourceID:  3001,
						BoundBy:     "owner@example.com",
					},
					CollaborationResource: &collabent.Resource{
						Base:       base.Base{ID: 3001, CreatedAt: 100},
						Type:       collabent.TypeMeeting,
						Provider:   collabent.ProviderLark,
						ResourceID: "meeting_1",
						Name:       "meeting_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{"bind-lark-meeting", "--workspace-id", "1001", "--meeting-id", "meeting_1"})

	err := cmd.Execute()
	require.NoError(t, err)
	require.True(t, postCalled)
	require.Equal(t, uint64(1001), req.WorkspaceID)
	require.Nil(t, req.LarkGroup)
	require.NotNil(t, req.LarkMeeting)
	require.Equal(t, "meeting_1", req.LarkMeeting.MeetingID)
	require.Contains(t, stdout.String(), `"workspace_id": 1001`)
}

func TestUnbindBuildsDeleteRequest(t *testing.T) {
	var called bool
	var req wsapi.UnbindCollaborationResourceRequest
	server := setupServer(t, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		called = true
		require.Equal(t, http.MethodDelete, r.Method)
		require.Equal(t, "/api/v1/ticketmanager/workspace/collaboration-resource", r.URL.Path)
		require.NoError(t, json.NewDecoder(r.Body).Decode(&req))
		writeResponse(t, w, &baseapi.BaseResponse{ErrCode: http.StatusOK})
	}))

	setupClient(t, server.URL)
	var stdout bytes.Buffer
	setupPrinter(t, &stdout)
	cmd := NewCommand()
	cmd.SetArgs([]string{"unbind", "--workspace-id", "1001", "2001"})

	err := cmd.Execute()
	require.NoError(t, err)
	require.True(t, called)
	require.Equal(t, uint64(1001), req.WorkspaceID)
	require.Equal(t, uint64(2001), req.ID)
	require.Empty(t, stdout.String())
}

func TestWorkspaceIDFlagIsRequired(t *testing.T) {
	cmd := NewCommand()
	cmd.SetArgs([]string{"list"})

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

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

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

func TestBindLarkGroupRequiresGroupID(t *testing.T) {
	cmd := NewCommand()
	cmd.SetArgs([]string{"bind-lark-group", "--workspace-id", "1001"})

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

func TestBindLarkMeetingRequiresMeetingID(t *testing.T) {
	cmd := NewCommand()
	cmd.SetArgs([]string{"bind-lark-meeting", "--workspace-id", "1001"})

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

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 testCollaborationResource() *collabent.Resource {
	return &collabent.Resource{
		Base:       base.Base{ID: 3001, CreatedAt: 100},
		Type:       collabent.TypeChat,
		Provider:   collabent.ProviderLark,
		ResourceID: "chat-id",
		Name:       "Lark Group",
	}
}

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))
}
