package agent

import (
	"net/http"
	"net/http/httptest"
	"strings"
	"testing"

	"github.com/gin-gonic/gin"
	"github.com/stretchr/testify/require"

	"github.com/tidbcloud/AgentTeam/internal/service/agentapproval"
	"github.com/tidbcloud/AgentTeam/internal/service/approvalapp"
	"github.com/tidbcloud/AgentTeam/internal/store/agent"
	"github.com/tidbcloud/AgentTeam/internal/store/approval"
	"github.com/tidbcloud/AgentTeam/internal/store/storeerr"
)

// newManagementRouteRouter wires the production route registration so the test
// exercises the same routes the server mounts (no hand-rolled copy that can drift).
func newManagementRouteRouter(auth AgentAuthenticator, handler *AgentManagementHandler) *gin.Engine {
	gin.SetMode(gin.TestMode)
	router := gin.New()
	base := "/api/v1/workspaces/:workspaceId/agents/:agentId/runtime"
	registerAgentLifecycleManagementRoutes(router, base, auth, handler)
	if handler.approvals != nil {
		registerAgentStructuralManagementRoutes(router, base, auth, handler)
	}
	return router
}

func managementRouteIdentity() agent.AuthenticatedAgent {
	return agent.AuthenticatedAgent{AgentID: "agt_0000000000test", WorkspaceID: "wsp_test", MemberID: "0000000agent"}
}

func managementRequest(t *testing.T, action, body string) *http.Request {
	t.Helper()
	request := httptest.NewRequest("POST",
		"/api/v1/workspaces/wsp_test/agents/agt_0000000000test/runtime/managed-agents/"+action,
		strings.NewReader(body))
	request.Header.Set("Authorization", "Bearer test-token")
	request.Header.Set("Content-Type", "application/json")
	return request
}

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

	handler := newManagementTestHandler(&fakeLifecycleCommands{}, &fakeRuntimeControl{connected: true, running: true}, newReadyReceipts())
	router := newManagementRouteRouter(fakeAgentAuthenticator{agent: managementRouteIdentity()}, handler)

	for _, action := range []string{"start", "stop", "restart"} {
		response := httptest.NewRecorder()
		router.ServeHTTP(response, managementRequest(t, action, `{"requestId":"r","agentId":"agt-target"}`))
		require.NotEqualf(t, http.StatusNotFound, response.Code, "%s route must be registered", action)
	}
}

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

	router := newManagementRouteRouter(fakeAgentAuthenticator{agent: managementRouteIdentity()},
		newManagementTestHandler(&fakeLifecycleCommands{}, &fakeRuntimeControl{connected: true}, newReadyReceipts()))

	response := httptest.NewRecorder()
	router.ServeHTTP(response, managementRequest(t, "start", `{"requestId":"r","agentId":"a","rogueField":1}`))
	require.Equal(t, http.StatusBadRequest, response.Code, "strict JSON rejects unknown fields before the handler runs")
}

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

	router := newManagementRouteRouter(fakeAgentAuthenticator{agent: managementRouteIdentity()},
		newManagementTestHandler(&fakeLifecycleCommands{}, &fakeRuntimeControl{connected: true}, newReadyReceipts()))

	request := managementRequest(t, "start", `{"requestId":"r","agentId":"a"}`)
	request.Header.Set("Authorization", "Bearer wrong-token")
	response := httptest.NewRecorder()
	router.ServeHTTP(response, request)
	require.Equal(t, http.StatusUnauthorized, response.Code, "a bad bearer is rejected before the handler runs")
}

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

	commands := &fakeLifecycleCommands{}
	control := &fakeRuntimeControl{connected: true, running: true}
	receipts := newReadyReceipts()
	handler := newManagementTestHandler(commands, control, receipts)
	handler.owners = fakeManagementOwner{ownerUserID: "usr-owner", getErr: storeerr.NotFound("agent not owned by caller's human")}
	router := newManagementRouteRouter(fakeAgentAuthenticator{agent: managementRouteIdentity()}, handler)

	response := httptest.NewRecorder()
	router.ServeHTTP(response, managementRequest(t, "start", `{"requestId":"r","agentId":"agt_000000000other_human_agent"}`))

	require.Equal(t, http.StatusNotFound, response.Code, "a cross-owner target maps to 404, never a dispatch")
	require.Empty(t, commands.startedIDs, "no runtime dispatch on a cross-owner target")
	require.Equal(t, 0, control.restarts, "no restart on a cross-owner target")
	require.Equal(t, 0, receipts.reserveCalls, "no receipt reserved before ownership is confirmed")
}

