package agent

import (
	"context"
	"net/http"
	"testing"

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

	storeagent "github.com/tidbcloud/AgentTeam/internal/store/agent"
)

type fakeAgentProjectStore struct {
	listAgent    storeagent.AuthenticatedAgent
	listOptions  storeagent.ProjectListOptions
	listPage     storeagent.ProjectPage
	listErr      error
	createAgent  storeagent.AuthenticatedAgent
	createParams storeagent.CreateProjectParams
	createRan    bool
	deleteAgent  storeagent.AuthenticatedAgent
	deleteID     string
}

func (s *fakeAgentProjectStore) ListProjectPageForAgent(_ context.Context, agent storeagent.AuthenticatedAgent, options storeagent.ProjectListOptions) (storeagent.ProjectPage, error) {
	s.listAgent = agent
	s.listOptions = options
	if s.listErr != nil {
		return storeagent.ProjectPage{}, s.listErr
	}
	if s.listPage.Projects == nil {
		return storeagent.ProjectPage{Projects: []storeagent.Project{{ID: "prj_1", WorkspaceID: "wsp_test", MachineID: "mach_test", RootPath: "/srv", Name: "AgentTeam"}}}, nil
	}
	return s.listPage, nil
}

func (s *fakeAgentProjectStore) CreateProjectForAgent(_ context.Context, agent storeagent.AuthenticatedAgent, params storeagent.CreateProjectParams) (storeagent.Project, error) {
	s.createAgent = agent
	s.createParams = params
	s.createRan = true
	return storeagent.Project{ID: "prj_new", WorkspaceID: params.WorkspaceID, MachineID: params.MachineID, RootPath: params.RootPath, Name: params.Name, OwnerUserID: "usr_0000000machine_owner"}, nil
}

func (s *fakeAgentProjectStore) DeleteProjectForAgent(_ context.Context, agent storeagent.AuthenticatedAgent, projectID string) error {
	s.deleteAgent = agent
	s.deleteID = projectID
	return nil
}

func projectTestRouter(auth AgentAuthenticator, store AgentProjectStore) *gin.Engine {
	gin.SetMode(gin.TestMode)
	router := gin.New()
	RegisterRoutes(router, Dependencies{Auth: auth, ProjectStore: store})
	return router
}

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

	auth := fakeAgentAuthenticator{agent: storeagent.AuthenticatedAgent{AgentID: "agt_0000000000test", WorkspaceID: "wsp_test", MemberID: "00000000test", MachineID: "mach_test"}}
	store := &fakeAgentProjectStore{}
	response := agentTaskRequest(t, projectTestRouter(auth, store), http.MethodGet, "/projects", nil)

	require.Equal(t, http.StatusOK, response.Code)
	require.Equal(t, "agt_0000000000test", store.listAgent.AgentID)
	require.Equal(t, "00000000test", store.listAgent.MemberID)
}

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

	auth := fakeAgentAuthenticator{agent: storeagent.AuthenticatedAgent{AgentID: "agt_0000000000test", WorkspaceID: "wsp_test", MemberID: "00000000test", MachineID: "mach_test"}}
	store := &fakeAgentProjectStore{}
	response := agentTaskRequest(t, projectTestRouter(auth, store), http.MethodPost, "/projects", map[string]any{
		"rootPath": "/srv/agentteam",
		"name":     "AgentTeam",
	})

	require.Equal(t, http.StatusOK, response.Code)
	require.Equal(t, "00000000test", store.createAgent.MemberID)
	require.Equal(t, "mach_test", store.createParams.MachineID, "machineId comes from the principal, never the body")
	require.Equal(t, "/srv/agentteam", store.createParams.RootPath)
}

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

	auth := fakeAgentAuthenticator{agent: storeagent.AuthenticatedAgent{AgentID: "agt_0000000000test", WorkspaceID: "wsp_test", MemberID: "00000000test", MachineID: "mach_test"}}
	store := &fakeAgentProjectStore{}
	response := agentTaskRequest(t, projectTestRouter(auth, store), http.MethodPost, "/projects", map[string]any{
		"rootPath":  "/srv/agentteam",
		"name":      "AgentTeam",
		"machineId": "forged_by_client",
	})

	require.Equal(t, http.StatusOK, response.Code)
	require.Equal(t, "mach_test", store.createParams.MachineID, "a client-supplied machineId is ignored")
}

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

	auth := fakeAgentAuthenticator{agent: storeagent.AuthenticatedAgent{AgentID: "agt_0000000000test", WorkspaceID: "wsp_test", MemberID: "00000000test"}}
	store := &fakeAgentProjectStore{}
	response := agentTaskRequest(t, projectTestRouter(auth, store), http.MethodPost, "/projects", map[string]any{
		"rootPath": "/srv/agentteam",
		"name":     "AgentTeam",
	})

	require.Equal(t, http.StatusBadRequest, response.Code)
	require.False(t, store.createRan, "store create must not run when the principal has no machine")
}

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

	auth := fakeAgentAuthenticator{agent: storeagent.AuthenticatedAgent{AgentID: "agt_0000000000test", WorkspaceID: "wsp_test", MemberID: "00000000test", MachineID: "mach_test"}}
	store := &fakeAgentProjectStore{}
	response := agentTaskRequest(t, projectTestRouter(auth, store), http.MethodPost, "/projects", map[string]any{
		"name": "AgentTeam",
	})

	require.Equal(t, http.StatusBadRequest, response.Code)
	require.False(t, store.createRan)
}

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

	auth := fakeAgentAuthenticator{agent: storeagent.AuthenticatedAgent{AgentID: "agt_0000000000test", WorkspaceID: "wsp_test", MemberID: "00000000test", MachineID: "mach_test"}}
	store := &fakeAgentProjectStore{}
	response := agentTaskRequest(t, projectTestRouter(auth, store), http.MethodDelete, "/projects/prj_1", nil)

	require.Equal(t, http.StatusOK, response.Code)
	require.Equal(t, "00000000test", store.deleteAgent.MemberID)
	require.Equal(t, "prj_1", store.deleteID)
}
