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/testkit"
	"github.com/tidbcloud/AgentTeam/internal/archive"
	"github.com/tidbcloud/AgentTeam/internal/core/secrets"
	"github.com/tidbcloud/AgentTeam/internal/service/agentapproval"
	"github.com/tidbcloud/AgentTeam/internal/store/approval"
)

func TestAgentManagementCreateRouteReplaysApprovalAndPendingCard(t *testing.T) {
	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))
	service := agent.New(logger, dataStore.AgentStorePortsDeps(), dataStore.Attachment(), taskStore, nil, nil)
	agent.RegisterRoutes(router, service)

	now := time.Date(2026, 8, 2, 3, 0, 0, 0, time.UTC)
	const (
		workspaceID   = "wsp_agent_management_replay"
		ownerUserID   = "usr_000000000agent_management_replay"
		ownerMemberID = "2mxuqz3n7swf"
		agentMemberID = "puw0nsxzqc33"
		machineID     = "mac_agent_management_replay"
		agentID       = "agt_000000000agent_management_replay"
		agentToken    = "agent-management-replay-token"
	)

	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, 'agent-management-replay@example.com', 'agent-management-replay', 'Agent Management Owner', '', FALSE, 'hash', $2, $2, $2)
`, ownerUserID, now)
	organizationID := seedAgentTestWorkspaceOrganization(t, pool, workspaceID, "agent-management-replay", ownerUserID, now)
	mustExec(t, pool, `
INSERT INTO workspaces (id, organization_id, slug, name, latest_message_seq, created_at, updated_at)
VALUES ($1, $2, 'agent-management-replay', 'agent-management-replay', 0, $3, $3)
`, workspaceID, organizationID, 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', 'Owner', $3, 'owner', 'active', $4, $4)
`, ownerMemberID, workspaceID, ownerUserID, now)
	mustExec(t, pool, `
INSERT INTO members (id, workspace_id, type, display_name, status, created_at, updated_at)
VALUES ($1, $2, 'agent', 'Applicant', 'active', $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, 'Agent management machine', 'machine-key', $4, $4)
`, machineID, workspaceID, ownerUserID, now)
	mustExec(t, pool, `
INSERT INTO agents (id, workspace_id, machine_id, member_id, name, runtime, model, description, auth_token_hash, status, activity_detail, created_at, updated_at)
VALUES ($1, $2, $3, $4, 'applicant-agent', 'codex', 'gpt-5', '', $5, 'active', '', $6, $6)
`, agentID, workspaceID, machineID, agentMemberID, secrets.HashToken(agentToken), now)

	path := "/api/v1/workspaces/" + workspaceID + "/agents/" + agentID + "/runtime/managed-agents/create"
	payload := map[string]any{
		"requestId": "req_agent_management_replay",
		"name":      "new-agent",
		"machineId": machineID,
		"runtime":   "codex",
	}
	first := doAgentRequest(t, router, agentID, agentToken, path, payload)
	require.Equal(t, http.StatusOK, first.Code, first.Body.String())
	second := doAgentRequest(t, router, agentID, agentToken, path, payload)
	require.Equal(t, http.StatusOK, second.Code, second.Body.String())

	var firstResponse, secondResponse struct {
		Status     string `json:"status"`
		ApprovalID string `json:"approvalId"`
	}
	require.NoError(t, json.Unmarshal(first.Body.Bytes(), &firstResponse))
	require.NoError(t, json.Unmarshal(second.Body.Bytes(), &secondResponse))
	require.Equal(t, "pending", firstResponse.Status)
	require.NotEmpty(t, firstResponse.ApprovalID)
	require.Equal(t, firstResponse, secondResponse)

	var approvals int
	require.NoError(t, pool.QueryRow(ctx, `
SELECT COUNT(*) FROM approval_instances
WHERE workspace_id = $1 AND subject_type = 'agent_management'
`, workspaceID).Scan(&approvals))
	require.Equal(t, 1, approvals)

	clientNotificationID := "agent-management:" + firstResponse.ApprovalID + ":review"
	var notificationID, messageID string
	require.NoError(t, pool.QueryRow(ctx, `
SELECT id, message_id FROM service_notifications
WHERE workspace_id = $1 AND client_notification_id = $2
`, workspaceID, clientNotificationID).Scan(&notificationID, &messageID))
	require.NotEmpty(t, notificationID)
	require.NotEmpty(t, messageID)

	var notifications, cards, messages int
	require.NoError(t, pool.QueryRow(ctx, `
SELECT COUNT(*) FROM service_notifications
WHERE workspace_id = $1 AND client_notification_id = $2
`, workspaceID, clientNotificationID).Scan(&notifications))
	require.NoError(t, pool.QueryRow(ctx, `
SELECT COUNT(*) FROM service_notification_cards
WHERE service_notification_id = $1
`, notificationID).Scan(&cards))
	require.NoError(t, pool.QueryRow(ctx, `
SELECT COUNT(*) FROM scope_messages
WHERE id = $1
`, messageID).Scan(&messages))
	require.Equal(t, 1, notifications)
	require.Equal(t, 1, cards)
	require.Equal(t, 1, messages)
}

