package webagent

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

	"github.com/stretchr/testify/require"

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

func TestReadModelHandlers(t *testing.T) {
	t.Run("directory forwards current role binding option", func(t *testing.T) {
		fake := newFakeStore()
		fake.directoryPage = agent.AgentDirectoryPage{
			Agents: []agent.AgentDirectoryItem{{
				ID:   "agt_1",
				Name: "Agent One",
				CurrentRoleBinding: &agent.AgentRoleBindingSummary{
					BindingKind: "preset_derived",
					RoleID:      "role_1",
				},
			}},
			Limit: 1,
		}
		router := newWebAgentRouter(Dependencies{AgentStore: fake, ReviewStore: fake})

		resp := httputil.DoJSON(t, router, http.MethodGet, "/api/v1/workspaces/wsp_1/agents/directory?includeCurrentRoleBinding=true", nil)
		require.Equal(t, http.StatusOK, resp.Code)
		require.True(t, fake.lastDirectory.IncludeCurrentRoleBinding)

		var out dto.AgentDirectoryResponse
		require.NoError(t, json.Unmarshal(resp.Body.Bytes(), &out))
		require.Len(t, out.Agents, 1)
		require.NotNil(t, out.Agents[0].CurrentRoleBinding)
		require.Equal(t, "preset-derived", out.Agents[0].CurrentRoleBinding.BindingKind)
		require.Equal(t, "role_1", out.Agents[0].CurrentRoleBinding.RoleID)
	})

	t.Run("detail loads one requested agent", func(t *testing.T) {
		fake := newFakeStore()
		runtime := &fakeRuntime{
			connectedMachines: map[string]bool{"mach_1": true},
			runningAgents:     map[agentCall]bool{{MachineID: "mach_1", AgentID: "agt_1"}: true},
		}
		router := newWebAgentRouter(Dependencies{AgentStore: fake, ReviewStore: fake, Runtime: runtime})

		resp := httputil.DoJSON(t, router, http.MethodGet, "/api/v1/workspaces/wsp_1/agents/agt_1", nil)
		require.Equal(t, http.StatusOK, resp.Code)

		var out dto.AgentDetail
		require.NoError(t, json.Unmarshal(resp.Body.Bytes(), &out))
		require.Equal(t, "agt_1", out.ID)
		require.Equal(t, "online", out.MachineStatus)
		require.Equal(t, "active", out.Status)
		require.Equal(t, "online", out.Activity)
		require.Empty(t, out.LatestRuntimeFailure)
	})

	t.Run("agent list only exposes detailed live runtime state to owners", func(t *testing.T) {
		fake := newFakeStore()
		fake.agents = []agent.AppAgent{
			{ID: "agt_owned", MachineID: "mac_owned", CanManage: true, Status: "inactive", Activity: "offline"},
			{ID: "agt_shared", MachineID: "mac_shared", CanManage: false, Status: "inactive", Activity: "offline"},
		}
		runtime := &fakeRuntime{
			connectedMachines: map[string]bool{"mac_owned": true, "mac_shared": true},
			runningAgents: map[agentCall]bool{
				{MachineID: "mac_owned", AgentID: "agt_owned"}:   true,
				{MachineID: "mac_shared", AgentID: "agt_shared"}: true,
			},
			sessionIDs: map[agentCall]string{
				{MachineID: "mac_owned", AgentID: "agt_owned"}:   "sess_owned",
				{MachineID: "mac_shared", AgentID: "agt_shared"}: "sess_private",
			},
			runtimeStatuses: map[agentCall]agentRuntimeStatus{
				{MachineID: "mac_owned", AgentID: "agt_owned"}:   {Activity: "working", Detail: "Owner detail"},
				{MachineID: "mac_shared", AgentID: "agt_shared"}: {Activity: "working", Detail: "Private detail"},
			},
		}
		router := newWebAgentRouter(Dependencies{AgentStore: fake, ReviewStore: fake, Runtime: runtime})

		resp := httputil.DoJSON(t, router, http.MethodGet, "/api/v1/workspaces/wsp_1/agents", nil)
		require.Equal(t, http.StatusOK, resp.Code)

		var out []dto.AppAgent
		require.NoError(t, json.Unmarshal(resp.Body.Bytes(), &out))
		require.Len(t, out, 2)
		require.Equal(t, "Owner detail", out[0].ActivityDetail)
		require.NotNil(t, out[0].CurrentInstance)
		require.Equal(t, "sess_owned", out[0].CurrentInstance.SessionID)
		require.Equal(t, "active", out[1].Status)
		require.Equal(t, "online", out[1].Activity)
		require.Empty(t, out[1].ActivityDetail)
		require.Nil(t, out[1].CurrentInstance)
	})

	t.Run("runtime failures normalize requested ids", func(t *testing.T) {
		now := time.Now().UTC()
		fake := newFakeStore()
		fake.runtimeFailures = map[string]agent.AgentRuntimeFailureSummary{
			"agt_1": {
				EventClass: agent.AgentRuntimeEventClassWakeFailed,
				Summary:    "wake failed",
				OccurredAt: now,
			},
		}
		router := newWebAgentRouter(Dependencies{AgentStore: fake, ReviewStore: fake})

		resp := httputil.DoJSON(t, router, http.MethodPost, "/api/v1/workspaces/wsp_1/agents/runtime-failures", map[string]any{
			"agentIds": []string{" agt_1 ", "agt_1", "", "agt_2"},
		})
		require.Equal(t, http.StatusOK, resp.Code)
		require.Equal(t, []string{"agt_1", "agt_2"}, fake.lastRuntimeFailureIDs)

		var out dto.AgentRuntimeFailuresResponse
		require.NoError(t, json.Unmarshal(resp.Body.Bytes(), &out))
		require.Len(t, out.Failures, 1)
		require.Equal(t, "agt_1", out.Failures[0].AgentID)
		require.NotNil(t, out.Failures[0].Failure)
	})

	t.Run("single runtime failure uses path agent id", func(t *testing.T) {
		now := time.Now().UTC()
		fake := newFakeStore()
		fake.runtimeFailure = &agent.AgentRuntimeFailureSummary{
			EventClass: agent.AgentRuntimeEventClassWakeFailed,
			Summary:    "wake failed",
			OccurredAt: now,
		}
		router := newWebAgentRouter(Dependencies{AgentStore: fake, ReviewStore: fake})

		resp := httputil.DoJSON(t, router, http.MethodGet, "/api/v1/workspaces/wsp_1/agents/agt_1/runtime-failure", nil)
		require.Equal(t, http.StatusOK, resp.Code)
		require.Equal(t, "agt_1", fake.lastRuntimeFailureID)

		var out dto.AgentRuntimeFailureItem
		require.NoError(t, json.Unmarshal(resp.Body.Bytes(), &out))
		require.Equal(t, "agt_1", out.AgentID)
		require.NotNil(t, out.Failure)
	})

	t.Run("directory forwards bounded query pagination and team assignment options", func(t *testing.T) {
		fake := newFakeStore()
		fake.directoryPage = agent.AgentDirectoryPage{
			Agents: []agent.AgentDirectoryItem{{
				ID:              "agt_1",
				Name:            "alpha",
				MemberID:        "mem_agt_1",
				DisplayName:     "Alpha Agent",
				CanManage:       true,
				TeamID:          "team_1",
				TeamDisplayName: "Research Team",
			}},
			Limit:      20,
			Cursor:     "cursor_1",
			HasMore:    true,
			NextCursor: "cursor_2",
		}
		router := newWebAgentRouter(Dependencies{AgentStore: fake, ReviewStore: fake})

		resp := httputil.DoJSON(t, router, http.MethodGet, "/api/v1/workspaces/wsp_1/agents/directory?scope=owned&q=Alpha&limit=20&cursor=cursor_1&includeTeamAssignment=true", nil)
		require.Equal(t, http.StatusOK, resp.Code)

		require.Equal(t, agent.AgentDirectoryOptions{
			Scope:                 "owned",
			Limit:                 20,
			Cursor:                "cursor_1",
			Query:                 "Alpha",
			IncludeTeamAssignment: true,
		}, fake.lastDirectory)

		var out dto.AgentDirectoryResponse
		require.NoError(t, json.Unmarshal(resp.Body.Bytes(), &out))
		require.Len(t, out.Agents, 1)
		require.Equal(t, "team_1", out.Agents[0].TeamID)
		require.Equal(t, "Research Team", out.Agents[0].TeamDisplayName)
		require.True(t, out.HasMore)
		require.Equal(t, "cursor_2", out.NextCursor)
	})
}
