package agent_test

import (
	"context"
	"encoding/json"
	"io"
	"log/slog"
	"net/http"
	"testing"
	"time"

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

	"github.com/tidbcloud/AgentTeam/internal/api/agent"
	"github.com/tidbcloud/AgentTeam/internal/api/daemon"
	"github.com/tidbcloud/AgentTeam/internal/api/testkit"
	"github.com/tidbcloud/AgentTeam/internal/archive"
	"github.com/tidbcloud/AgentTeam/internal/core/domain"
	"github.com/tidbcloud/AgentTeam/internal/core/secrets"
	"github.com/tidbcloud/AgentTeam/internal/coredb"
	"github.com/tidbcloud/AgentTeam/internal/store/testfixture"
)

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

	ctx := context.Background()
	_, pool := startAgentAPITestPool(t, ctx)

	archiveStorage, err := archive.NewFilesystem(t.TempDir())
	require.NoError(t, err)
	dataStore := testkit.New(pool, archiveStorage, 30)
	taskStore := mustTaskClient(t, pool)

	gin.SetMode(gin.TestMode)
	router := gin.New()
	logger := slog.New(slog.NewTextHandler(io.Discard, nil))
	deps := dataStore.AgentStorePortsDeps()
	service := agent.New(logger, deps, dataStore.Attachment(), taskStore, daemon.New(logger, daemon.NewStore(newAgentRuntime(pool, dataStore).Agents(), newAgentRuntime(pool, dataStore).Recovery(), newAgentRuntime(pool, dataStore).PrepareAgentStart), "http://example.com", nil), nil)
	agent.RegisterRoutes(router, service)

	now := time.Date(2026, 5, 11, 15, 30, 0, 0, time.UTC)
	const (
		workspaceID   = "wsp_private_target_send"
		userID        = "usr_0000000private_target_send"
		otherUserID   = "usr_0000000private_target_other"
		humanMemberID = "nh5g4fqphd66"
		otherMemberID = "vgiaj9r50fns"
		agentMemberID = "p4wp9a2kcx7l"
		channelID     = "chn_private_target_all"
		threadID      = "thr_private_target_all"
		machineID     = "mac_private_target_send"
		agentID       = "agt_0000000private_target_send"
		agentToken    = "agent-token-private-target-send"
	)

	seedPrivateAgentTargetSendFixture(t, pool, now, workspaceID, userID, otherUserID, humanMemberID, otherMemberID, agentMemberID, channelID, threadID, machineID, agentID, agentToken)

	sendChannel := doAgentRequest(t, router, agentID, agentToken, "/api/v1/workspaces/"+workspaceID+"/agents/"+agentID+"/runtime/send", map[string]any{
		"target":  "#all",
		"content": "private agent channel target writes parent",
	})
	require.Equal(t, http.StatusOK, sendChannel.Code, sendChannel.Body.String())
	var channelPayload struct {
		MessageID string `json:"messageId"`
	}
	require.NoError(t, json.Unmarshal(sendChannel.Body.Bytes(), &channelPayload))
	require.NotEmpty(t, channelPayload.MessageID)

	assertPrivateTargetParentMessage(t, pool, channelPayload.MessageID, channelID, agentMemberID, "private agent channel target writes parent")
	assertPrivateAgentHasNoPublicParentMembership(t, pool, channelID, agentMemberID)

	parentMessage, _, err := dataStore.SendMessageForUser(ctx, domain.User{ID: userID}, workspaceID, channelID, "parent for private target thread", nil)
	require.NoError(t, err)
	require.NotEmpty(t, parentMessage.ID)
	threadSummary, err := dataStore.OpenReplyThreadForUser(ctx, userID, workspaceID, channelID, parentMessage.ID)
	require.NoError(t, err)
	require.NotEmpty(t, threadSummary.ThreadChannelID)

	sendThread := doAgentRequest(t, router, agentID, agentToken, "/api/v1/workspaces/"+workspaceID+"/agents/"+agentID+"/runtime/send", map[string]any{
		"target":  "#all:" + parentMessage.ID[:8],
		"content": "private agent thread target writes parent",
	})
	require.Equal(t, http.StatusOK, sendThread.Code, sendThread.Body.String())
	var threadPayload struct {
		MessageID string `json:"messageId"`
	}
	require.NoError(t, json.Unmarshal(sendThread.Body.Bytes(), &threadPayload))
	require.NotEmpty(t, threadPayload.MessageID)

	assertPrivateTargetParentMessage(t, pool, threadPayload.MessageID, threadSummary.ThreadChannelID, agentMemberID, "private agent thread target writes parent")
	assertPrivateAgentHasNoPublicParentMembership(t, pool, channelID, agentMemberID)

	hiddenParentMessage, _, err := dataStore.SendMessageForUser(ctx, domain.User{ID: otherUserID}, workspaceID, channelID, "non-owner parent for hidden thread", nil)
	require.NoError(t, err)
	require.NotEmpty(t, hiddenParentMessage.ID)
	sendHiddenThread := doAgentRequest(t, router, agentID, agentToken, "/api/v1/workspaces/"+workspaceID+"/agents/"+agentID+"/runtime/send", map[string]any{
		"target":  "#all:" + hiddenParentMessage.ID[:8],
		"content": "private agent can reply by parent roster",
	})
	require.Equal(t, http.StatusOK, sendHiddenThread.Code, sendHiddenThread.Body.String())
	var hiddenThreadPayload struct {
		MessageID string `json:"messageId"`
	}
	require.NoError(t, json.Unmarshal(sendHiddenThread.Body.Bytes(), &hiddenThreadPayload))
	require.NotEmpty(t, hiddenThreadPayload.MessageID)
	assertReplyThreadForParentMessage(t, pool, workspaceID, hiddenParentMessage.ID)
}

