package review

import (
	"context"
	"errors"
	"testing"

	"github.com/stretchr/testify/require"

	"github.com/tidbcloud/AgentTeam/internal/coredb"
	"github.com/tidbcloud/AgentTeam/internal/store/agent"
	"github.com/tidbcloud/AgentTeam/internal/store/storeerr"
)

func seedProjectAgentMachine(t *testing.T, ctx context.Context, db coredb.QueryExecer, seed projectTestSeed, machineID, userID string) {
	t.Helper()
	mustExecProj(t, ctx, db, `
INSERT INTO machines (id, workspace_id, created_by_user_id, name, api_key, created_at, updated_at)
VALUES ($1, $2, $3, $1, $1, $4, $4)
`, machineID, seed.WorkspaceID, userID, seed.Now)
}

func projectAgentPrincipal(workspaceID, agentID, memberID, machineID string) agent.AuthenticatedAgent {
	return agent.AuthenticatedAgent{
		AgentID:     agentID,
		WorkspaceID: workspaceID,
		MemberID:    memberID,
		MachineID:   machineID,
	}
}

func TestProjectAgentCreateUsesMachineOwnerUserAndIgnoresForgedParams(t *testing.T) {
	ctx := context.Background()
	db := startReviewRollbackDB(t, ctx)
	seed := seedProjectTestWorkspace(t, ctx, db, "agent_user_owner")
	seedProjectAgentMachine(t, ctx, db, seed, "mach_user_owner", seed.OwnerUserID)
	store := newProjectTestStore(db)
	principal := projectAgentPrincipal(seed.WorkspaceID, "agt_runtime", "mem_agent_runtime", "mach_user_owner")

	project, err := store.CreateProjectForAgent(ctx, principal, agent.CreateProjectParams{
		WorkspaceID: "wsp_forged",
		MachineID:   "mach_forged",
		RootPath:    "/repo/agent-user-owner",
		Name:        "Agent project",
		Visibility:  agent.ProjectVisibilityPrivate,
	})
	require.NoError(t, err)
	require.Equal(t, seed.WorkspaceID, project.WorkspaceID)
	require.Equal(t, seed.OwnerUserID, project.OwnerUserID, "Project owner is the machine owner's human member, not the Agent member")
	require.Equal(t, "mach_user_owner", project.MachineID)
}

func TestProjectAgentSameUserDifferentAgentsReuseTarget(t *testing.T) {
	ctx := context.Background()
	db := startReviewRollbackDB(t, ctx)
	seed := seedProjectTestWorkspace(t, ctx, db, "agent_same_user")
	seedProjectAgentMachine(t, ctx, db, seed, "mach_same_user", seed.OwnerUserID)
	store := newProjectTestStore(db)
	firstAgent := projectAgentPrincipal(seed.WorkspaceID, "agt_first", "mem_agent_first", "mach_same_user")
	secondAgent := projectAgentPrincipal(seed.WorkspaceID, "agt_second", "mem_agent_second", "mach_same_user")
	params := agent.CreateProjectParams{RootPath: "/repo/shared-agent-target", Name: "Shared", Visibility: agent.ProjectVisibilityPrivate}

	first, err := store.CreateProjectForAgent(ctx, firstAgent, params)
	require.NoError(t, err)
	second, err := store.CreateProjectForAgent(ctx, secondAgent, params)
	require.NoError(t, err)
	require.Equal(t, first.ID, second.ID)
	require.Equal(t, seed.OwnerUserID, second.OwnerUserID)

	projects, err := store.ListProjectsForAgent(ctx, secondAgent)
	require.NoError(t, err)
	require.Contains(t, projectIDs(projects), first.ID)
}

func TestProjectAgentDifferentUsersRespectVisibilityAndOwnerDelete(t *testing.T) {
	ctx := context.Background()
	db := startReviewRollbackDB(t, ctx)
	seed := seedProjectTestWorkspace(t, ctx, db, "agent_user_acl")
	seedProjectAgentMachine(t, ctx, db, seed, "mach_owner_user", seed.OwnerUserID)
	seedProjectAgentMachine(t, ctx, db, seed, "mach_other_user", seed.ViewUserID)
	store := newProjectTestStore(db)
	ownerAgent := projectAgentPrincipal(seed.WorkspaceID, "agt_owner", "mem_agent_owner", "mach_owner_user")
	otherUserAgent := projectAgentPrincipal(seed.WorkspaceID, "agt_other", "mem_agent_other", "mach_other_user")

	privateProject, err := store.CreateProjectForAgent(ctx, ownerAgent, agent.CreateProjectParams{
		RootPath: "/repo/user-private", Name: "Private", Visibility: agent.ProjectVisibilityPrivate,
	})
	require.NoError(t, err)
	otherProjects, err := store.ListProjectsForAgent(ctx, otherUserAgent)
	require.NoError(t, err)
	require.NotContains(t, projectIDs(otherProjects), privateProject.ID)
	require.ErrorIs(t, store.DeleteProjectForAgent(ctx, otherUserAgent, privateProject.ID), storeerr.ErrForbidden)

	workspaceProject, err := store.CreateProjectForAgent(ctx, ownerAgent, agent.CreateProjectParams{
		RootPath: "/repo/user-workspace", Name: "Workspace", Visibility: agent.ProjectVisibilityWorkspace,
	})
	require.NoError(t, err)
	otherProjects, err = store.ListProjectsForAgent(ctx, otherUserAgent)
	require.NoError(t, err)
	require.Contains(t, projectIDs(otherProjects), workspaceProject.ID)
	require.ErrorIs(t, store.DeleteProjectForAgent(ctx, otherUserAgent, workspaceProject.ID), storeerr.ErrForbidden)
	require.NoError(t, store.DeleteProjectForAgent(ctx, ownerAgent, privateProject.ID))
}

func TestProjectAgentCrossWorkspaceMachineRejected(t *testing.T) {
	ctx := context.Background()
	db := startReviewRollbackDB(t, ctx)
	seed := seedProjectTestWorkspace(t, ctx, db, "agent_machine_workspace")
	otherSeed := seedProjectTestWorkspace(t, ctx, db, "agent_machine_other")
	seedProjectAgentMachine(t, ctx, db, seed, "mach_workspace_bound", seed.OwnerUserID)
	store := newProjectTestStore(db)
	forgedWorkspace := projectAgentPrincipal(otherSeed.WorkspaceID, "agt_cross", "mem_agent_cross", "mach_workspace_bound")

	_, err := store.ListProjectsForAgent(ctx, forgedWorkspace)
	if !errors.Is(err, storeerr.ErrNotFound) && !errors.Is(err, storeerr.ErrForbidden) {
		t.Fatalf("machine used with another workspace must be rejected, got %v", err)
	}
}

func projectIDs(projects []agent.Project) []string {
	ids := make([]string, 0, len(projects))
	for _, project := range projects {
		ids = append(ids, project.ID)
	}
	return ids
}
