package app

import (
	"encoding/json"
	"net/http"
	"testing"

	"github.com/stretchr/testify/require"

	"github.com/tidbcloud/AgentTeam/internal/api/testkit/httputil"
	storeapp "github.com/tidbcloud/AgentTeam/internal/store/app"
)

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

	fake := newFakeStore()
	fake.appManagers = []storeapp.AppManagerAssignment{
		{
			ID: "botmanager_creator", WorkspaceID: "wsp_1", AppID: "app_bot_1",
			MemberID: "mem_creator", Status: storeapp.AppManagerStatusActive, Version: 1,
			GrantedByMemberID: "mem_creator",
		},
	}
	fake.appManagerResult = storeapp.AppManagerAssignment{
		ID: "botmanager_peer", WorkspaceID: "wsp_1", AppID: "app_bot_1",
		MemberID: "mem_peer", Status: storeapp.AppManagerStatusActive, Version: 7,
		GrantedByMemberID: "mem_creator",
	}
	fake.appAvatarResult = storeapp.AppInstallationView{
		ID: "appinst_bot_1", AppID: "app_bot_1", AvatarURL: "/api/v1/avatar-assets/ava_bot_1",
	}
	router := newAppRouter(Dependencies{AppStore: fake})
	base := "/api/v1/workspaces/wsp_1/app-installations/appinst_bot_1"

	listed := httputil.DoJSON(t, router, http.MethodGet, base+"/managers", nil)
	require.Equal(t, http.StatusOK, listed.Code)
	require.Equal(t, "appinst_bot_1", fake.lastAppManagerInstallID)
	var listPayload map[string][]map[string]any
	require.NoError(t, json.Unmarshal(listed.Body.Bytes(), &listPayload))
	require.Equal(t, []map[string]any{{
		"id":       "botmanager_creator",
		"memberId": "mem_creator",
		"status":   storeapp.AppManagerStatusActive,
		"version":  float64(1),
	}}, listPayload["managers"])
	encodedList := listed.Body.String()
	require.NotContains(t, encodedList, "workspaceId")
	require.NotContains(t, encodedList, "appId")
	require.NotContains(t, encodedList, "grantedByMemberId")

	added := httputil.DoJSON(t, router, http.MethodPost, base+"/managers", map[string]any{
		"memberId": "mem_peer",
	})
	require.Equal(t, http.StatusOK, added.Code)
	require.Equal(t, "appinst_bot_1", fake.lastAppManagerInstallID)
	require.Equal(t, "mem_peer", fake.lastAppManagerMemberID)
	require.JSONEq(t, `{
		"id":"botmanager_peer",
		"memberId":"mem_peer",
		"status":"active",
		"version":7
	}`, added.Body.String())

	removed := httputil.DoJSON(t, router, http.MethodDelete, base+"/managers/botmanager_peer?expectedVersion=7", nil)
	require.Equal(t, http.StatusOK, removed.Code)
	require.Equal(t, "appinst_bot_1", fake.lastAppManagerInstallID)
	require.Equal(t, "botmanager_peer", fake.lastAppManagerID)
	require.EqualValues(t, 7, fake.lastAppManagerVersion)

	avatar := httputil.DoJSON(t, router, http.MethodPatch, base+"/avatar", map[string]any{
		"avatarAssetId": "ava_bot_1",
	})
	require.Equal(t, http.StatusOK, avatar.Code)
	require.Equal(t, "appinst_bot_1", fake.lastAppAvatarInstallID)
	require.Equal(t, "ava_bot_1", fake.lastAppAvatarAssetID)
	require.JSONEq(t, `{"avatarUrl":"/api/v1/avatar-assets/ava_bot_1"}`, avatar.Body.String())
}

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

	fake := newFakeStore()
	router := newAppRouter(Dependencies{AppStore: fake})
	response := httputil.DoJSON(
		t,
		router,
		http.MethodPost,
		"/api/v1/workspaces/wsp_1/app-installations/appinst_bot_1/managers",
		map[string]any{},
	)
	require.Equal(t, http.StatusBadRequest, response.Code)
	require.Empty(t, fake.lastAppManagerInstallID, "invalid input must not reach the store")
}

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

	for _, expectedVersion := range []string{"", "0", "-1", "not-a-number"} {
		expectedVersion := expectedVersion
		t.Run(expectedVersion, func(t *testing.T) {
			t.Parallel()
			fake := newFakeStore()
			router := newAppRouter(Dependencies{AppStore: fake})
			path := "/api/v1/workspaces/wsp_1/app-installations/appinst_bot_1/managers/botmanager_1"
			if expectedVersion != "" {
				path += "?expectedVersion=" + expectedVersion
			}
			response := httputil.DoJSON(t, router, http.MethodDelete, path, nil)
			require.Equal(t, http.StatusBadRequest, response.Code)
			require.Empty(t, fake.lastAppManagerInstallID, "invalid concurrency input must not reach the store")
		})
	}
}

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

	fake := newFakeStore()
	fake.appAvatarResult = storeapp.AppInstallationView{ID: "appinst_bot_1", AvatarURL: ""}
	router := newAppRouter(Dependencies{AppStore: fake})
	response := httputil.DoJSON(
		t,
		router,
		http.MethodPatch,
		"/api/v1/workspaces/wsp_1/app-installations/appinst_bot_1/avatar",
		map[string]any{"avatarAssetId": ""},
	)
	require.Equal(t, http.StatusOK, response.Code)
	require.Equal(t, "appinst_bot_1", fake.lastAppAvatarInstallID)
	require.Empty(t, fake.lastAppAvatarAssetID)
	require.JSONEq(t, `{"avatarUrl":""}`, response.Body.String())
}
