package workspace

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

func TestListBuildsWorkspaceRequest(t *testing.T) {
	var called bool
	server := setupServer(t, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		called = true
		require.Equal(t, http.MethodGet, r.Method)
		require.Equal(t, "/api/v1/ticketmanager/workspace/workspaces", r.URL.Path)
		require.Equal(t, "owner@example.com", r.URL.Query().Get("member"))
		writeResponse(t, w, &wsapi.ListWorkspacesResponse{
			BaseResponse: &baseapi.BaseResponse{ErrCode: http.StatusOK},
			Workspaces: []*wsent.Workspace{
				{
					Updatable: base.Updatable{Base: base.Base{ID: 1001}},
					Name:      "workspace-a",
					Members: []wsent.WorkspaceMember{{
						Email: "owner@example.com",
						Role:  wsent.WorkspaceMemberRoleOwner,
						Group: "default",
					}},
					Status: wsent.WorkspaceStatusFiring,
				},
			},
		})
	}))

	setupClient(t, server.URL)
	cmd := NewCommand()
	var stdout bytes.Buffer
	setupPrinter(t, &stdout)
	cmd.SetOut(&stdout)
	cmd.SetArgs([]string{"list", "--member", "owner@example.com"})

	err := cmd.Execute()
	require.NoError(t, err)
	require.True(t, called)
	require.Contains(t, stdout.String(), `"id": 1001`)
	require.Contains(t, stdout.String(), `"name": "workspace-a"`)
}

func TestWorkspaceCommandRegistersCollabCommand(t *testing.T) {
	cmd := NewCommand()

	found, _, err := cmd.Find([]string{"collab"})
	require.NoError(t, err)
	require.Equal(t, "collab", found.Name())

	found, _, err = cmd.Find([]string{"advice"})
	require.NoError(t, err)
	require.Equal(t, "advice", found.Name())

	found, _, err = cmd.Find([]string{"task"})
	require.NoError(t, err)
	require.Equal(t, "task", found.Name())

	found, _, err = cmd.Find([]string{"archive"})
	require.NoError(t, err)
	require.Equal(t, "archive", found.Name())

	found, _, err = cmd.Find([]string{"recover"})
	require.NoError(t, err)
	require.Equal(t, "recover", found.Name())
}

func TestGetBuildsWorkspaceRequest(t *testing.T) {
	var called bool
	server := setupServer(t, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		called = true
		require.Equal(t, http.MethodGet, r.Method)
		require.Equal(t, "/api/v1/ticketmanager/workspace/workspaces/1001", r.URL.Path)
		writeResponse(t, w, &wsapi.GetWorkspaceResponse{
			BaseResponse: &baseapi.BaseResponse{ErrCode: http.StatusOK},
			Workspace: &wsent.Workspace{
				Updatable: base.Updatable{Base: base.Base{ID: 1001}},
				Name:      "workspace-a",
				Members: []wsent.WorkspaceMember{{
					Email: "owner@example.com",
					Role:  wsent.WorkspaceMemberRoleOwner,
					Group: "default",
				}},
				Status: wsent.WorkspaceStatusFiring,
			},
		})
	}))

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

	err := cmd.Execute()
	require.NoError(t, err)
	require.True(t, called)
}