func seedPrivateAgentTargetSendFixture(t *testing.T, pool coredb.QueryExecer, now time.Time, workspaceID, userID, otherUserID, humanMemberID, otherMemberID, agentMemberID, channelID, threadID, machineID, agentID, agentToken string) {
	t.Helper()

	mustExec(t, pool, `
INSERT INTO users (id, email, name, display_name, avatar_url, is_admin, password_hash, email_verified_at, created_at, updated_at)
VALUES ($1, 'private-target-send@example.com', 'private-target-send', 'Private Target Send', '', TRUE, 'hash', $2, $2, $2)
`, userID, now)
	mustExec(t, pool, `
INSERT INTO users (id, email, name, display_name, avatar_url, is_admin, password_hash, email_verified_at, created_at, updated_at)
VALUES ($1, 'private-target-other@example.com', 'private-target-other', 'Private Target Other', '', TRUE, 'hash', $2, $2, $2)
`, otherUserID, now)
	organizationID := testfixture.SeedWorkspaceOrganization(t, context.Background(), pool, workspaceID, now)
	mustExec(t, pool, `
INSERT INTO workspaces (id, organization_id, slug, name, latest_message_seq, created_at, updated_at)
VALUES ($1, $2, 'private-target-send', 'private-target-send', 0, $3, $3)
`, workspaceID, organizationID, now)
	mustExec(t, pool, `
INSERT INTO members (id, workspace_id, type, display_name, user_id, workspace_role, created_at, updated_at)
VALUES ($1, $2, 'human', 'Private Target Owner', $3, 'owner', $4, $4)
`, humanMemberID, workspaceID, userID, now)
	mustExec(t, pool, `
INSERT INTO members (id, workspace_id, type, display_name, user_id, workspace_role, created_at, updated_at)
VALUES ($1, $2, 'human', 'Private Target Other', $3, 'member', $4, $4)
`, otherMemberID, workspaceID, otherUserID, now)
	mustExec(t, pool, `
INSERT INTO members (id, workspace_id, type, display_name, created_at, updated_at)
VALUES ($1, $2, 'agent', 'Private Target Agent', $3, $3)
`, agentMemberID, workspaceID, now)
	mustExec(t, pool, `
INSERT INTO machines (id, workspace_id, created_by_user_id, name, api_key, created_at, updated_at)
VALUES ($1, $2, $3, 'private-target-machine', 'private-target-machine-key', $4, $4)
`, machineID, workspaceID, userID, now)
	mustExec(t, pool, `
INSERT INTO agents (id, workspace_id, machine_id, member_id, name, runtime, model, description, auth_token_hash, activity_detail, created_at, updated_at)
VALUES ($1, $2, $3, $4, 'private-target-agent', 'codex', 'gpt-5', '', $5, '', $6, $6)
`, agentID, workspaceID, machineID, agentMemberID, secrets.HashToken(agentToken), now)
	seedAgentTestChannelScope(t, pool, workspaceID, channelID, "all", humanMemberID, now)
	seedAgentTestPublicScopeMemberships(t, pool, channelID, now, humanMemberID, otherMemberID)
	seedAgentTestPrivateScopeMembership(t, pool, channelID, agentMemberID, humanMemberID, now)
}

func assertPrivateTargetParentMessage(t *testing.T, pool coredb.QueryRower, messageID string, wantScopeID string, wantAuthorMemberID string, wantContent string) {
	t.Helper()

	var content string
	var scopeID string
	var authorMemberID string
	require.NoError(t, pool.QueryRow(context.Background(), `
SELECT content, scope_id, author_member_id
FROM scope_messages
WHERE id = $1
`, messageID).Scan(&content, &scopeID, &authorMemberID))
	require.Equal(t, wantContent, content)
	require.Equal(t, wantScopeID, scopeID)
	require.Equal(t, wantAuthorMemberID, authorMemberID)

	var duplicateCount int
	require.NoError(t, pool.QueryRow(context.Background(), `
SELECT COUNT(*)
FROM scope_messages
WHERE id <> $1
  AND scope_id <> $2
  AND author_member_id = $3
  AND content = $4
`, messageID, wantScopeID, wantAuthorMemberID, wantContent).Scan(&duplicateCount))
	require.Equal(t, 0, duplicateCount)
}

func assertPrivateAgentHasNoPublicParentMembership(t *testing.T, pool coredb.QueryRower, parentScopeID string, agentMemberID string) {
	t.Helper()

	var participantCount int
	require.NoError(t, pool.QueryRow(context.Background(), `
SELECT COUNT(*)
FROM scope_memberships
WHERE scope_id = $1
  AND member_id = $2
  AND visibility = 'public'
  AND removed_at IS NULL
  AND effective_to_seq IS NULL
`, parentScopeID, agentMemberID).Scan(&participantCount))
	require.Equal(t, 0, participantCount)
}

func assertReplyThreadForParentMessage(t *testing.T, pool coredb.QueryRower, workspaceID string, parentMessageID string) {
	t.Helper()

	var replyThreadCount int
	require.NoError(t, pool.QueryRow(context.Background(), `
SELECT COUNT(*)
FROM scopes
WHERE workspace_id = $1
  AND type = 'thread'
  AND root_message_id = $2
  AND status = 'active'
`, workspaceID, parentMessageID).Scan(&replyThreadCount))
	require.Equal(t, 1, replyThreadCount)
}
