package daemon

import (
	"context"
	"io"
	"log/slog"
	"testing"
	"time"

	"github.com/stretchr/testify/require"

	"github.com/tidbcloud/AgentTeam/internal/archive"
	"github.com/tidbcloud/AgentTeam/internal/coredb"
	"github.com/tidbcloud/AgentTeam/internal/store"
)

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

	ctx := context.Background()
	pool := openDaemonTestTiDB(t)

	archiveStorage, err := archive.NewFilesystem(t.TempDir())
	require.NoError(t, err)

	graph := store.New(pool, archiveStorage, nil, 30)
	hub := &Hub{
		logger: slog.New(slog.NewTextHandler(io.Discard, nil)),
		store:  NewStore(newTestAgentRuntime(pool, graph).Agents(), newTestAgentRuntime(pool, graph).Recovery(), newTestAgentRuntime(pool, graph).PrepareAgentStart),
	}

	now := time.Now().UTC()
	workspaceID := "wsp_test"
	userID := "usr_test"
	humanMemberID := "mem_human"
	agentMemberID := "mem_agent"
	machineID := "mac_expected"
	agentID := "agt_expected"
	channelID := "chn_all"
	messageID := "msg-1"

	mustExecDaemonTest(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, 'test@example.com', 'test', 'Test', '', TRUE, 'hash', $2, $2, $2)
`, userID, now)
	organizationID := seedDaemonTestWorkspaceOrganization(t, pool, workspaceID, "test", userID, now)
	mustExecDaemonTest(t, pool, `
INSERT INTO workspaces (id, organization_id, slug, name, latest_message_seq, created_at, updated_at)
VALUES ($1, $2, 'test', 'Test', 1, $3, $3)
`, workspaceID, organizationID, now)
	mustExecDaemonTest(t, pool, `
INSERT INTO members (id, workspace_id, type, display_name, user_id, workspace_role, created_at, updated_at)
VALUES ($1, $2, 'human', 'Human', $3, 'owner', $4, $4)
`, humanMemberID, workspaceID, userID, now)
	mustExecDaemonTest(t, pool, `
INSERT INTO members (id, workspace_id, type, display_name, created_at, updated_at)
VALUES ($1, $2, 'agent', 'Agent', $3, $3)
`, agentMemberID, workspaceID, now)
	seedDaemonTestChannelScope(t, pool, workspaceID, channelID, "all", humanMemberID, now)
	mustExecDaemonTest(t, pool, `
INSERT INTO machines (id, workspace_id, created_by_user_id, name, api_key, created_at, updated_at)
VALUES ($1, $2, $3, 'Expected machine', 'key-1', $4, $4)
`, machineID, workspaceID, userID, now)
	mustExecDaemonTest(t, pool, `
INSERT INTO agents (id, workspace_id, machine_id, member_id, name, runtime, model, description, auth_token_hash, auto_wake_enabled, status, activity, activity_detail, created_at, updated_at)
VALUES ($1, $2, $3, $4, 'agent-1', 'codex', 'gpt-5-codex', '', 'hash', TRUE, 'active', 'online', '', $5, $5)
`, agentID, workspaceID, machineID, agentMemberID, now)
	mustExecDaemonTest(t, pool, `
INSERT INTO scope_memberships (
    scope_id, member_id, visibility, owner_member_id, last_read_seq,
    last_read_global_seq, last_activity_global_seq,
    owner_context_dismissed_through_global_seq, effective_from_seq,
    effective_to_seq, created_at, updated_at, removed_at, archived_at
)
VALUES ($1, $2, 'public', NULL, 0, 0, 0, 0, 1, NULL, $3, $3, NULL, NULL)
`, channelID, agentMemberID, now)
	mustExecDaemonTest(t, pool, `
INSERT IGNORE INTO scope_messages (
    id, workspace_id, scope_id, seq, workspace_global_seq,
    author_member_id, author_type, message_type, content, metadata, created_at, updated_at
) VALUES (
    $1, $2, $3, 1, 1,
    $4, 'human', '', 'hello', NULL, $5, $5
)
`, messageID, workspaceID, channelID, humanMemberID, now)
	payload := []byte(`{"type":"agent:recovery_point_prepared","agentId":"agt_expected","messageId":"msg-1","threadId":"codex-thread-1","strategy":"codex_app_server_turns","recoveryRef":"{\"threadId\":\"codex-thread-1\",\"turnCount\":0}"}`)
	client := &clientState{
		workspaceID: workspaceID,
		machineID:   machineID,
		logger:      slog.New(slog.NewTextHandler(io.Discard, nil)),
	}

	require.NoError(t, hub.handleMessage(ctx, client, payload))

	var recoveryRef string
	err = pool.QueryRow(ctx, `
SELECT recovery_ref
FROM message_recovery_points
WHERE workspace_id = $1 AND message_id = $2 AND agent_id = $3
`, workspaceID, messageID, agentID).Scan(&recoveryRef)
	require.NoError(t, err)
	require.Equal(t, `{"threadId":"codex-thread-1","turnCount":0}`, recoveryRef)
}

func seedDaemonTestChannelScope(t *testing.T, pool coredb.QueryExecer, workspaceID string, channelID string, slug string, createdByMemberID string, now time.Time) {
	t.Helper()

	mustExecDaemonTest(t, pool, `
INSERT IGNORE INTO scopes (
    id, workspace_id, type, identity_key, latest_seq, latest_global_seq,
    latest_message_at, archived_until_seq, status, created_by_member_id,
    created_at, updated_at
) VALUES ($1, $2, 'channel', $3, 0, 0, NULL, 0, 'active', NULLIF($4, ''), $5, $5)
`, channelID, workspaceID, "channel:"+channelID, createdByMemberID, now)
	mustExecDaemonTest(t, pool, `
INSERT IGNORE INTO scope_channel_profiles (
    scope_id, workspace_id, slug, display_name, description, visibility,
    owner_member_id, is_all_channel, created_at, updated_at
) VALUES ($1, $2, $3, $3, '', 'public', NULL, CASE WHEN $3 = 'all' THEN 1 ELSE 0 END, $4, $4)
`, channelID, workspaceID, slug, now)
}