func TestUpdateBuildsWorkspaceRequest(t *testing.T) {
	var called bool
	var req wsapi.UpdateWorkspaceRequest
	server := setupServer(t, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		called = true
		require.Equal(t, http.MethodPut, r.Method)
		require.Equal(t, "/api/v1/ticketmanager/workspace/workspaces/1001", r.URL.Path)
		require.Equal(t, "application/json", r.Header.Get("Content-Type"))
		require.NoError(t, json.NewDecoder(r.Body).Decode(&req))
		writeResponse(t, w, &wsapi.UpdateWorkspaceResponse{
			BaseResponse: &baseapi.BaseResponse{ErrCode: http.StatusOK},
			Workspace: &wsent.Workspace{
				Updatable: base.Updatable{Base: base.Base{ID: 1001}},
				Name:      "workspace name",
				Members: []wsent.WorkspaceMember{{
					Email: "owner@example.com",
					Role:  wsent.WorkspaceMemberRoleOwner,
					Group: "default",
				}},
				Status: wsent.WorkspaceStatusFiring,
			},
		})
	}))

	setupClient(t, server.URL)
	cmd := NewCommand()
	var stdout bytes.Buffer
	setupPrinter(t, &stdout)
	cmd.SetArgs([]string{
		"update",
		"1001",
		"--name",
		"workspace name",
		"--description",
		"workspace description",
		"--severity",
		"P2",
		"--member",
		"owner@example.com:Owner:default",
		"--member",
		"oncall@example.com:Component Owner:sre",
	})

	err := cmd.Execute()
	require.NoError(t, err)
	require.True(t, called)
	require.NotNil(t, req.Name)
	require.Equal(t, "workspace name", *req.Name)
	require.NotNil(t, req.Description)
	require.Equal(t, "workspace description", *req.Description)
	require.NotNil(t, req.Severity)
	require.Equal(t, wsent.WorkspaceSeverityP2, *req.Severity)
	require.NotNil(t, req.Members)
	require.Equal(t, []wsent.WorkspaceMember{
		{
			Email: "owner@example.com",
			Role:  wsent.WorkspaceMemberRoleOwner,
			Group: "default",
		},
		{
			Email: "oncall@example.com",
			Role:  wsent.WorkspaceMemberRoleComponentOwner,
			Group: "sre",
		},
	}, *req.Members)
}

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

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

func TestArchiveBuildsWorkspaceRequest(t *testing.T) {
	var called bool
	server := setupServer(t, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		called = true
		require.Equal(t, http.MethodPost, r.Method)
		require.Equal(t, "/api/v1/ticketmanager/workspace/workspaces/1001/archive", r.URL.Path)
		writeResponse(t, w, &wsapi.ArchiveWorkspaceResponse{
			BaseResponse: &baseapi.BaseResponse{ErrCode: http.StatusOK},
			Workspace: &wsent.Workspace{
				Updatable: base.Updatable{Base: base.Base{ID: 1001}},
				Name:      "workspace-a",
				Members: []wsent.WorkspaceMember{{
					Email: "owner@example.com",
					Role:  wsent.WorkspaceMemberRoleOwner,
					Group: "default",
				}},
				Status: wsent.WorkspaceStatusArchived,
			},
		})
	}))

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

	err := cmd.Execute()
	require.NoError(t, err)
	require.True(t, called)
	require.Contains(t, stdout.String(), `"status": "archived"`)
}

func TestRecoverBuildsWorkspaceRequest(t *testing.T) {
	var called bool
	recoveredAt := time.Date(2026, 5, 13, 10, 0, 0, 0, time.UTC).Unix()
	server := setupServer(t, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		called = true
		require.Equal(t, http.MethodPost, r.Method)
		require.Equal(t, "/api/v1/ticketmanager/workspace/workspaces/1001/recover", r.URL.Path)
		writeResponse(t, w, &wsapi.RecoverWorkspaceResponse{
			BaseResponse: &baseapi.BaseResponse{ErrCode: http.StatusOK},
			Workspace: &wsent.Workspace{
				Updatable: base.Updatable{Base: base.Base{ID: 1001}},
				Name:      "workspace-a",
				Members: []wsent.WorkspaceMember{{
					Email: "owner@example.com",
					Role:  wsent.WorkspaceMemberRoleOwner,
					Group: "default",
				}},
				Status:      wsent.WorkspaceStatusRecovered,
				RecoveredAt: &recoveredAt,
			},
		})
	}))

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

	err := cmd.Execute()
	require.NoError(t, err)
	require.True(t, called)
	require.Contains(t, stdout.String(), `"status": "recovered"`)
	require.Contains(t, stdout.String(), `"recovered_at": 1778666400`)
}

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