func TestManagementRouteRegistersAllSevenActions(t *testing.T) {
	t.Parallel()
	handler := newManagementTestHandler(&fakeLifecycleCommands{}, &fakeRuntimeControl{connected: true, running: true}, newReadyReceipts())
	handler.approvals = &fakeApprovalRuntime{submitResult: approvalapp.SubmitResult{Instance: approval.Instance{ID: "appr_test", State: approval.StatePending}}}
	router := newManagementRouteRouter(fakeAgentAuthenticator{agent: managementRouteIdentity()}, handler)
	bodies := map[string]string{
		"start":     `{"requestId":"r","agentId":"a"}`,
		"stop":      `{"requestId":"r","agentId":"a"}`,
		"restart":   `{"requestId":"r","agentId":"a"}`,
		"create":    `{"requestId":"r","name":"n","machineId":"mac","runtime":"rt"}`,
		"update":    `{"requestId":"r","agentId":"a","description":"d"}`,
		"delete":    `{"requestId":"r","agentIds":["a"]}`,
		"duplicate": `{"requestId":"r","sourceAgentId":"a","count":1}`,
	}
	for _, action := range []string{"start", "stop", "restart", "create", "update", "delete", "duplicate"} {
		response := httptest.NewRecorder()
		router.ServeHTTP(response, managementRequest(t, action, bodies[action]))
		require.NotEqualf(t, http.StatusNotFound, response.Code, "%s route must be registered", action)
	}
}

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

	auth := fakeAgentAuthenticator{agent: managementRouteIdentity()}
	cases := []struct {
		name         string
		dependencies Dependencies
		want         []string
		notWant      []string
	}{
		{
			name: "structural only",
			dependencies: Dependencies{
				Auth:                 auth,
				AgentManagementOwner: fakeManagementOwner{ownerUserID: "usr_000000000owner"},
				ApprovalSubjects: &fakeApprovalRuntime{submitResult: approvalapp.SubmitResult{
					Instance: approval.Instance{ID: "appr_structural", State: approval.StatePending},
				}},
			},
			want:    []string{"create", "update", "delete", "duplicate"},
			notWant: []string{"start", "stop", "restart"},
		},
		{
			name: "lifecycle only",
			dependencies: Dependencies{
				Auth:                   auth,
				AgentManagementOwner:   fakeManagementOwner{ownerUserID: "usr_000000000owner", target: agent.AgentView{AgentID: "agt-target", MachineID: "mac"}},
				AgentLifecycleCommands: &fakeLifecycleCommands{},
				AgentRuntimeControl:    &fakeRuntimeControl{connected: true},
				AgentLifecycleReceipts: newReadyReceipts(),
			},
			want:    []string{"start", "stop", "restart"},
			notWant: []string{"create", "update", "delete", "duplicate"},
		},
	}
	for _, tc := range cases {
		t.Run(tc.name, func(t *testing.T) {
			router := gin.New()
			RegisterRoutes(router, tc.dependencies)
			registered := make(map[string]bool)
			for _, route := range router.Routes() {
				registered[route.Path] = true
			}
			base := "/api/v1/workspaces/:workspaceId/agents/:agentId/runtime/managed-agents/"
			for _, action := range tc.want {
				require.Truef(t, registered[base+action], "%s route must be registered", action)
			}
			for _, action := range tc.notWant {
				require.Falsef(t, registered[base+action], "%s route must not be registered", action)
			}
		})
	}
}

