package app

import (
	"context"
	"database/sql"
	"testing"
	"time"

	"github.com/stretchr/testify/require"

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

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

	ctx := context.Background()
	pool := startAppRollbackDB(t, ctx)
	now := time.Unix(100, 0).UTC()
	fixture := seedAppAdminFixture(t, pool, "agent_display_name_create", now)
	seedMachineForRuntimeList(t, pool, fixture.WorkspaceID, fixture.UserID, "mac_agent_display_name_create", now)

	mustExec(t, pool, `
INSERT INTO users (id, email, name, display_name, password_hash, is_admin, email_verified_at, created_at, updated_at)
VALUES ($1, $2, $3, $4, 'hash', FALSE, $5, $5, $5)
`, "usr_agent_display_name_peer", "peer@example.com", "peer", "Helper", now)
	mustExec(t, pool, `
INSERT INTO members (id, workspace_id, type, display_name, user_id, workspace_role, status, created_at, updated_at)
VALUES ($1, $2, 'human', $3, $4, 'member', 'active', $5, $5)
`, "mem_agent_display_name_peer", fixture.WorkspaceID, "Helper", "usr_agent_display_name_peer", now)

	store := New(pool, Options{})
	created, err := store.CreateAgentForUser(ctx, fixture.UserID, fixture.WorkspaceID, "mac_agent_display_name_create", agent.CreateAgentParams{
		Name:        "helper-primary",
		DisplayName: "Helper",
		Runtime:     "codex",
		Model:       "gpt-5.4",
	})
	require.NoError(t, err)
	require.Equal(t, "Helper", created.DisplayName)

	_, err = store.CreateAgentForUser(ctx, fixture.UserID, fixture.WorkspaceID, "mac_agent_display_name_create", agent.CreateAgentParams{
		Name:        "helper-primary",
		DisplayName: "Different Helper",
		Runtime:     "codex",
		Model:       "gpt-5.4",
	})
	require.ErrorIs(t, err, ErrConflict)
	require.ErrorIs(t, err, agent.ErrNameConflict)
	require.EqualError(t, err, agentWorkspaceNameConflictMessage)

	_, err = store.CreateAgentForUser(ctx, fixture.UserID, fixture.WorkspaceID, "mac_agent_display_name_create", agent.CreateAgentParams{
		Name:        "helper-secondary",
		DisplayName: "  helper  ",
		Runtime:     "codex",
		Model:       "gpt-5.4",
	})
	require.ErrorIs(t, err, ErrConflict)
	require.ErrorIs(t, err, agent.ErrNameConflict)
	require.EqualError(t, err, agentWorkspaceNameConflictMessage)

	createdDoubleSpace, err := store.CreateAgentForUser(ctx, fixture.UserID, fixture.WorkspaceID, "mac_agent_display_name_create", agent.CreateAgentParams{
		Name:        "helper-double-space",
		DisplayName: "Helper  App",
		Runtime:     "codex",
		Model:       "gpt-5.4",
	})
	require.NoError(t, err)
	require.Equal(t, "Helper  App", createdDoubleSpace.DisplayName)

	createdSingleSpace, err := store.CreateAgentForUser(ctx, fixture.UserID, fixture.WorkspaceID, "mac_agent_display_name_create", agent.CreateAgentParams{
		Name:        "helper-single-space",
		DisplayName: "Helper App",
		Runtime:     "codex",
		Model:       "gpt-5.4",
	})
	require.NoError(t, err)
	require.Equal(t, "Helper App", createdSingleSpace.DisplayName)
}

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

	ctx := context.Background()
	pool := startAppRollbackDB(t, ctx)
	now := time.Unix(100, 0).UTC()
	fixture := seedAppAdminFixture(t, pool, "agent_display_name_create_handle", now)
	seedMachineForRuntimeList(t, pool, fixture.WorkspaceID, fixture.UserID, "mac_agent_display_name_create_handle", now)

	store := New(pool, Options{})
	created, err := store.CreateAgentForUser(ctx, fixture.UserID, fixture.WorkspaceID, "mac_agent_display_name_create_handle", agent.CreateAgentParams{
		DisplayName: "New Agent",
		Runtime:     "codex",
		Model:       "gpt-5.4",
	})
	require.NoError(t, err)
	require.Equal(t, "New Agent", created.DisplayName)

	appAgent, err := store.AgentForWorkspace(ctx, fixture.UserID, fixture.WorkspaceID, created.AgentID)
	require.NoError(t, err)
	require.Equal(t, "new-agent", appAgent.Name)
	require.Equal(t, "New Agent", appAgent.DisplayName)

	createdWithSuffix, err := store.CreateAgentForUser(ctx, fixture.UserID, fixture.WorkspaceID, "mac_agent_display_name_create_handle", agent.CreateAgentParams{
		DisplayName: "New-Agent",
		Runtime:     "codex",
		Model:       "gpt-5.4",
	})
	require.NoError(t, err)
	require.Equal(t, "New-Agent", createdWithSuffix.DisplayName)

	appAgentWithSuffix, err := store.AgentForWorkspace(ctx, fixture.UserID, fixture.WorkspaceID, createdWithSuffix.AgentID)
	require.NoError(t, err)
	require.Equal(t, "new-agent-2", appAgentWithSuffix.Name)
	require.Equal(t, "New-Agent", appAgentWithSuffix.DisplayName)
}

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

	ctx := context.Background()
	pool := startAppRollbackDB(t, ctx)
	now := time.Unix(100, 0).UTC()
	fixture := seedAppAdminFixture(t, pool, "agent_display_name_update", now)
	seedMachineForRuntimeList(t, pool, fixture.WorkspaceID, fixture.UserID, "mac_agent_display_name_update", now)

	store := New(pool, Options{})
	createdPrimary, err := store.CreateAgentForUser(ctx, fixture.UserID, fixture.WorkspaceID, "mac_agent_display_name_update", agent.CreateAgentParams{
		Name:        "helper-primary",
		DisplayName: "Helper",
		Runtime:     "codex",
		Model:       "gpt-5.4",
	})
	require.NoError(t, err)

	createdSecondary, err := store.CreateAgentForUser(ctx, fixture.UserID, fixture.WorkspaceID, "mac_agent_display_name_update", agent.CreateAgentParams{
		Name:        "helper-secondary",
		DisplayName: "Helper App",
		Runtime:     "codex",
		Model:       "gpt-5.4",
	})
	require.NoError(t, err)

	doubleSpaceName := "Helper  App"
	updated, err := store.UpdateAgentForUser(ctx, fixture.UserID, fixture.WorkspaceID, createdSecondary.AgentID, agent.UpdateAgentParams{
		DisplayName: &doubleSpaceName,
	})
	require.NoError(t, err)
	require.Equal(t, "Helper  App", updated.DisplayName)

	conflictingDisplayName := "  HELPER  "
	_, err = store.UpdateAgentForUser(ctx, fixture.UserID, fixture.WorkspaceID, createdSecondary.AgentID, agent.UpdateAgentParams{
		DisplayName: &conflictingDisplayName,
	})
	require.ErrorIs(t, err, ErrConflict)
	require.ErrorIs(t, err, agent.ErrNameConflict)
	require.EqualError(t, err, agentWorkspaceNameConflictMessage)

	var storedDisplayName string
	require.NoError(t, pool.QueryRow(ctx, `
SELECT display_name
FROM members
WHERE id = $1
`, createdSecondary.MemberID).Scan(&storedDisplayName))
	require.Equal(t, "Helper  App", storedDisplayName)
	require.Equal(t, "Helper", createdPrimary.DisplayName)
}

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

	ctx := context.Background()
	pool := startAppRollbackDB(t, ctx)
	now := time.Unix(100, 0).UTC()
	fixture := seedAppAdminFixture(t, pool, "agent_rename_identity", now)
	seedMachineForRuntimeList(t, pool, fixture.WorkspaceID, fixture.UserID, "mac_agent_rename_identity", now)

	store := New(pool, Options{})
	created, err := store.CreateAgentForUser(ctx, fixture.UserID, fixture.WorkspaceID, "mac_agent_rename_identity", agent.CreateAgentParams{
		Name:        "helper-before",
		DisplayName: "Helper Before",
		Runtime:     "codex",
		Model:       "gpt-5.4",
	})
	require.NoError(t, err)

	nextDisplayName := "Helper After"
	updated, err := store.UpdateAgentForUser(ctx, fixture.UserID, fixture.WorkspaceID, created.AgentID, agent.UpdateAgentParams{
		DisplayName: &nextDisplayName,
		DeriveName:  true,
	})
	require.NoError(t, err)
	require.Equal(t, nextDisplayName, updated.DisplayName)

	appAgent, err := store.AgentForWorkspace(ctx, fixture.UserID, fixture.WorkspaceID, created.AgentID)
	require.NoError(t, err)
	require.Equal(t, "helper-after", appAgent.Name)
	require.Equal(t, nextDisplayName, appAgent.DisplayName)

	var storedName string
	var storedDisplayName string
	var storedDisplayNameKey string
	require.NoError(t, pool.QueryRow(ctx, `
SELECT agents.name, members.display_name, members.agent_display_name_key
FROM agents
JOIN members ON members.id = agents.member_id
WHERE agents.id = $1
`, created.AgentID).Scan(&storedName, &storedDisplayName, &storedDisplayNameKey))
	require.Equal(t, "helper-after", storedName)
	require.Equal(t, "Helper After", storedDisplayName)
	require.Equal(t, "helper after", storedDisplayNameKey)
}

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

	ctx := context.Background()
	pool := startAppRollbackDB(t, ctx)
	now := time.Unix(100, 0).UTC()
	fixture := seedAppAdminFixture(t, pool, "agent_rename_handle_conflict", now)
	seedMachineForRuntimeList(t, pool, fixture.WorkspaceID, fixture.UserID, "mac_agent_rename_handle_conflict", now)

	store := New(pool, Options{})
	_, err := store.CreateAgentForUser(ctx, fixture.UserID, fixture.WorkspaceID, "mac_agent_rename_handle_conflict", agent.CreateAgentParams{
		Name:        "helper-app",
		DisplayName: "Primary Helper",
		Runtime:     "codex",
		Model:       "gpt-5.4",
	})
	require.NoError(t, err)
	created, err := store.CreateAgentForUser(ctx, fixture.UserID, fixture.WorkspaceID, "mac_agent_rename_handle_conflict", agent.CreateAgentParams{
		Name:        "secondary-helper",
		DisplayName: "Secondary Helper",
		Runtime:     "codex",
		Model:       "gpt-5.4",
	})
	require.NoError(t, err)

	nextDisplayName := "Helper App"
	_, err = store.UpdateAgentForUser(ctx, fixture.UserID, fixture.WorkspaceID, created.AgentID, agent.UpdateAgentParams{
		DisplayName: &nextDisplayName,
		DeriveName:  true,
	})
	require.ErrorIs(t, err, ErrConflict)
	require.ErrorIs(t, err, agent.ErrNameConflict)
	require.EqualError(t, err, agentWorkspaceNameConflictMessage)

	appAgent, err := store.AgentForWorkspace(ctx, fixture.UserID, fixture.WorkspaceID, created.AgentID)
	require.NoError(t, err)
	require.Equal(t, "secondary-helper", appAgent.Name)
	require.Equal(t, "Secondary Helper", appAgent.DisplayName)
}

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

	ctx := context.Background()
	pool := startAppRollbackDB(t, ctx)
	now := time.Unix(100, 0).UTC()
	fixture := seedAppAdminFixture(t, pool, "agent_handle_validation", now)
	seedMachineForRuntimeList(t, pool, fixture.WorkspaceID, fixture.UserID, "mac_agent_handle_validation", now)

	store := New(pool, Options{})
	_, err := store.CreateAgentForUser(ctx, fixture.UserID, fixture.WorkspaceID, "mac_agent_handle_validation", agent.CreateAgentParams{
		Name:        "bad handle",
		DisplayName: "Bad Handle",
		Runtime:     "codex",
		Model:       "gpt-5.4",
	})
	require.ErrorIs(t, err, ErrBadRequest)
	require.EqualError(t, err, "name must start with a letter and contain only letters, numbers, underscores, or hyphens")

	created, err := store.CreateAgentForUser(ctx, fixture.UserID, fixture.WorkspaceID, "mac_agent_handle_validation", agent.CreateAgentParams{
		Name:        "valid-handle",
		DisplayName: "Valid Handle",
		Runtime:     "codex",
		Model:       "gpt-5.4",
	})
	require.NoError(t, err)

	invalidName := "1invalid"
	_, err = store.UpdateAgentForUser(ctx, fixture.UserID, fixture.WorkspaceID, created.AgentID, agent.UpdateAgentParams{
		Name: &invalidName,
	})
	require.ErrorIs(t, err, ErrBadRequest)
	require.EqualError(t, err, "name must start with a letter and contain only letters, numbers, underscores, or hyphens")
}

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

	ctx := context.Background()
	pool := startAppRollbackDB(t, ctx)
	now := time.Unix(100, 0).UTC()
	fixture := seedAppAdminFixture(t, pool, "agent_display_name_delete", now)
	seedMachineForRuntimeList(t, pool, fixture.WorkspaceID, fixture.UserID, "mac_agent_display_name_delete", now)

	store := New(pool, Options{})
	created, err := store.CreateAgentForUser(ctx, fixture.UserID, fixture.WorkspaceID, "mac_agent_display_name_delete", agent.CreateAgentParams{
		Name:        "helper-primary",
		DisplayName: "Helper",
		Runtime:     "codex",
		Model:       "gpt-5.4",
	})
	require.NoError(t, err)

	_, err = store.DeleteAgentForWorkspace(ctx, fixture.UserID, fixture.WorkspaceID, created.AgentID)
	require.NoError(t, err)

	recreated, err := store.CreateAgentForUser(ctx, fixture.UserID, fixture.WorkspaceID, "mac_agent_display_name_delete", agent.CreateAgentParams{
		Name:        "helper-secondary",
		DisplayName: "  helper  ",
		Runtime:     "codex",
		Model:       "gpt-5.4",
	})
	require.NoError(t, err)
	require.Equal(t, "helper", recreated.DisplayName)

	var releasedKey sql.NullString
	require.NoError(t, pool.QueryRow(ctx, `
SELECT agent_display_name_key
FROM members
WHERE id = $1
`, created.MemberID).Scan(&releasedKey))
	require.False(t, releasedKey.Valid)
}