func TestAgentManagementDeleteRouteReplaysAfterApprovedTargetDisappears(t *testing.T) {
	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))
	service := agent.New(logger, dataStore.AgentStorePortsDeps(), dataStore.Attachment(), taskStore, nil, nil)
	agent.RegisterRoutes(router, service)

	now := time.Date(2026, 8, 2, 3, 15, 0, 0, time.UTC)
	const (
		workspaceID     = "wsp_agent_management_terminal_replay"
		ownerUserID     = "usr_000000000agent_management_terminal_replay"
		ownerMemberID   = "edtpj7kxegfn"
		applicantMember = "p1ann4ck7vfw"
		targetMember    = "0f2ou37p8huf"
		machineID       = "mac_agent_management_terminal"
		applicantID     = "agt_000000000agent_management_terminal_app"
		targetID        = "agt_000000000agent_management_terminal_target"
		agentToken      = "agent-management-terminal-token"
	)

	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, 'agent-management-terminal@example.com', 'agent-management-terminal', 'Agent Management Owner', '', FALSE, 'hash', $2, $2, $2)
`, ownerUserID, now)
	organizationID := seedAgentTestWorkspaceOrganization(t, pool, workspaceID, "agent-management-terminal", ownerUserID, now)
	mustExec(t, pool, `
INSERT INTO workspaces (id, organization_id, slug, name, latest_message_seq, created_at, updated_at)
VALUES ($1, $2, 'agent-management-terminal', 'agent-management-terminal', 0, $3, $3)
`, workspaceID, organizationID, 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', 'Owner', $3, 'owner', 'active', $4, $4)
`, ownerMemberID, workspaceID, ownerUserID, now)
	mustExec(t, pool, `
INSERT INTO members (id, workspace_id, type, display_name, status, created_at, updated_at)
VALUES ($1, $2, 'agent', 'Applicant', 'active', $4, $4),
       ($3, $2, 'agent', 'Target', 'active', $4, $4)
`, applicantMember, workspaceID, targetMember, 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, 'Agent management machine', 'machine-key', $4, $4)
`, machineID, workspaceID, ownerUserID, now)
	mustExec(t, pool, `
INSERT INTO agents (id, workspace_id, machine_id, member_id, name, runtime, model, description, auth_token_hash, status, activity_detail, created_at, updated_at)
VALUES ($1, $3, $4, $5, 'applicant-agent', 'codex', 'gpt-5', '', $7, 'active', '', $8, $8),
       ($2, $3, $4, $6, 'target-agent', 'codex', 'gpt-5', '', NULL, 'active', '', $8, $8)
`, applicantID, targetID, workspaceID, machineID, applicantMember, targetMember, secrets.HashToken(agentToken), now)

	path := "/api/v1/workspaces/" + workspaceID + "/agents/" + applicantID + "/runtime/managed-agents/delete"
	payload := map[string]any{
		"requestId": "req_agent_management_terminal_replay",
		"agentIds":  []string{targetID},
	}
	first := doAgentRequest(t, router, applicantID, agentToken, path, payload)
	require.Equal(t, http.StatusOK, first.Code, first.Body.String())
	var firstResponse struct {
		Status     string `json:"status"`
		ApprovalID string `json:"approvalId"`
	}
	require.NoError(t, json.Unmarshal(first.Body.Bytes(), &firstResponse))
	require.Equal(t, "pending", firstResponse.Status)
	require.NotEmpty(t, firstResponse.ApprovalID)

	instance, err := dataStore.Graph.Approval.Load(ctx, workspaceID, firstResponse.ApprovalID)
	require.NoError(t, err)
	assignments, err := dataStore.Graph.Approval.ListAssignments(ctx, workspaceID, instance.ID)
	require.NoError(t, err)
	require.Len(t, assignments, 1)
	// Use the subject service so approval and operation enqueue stay in the
	// production DecideWithEffect transaction.
	subjectService := agentapproval.New(pool, dataStore.Graph.Approval, dataStore.AgentOps())
	decided, err := subjectService.Decide(ctx, approval.DecideParams{
		WorkspaceID: workspaceID, ApprovalID: instance.ID,
		AssignmentID: assignments[0].ID, ActorMemberID: ownerMemberID,
		ExpectedVersion: instance.Version, ClientDecisionID: "dec_agent_management_terminal",
		Decision: approval.DecisionApproved, Now: now,
	})
	require.NoError(t, err)
	require.NotZero(t, decided.OperationID)

	_, err = dataStore.App().DeleteAgentForWorkspace(ctx, ownerUserID, workspaceID, targetID)
	require.NoError(t, err)

	replay := doAgentRequest(t, router, applicantID, agentToken, path, payload)
	require.Equal(t, http.StatusOK, replay.Code, replay.Body.String())
	var replayResponse struct {
		Status     string `json:"status"`
		ApprovalID string `json:"approvalId"`
	}
	require.NoError(t, json.Unmarshal(replay.Body.Bytes(), &replayResponse))
	require.Equal(t, firstResponse, replayResponse)

	conflict := doAgentRequest(t, router, applicantID, agentToken, path, map[string]any{
		"requestId": "req_agent_management_terminal_replay",
		"agentIds":  []string{"agt_00000different_payload"},
	})
	require.Equal(t, http.StatusConflict, conflict.Code, conflict.Body.String())

	var approvals, operations int
	require.NoError(t, pool.QueryRow(ctx, `
SELECT COUNT(*) FROM approval_instances
WHERE workspace_id = $1 AND subject_type = 'agent_management'
`, workspaceID).Scan(&approvals))
	require.NoError(t, pool.QueryRow(ctx, `
SELECT COUNT(*) FROM agent_operations WHERE approval_id = $1
`, firstResponse.ApprovalID).Scan(&operations))
	require.Equal(t, 1, approvals)
	require.Equal(t, 1, operations)

	var operationStatus string
	require.NoError(t, pool.QueryRow(ctx, `
SELECT status FROM agent_operations WHERE approval_id = $1
`, firstResponse.ApprovalID).Scan(&operationStatus))
	require.Equal(t, "queued", operationStatus)
}