func TestManagementRouteStructuralActionsRejectUnknownJSONField(t *testing.T) {
	t.Parallel()
	handler := newManagementTestHandler(&fakeLifecycleCommands{}, &fakeRuntimeControl{connected: true}, newReadyReceipts())
	handler.approvals = &fakeApprovalRuntime{}
	router := newManagementRouteRouter(fakeAgentAuthenticator{agent: managementRouteIdentity()}, handler)
	for _, action := range []string{"create", "update", "delete", "duplicate"} {
		response := httptest.NewRecorder()
		router.ServeHTTP(response, managementRequest(t, action, `{"requestId":"r","rogueField":1}`))
		require.Equalf(t, http.StatusBadRequest, response.Code, "%s must reject unknown fields", action)
	}
}

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

	// cross-owner: update target + delete 2nd item.
	crossOwner := fakeManagementOwner{ownerUserID: "usr_000000000owner", crossOwnerAgents: map[string]string{"agt_000000000other": "usr_000000000other"}}
	crossApprovals := &fakeApprovalRuntime{submitResult: approvalapp.SubmitResult{Instance: approval.Instance{ID: "appr_co", State: approval.StatePending}}}
	crossHandler := newManagementTestHandler(&fakeLifecycleCommands{}, &fakeRuntimeControl{connected: true}, newReadyReceipts())
	crossHandler.owners = crossOwner
	crossHandler.approvals = crossApprovals
	crossRouter := newManagementRouteRouter(fakeAgentAuthenticator{agent: managementRouteIdentity()}, crossHandler)

	r1 := httptest.NewRecorder()
	crossRouter.ServeHTTP(r1, managementRequest(t, "update", `{"requestId":"r","agentId":"agt_000000000other","description":"updated"}`))
	require.Equal(t, http.StatusForbidden, r1.Code)
	require.Equal(t, 0, crossApprovals.submitCount, "update cross-owner must not submit")

	r2 := httptest.NewRecorder()
	crossRouter.ServeHTTP(r2, managementRequest(t, "delete", `{"requestId":"r","agentIds":["agt_000000000owner","agt_000000000other"]}`))
	require.Equal(t, http.StatusForbidden, r2.Code)
	require.Equal(t, 0, crossApprovals.submitCount, "delete cross-owner must not submit")

	// machine error: create + duplicate.
	machineOwner := fakeManagementOwner{ownerUserID: "usr_000000000owner", machineErr: storeerr.Forbidden("not your machine")}
	machineApprovals := &fakeApprovalRuntime{submitResult: approvalapp.SubmitResult{Instance: approval.Instance{ID: "appr_mac", State: approval.StatePending}}}
	machineHandler := newManagementTestHandler(&fakeLifecycleCommands{}, &fakeRuntimeControl{connected: true}, newReadyReceipts())
	machineHandler.owners = machineOwner
	machineHandler.approvals = machineApprovals
	machineRouter := newManagementRouteRouter(fakeAgentAuthenticator{agent: managementRouteIdentity()}, machineHandler)

	r3 := httptest.NewRecorder()
	machineRouter.ServeHTTP(r3, managementRequest(t, "create", `{"requestId":"r","name":"n","machineId":"mac_other","runtime":"rt"}`))
	require.Equal(t, http.StatusForbidden, r3.Code)
	require.Equal(t, 0, machineApprovals.submitCount, "create machine error must not submit")

	r4 := httptest.NewRecorder()
	machineRouter.ServeHTTP(r4, managementRequest(t, "duplicate", `{"requestId":"r","sourceAgentId":"agt_00000000000src","count":1,"machineId":"mac_other"}`))
	require.Equal(t, http.StatusForbidden, r4.Code)
	require.Equal(t, 0, machineApprovals.submitCount, "duplicate machine error must not submit")
}

func TestManagementRouteStructuralHappyResponse(t *testing.T) {
	t.Parallel()
	approvals := &fakeApprovalRuntime{submitResult: approvalapp.SubmitResult{Instance: approval.Instance{ID: "appr_happy", State: approval.StatePending}}}
	handler := newManagementTestHandler(&fakeLifecycleCommands{}, &fakeRuntimeControl{connected: true}, newReadyReceipts())
	handler.owners = fakeManagementOwner{ownerUserID: "usr_000000000owner"}
	handler.approvals = approvals
	router := newManagementRouteRouter(fakeAgentAuthenticator{agent: managementRouteIdentity()}, handler)

	response := httptest.NewRecorder()
	router.ServeHTTP(response, managementRequest(t, "create", `{"requestId":"r","name":"n","machineId":"mac","runtime":"rt"}`))
	require.Equal(t, http.StatusOK, response.Code)
	require.Contains(t, response.Body.String(), `"status":"pending"`)
	require.Contains(t, response.Body.String(), `"approvalId":"appr_happy"`)
}

func TestManagementRouteDuplicateCountValidation(t *testing.T) {
	t.Parallel()
	approvals := &fakeApprovalRuntime{submitResult: approvalapp.SubmitResult{Instance: approval.Instance{ID: "appr_dup", State: approval.StatePending}}}
	handler := newManagementTestHandler(&fakeLifecycleCommands{}, &fakeRuntimeControl{connected: true}, newReadyReceipts())
	handler.owners = fakeManagementOwner{ownerUserID: "usr_000000000owner"}
	handler.approvals = approvals
	router := newManagementRouteRouter(fakeAgentAuthenticator{agent: managementRouteIdentity()}, handler)

	resp1 := httptest.NewRecorder()
	router.ServeHTTP(resp1, managementRequest(t, "duplicate", `{"requestId":"r","sourceAgentId":"a"}`))
	require.Equal(t, http.StatusOK, resp1.Code)
	submit, ok := approvals.submitRequest.Payload.(agentapproval.SubmitInput)
	require.True(t, ok)
	require.Equal(t, 1, submit.Payload.Duplicate.Count)
	require.Equal(t, 1, approvals.submitCount, "omitted count submits exactly once with the default")

	for _, body := range []string{
		`{"requestId":"r2","sourceAgentId":"a","count":0}`,
		`{"requestId":"r3","sourceAgentId":"a","count":11}`,
	} {
		response := httptest.NewRecorder()
		router.ServeHTTP(response, managementRequest(t, "duplicate", body))
		require.Equal(t, http.StatusBadRequest, response.Code)
		require.Equal(t, 1, approvals.submitCount, "invalid count must not submit")
	}
}
