package daemon

import (
	"context"
	"encoding/json"
	"fmt"
	"strings"
	"testing"
	"time"

	"github.com/stretchr/testify/require"

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

func TestReviewSummaryRemoteRequestUsesElasticUnderPolicy(t *testing.T) {
	now := time.Date(2026, 6, 30, 10, 0, 0, 0, time.UTC)
	manager := newTestElasticRequestLinkManager(t, &now, ElasticRequestLinkManagerConfig{})
	hub := &Hub{remoteEndpoints: defaultRemoteEndpointRegistry(), elasticRequestLinks: manager}
	client := newElasticReadyClient()
	descriptor, ok := defaultRemoteEndpointRegistry().Lookup(remoteEndpointReviewSummary, 1)
	require.True(t, ok)
	open, _, err := manager.CreateLinkGrant(client, descriptor)
	require.NoError(t, err)
	sender := newCaptureElasticSender()
	_, err = manager.AcceptLink(client.workspaceID, open.LinkID, open.Token, sender)
	require.NoError(t, err)

	go func() {
		message := <-sender.messages
		assignment, ok := message.(RemoteRequestAssignment[json.RawMessage])
		require.True(t, ok)
		require.Equal(t, remoteEndpointReviewSummary, assignment.EndpointID)
		var payload reviewSummaryRemoteRequestPayload
		require.NoError(t, json.Unmarshal(assignment.Payload, &payload))
		require.Equal(t, "agt_1", payload.AgentID)
		require.Equal(t, "/tmp/review", payload.TargetPath)
		require.Equal(t, "main", payload.BaseRef)
		manager.CompleteRemoteRequest(RemoteRequestResult[json.RawMessage]{
			Type:      sessionLinkRemoteRequestResultType,
			RequestID: assignment.RequestID,
			Success:   true,
			Outcome:   "ok",
			Payload: mustMarshalRemoteWorkspaceRawTest(t, reviewSummaryRemoteRequestResponse{
				AgentID: payload.AgentID,
				Summary: &agent.AgentReviewSummary{Status: "ready", Branch: "feature"},
			}),
		})
	}()

	ctx, cancel := context.WithTimeout(context.Background(), time.Second)
	defer cancel()
	summary, err := hub.reviewSummary(ctx, client, "agt_1", "/tmp/review", "main", "feature", "direct")
	require.NoError(t, err)
	require.Equal(t, "ready", summary.Status)
	require.Equal(t, "feature", summary.Branch)
}

func TestReviewSnapshotRemoteRequestUsesDedicatedEightMiBEndpoint(t *testing.T) {
	descriptor, ok := defaultRemoteEndpointRegistry().Lookup(remoteEndpointReviewSnapshot, 1)
	require.True(t, ok)
	require.Equal(t, int64(8*1024*1024), descriptor.Limits.ResponseBytes)

	payload, err := json.Marshal(reviewSnapshotRemoteRequestResponse{
		AgentID: "agt_1",
		Snapshot: &agent.AgentReviewSnapshot{
			Summary: agent.AgentReviewSummary{Status: "ready"},
			Files:   makeLongReviewFiles(7000),
		},
	})
	require.NoError(t, err)
	require.Greater(t, len(payload), 256*1024)
	require.Less(t, int64(len(payload)), descriptor.Limits.ResponseBytes)
}

func makeLongReviewFiles(count int) []agent.AgentReviewFile {
	files := make([]agent.AgentReviewFile, count)
	for i := range files {
		files[i] = agent.AgentReviewFile{Path: strings.Repeat("segment/", 8) + fmt.Sprintf("file-%05d.go", i), ChangeType: "M"}
	}
	return files
}

func TestReviewFilesRemoteRequestFallsBackForOldDesktop(t *testing.T) {
	serverConn, desktopConn := newDaemonProtocolTestPair(t)
	client := &clientState{
		machineID:                 "mac_1",
		workspaceID:               "wsp_1",
		conn:                      serverConn,
		capabilities:              map[string]struct{}{},
		supportedEndpointVersions: make(map[string]map[int]struct{}),
		reviewFilesRequests:       make(map[string]chan reviewFilesResult),
		remoteRequests:            make(map[string]chan RemoteRequestResult[json.RawMessage]),
	}
	now := time.Date(2026, 6, 30, 10, 10, 0, 0, time.UTC)
	hub := &Hub{remoteEndpoints: defaultRemoteEndpointRegistry(), elasticRequestLinks: newTestElasticRequestLinkManager(t, &now, ElasticRequestLinkManagerConfig{})}

	go func() {
		var command agentReviewFilesCommand
		require.NoError(t, desktopConn.ReadJSON(&command))
		require.Equal(t, sessionLinkCommandAgentReviewFiles, command.Type)
		require.Equal(t, "agt_1", command.AgentID)
		require.Equal(t, "working", command.Scope)
		require.NoError(t, handleSessionLinkAgentReviewFilesResultCommand(nil, context.Background(), client, mustMarshalRemoteWorkspaceTest(t, reviewFilesResultMessage{
			Type:      sessionLinkCommandAgentReviewFilesResult,
			AgentID:   command.AgentID,
			RequestID: command.RequestID,
			Files:     []agent.AgentReviewFile{{Path: "README.md", ChangeType: "M"}},
		})))
	}()

	ctx, cancel := context.WithTimeout(context.Background(), time.Second)
	defer cancel()
	files, err := hub.reviewFiles(ctx, client, "agt_1", "/tmp/review", "working", "main", "feature", "direct")
	require.NoError(t, err)
	require.Equal(t, []agent.AgentReviewFile{{Path: "README.md", ChangeType: "M"}}, files)
}

func TestReviewDiffRemoteRequestTimeoutFallsBackToLegacy(t *testing.T) {
	serverConn, desktopConn := newDaemonProtocolTestPair(t)
	client := newWorkspaceListRemoteReadyClient(serverConn)
	now := time.Date(2026, 6, 30, 10, 20, 0, 0, time.UTC)
	descriptor, ok := defaultRemoteEndpointRegistry().Lookup(remoteEndpointReviewDiff, 1)
	require.True(t, ok)
	descriptor.DialTimeout = time.Millisecond
	registry := mustRemoteEndpointRegistry([]RemoteEndpointDescriptor{descriptor})
	hub := &Hub{remoteEndpoints: registry, elasticRequestLinks: newTestElasticRequestLinkManager(t, &now, ElasticRequestLinkManagerConfig{})}

	go func() {
		var open ElasticRequestLinkOpenCommand
		require.NoError(t, desktopConn.ReadJSON(&open))
		require.Equal(t, elasticRequestLinkOpenType, open.Type)
		var command agentReviewDiffCommand
		require.NoError(t, desktopConn.ReadJSON(&command))
		require.Equal(t, sessionLinkCommandAgentReviewDiff, command.Type)
		require.Equal(t, "README.md", command.Path)
		require.NoError(t, handleSessionLinkAgentReviewDiffResultCommand(nil, context.Background(), client, mustMarshalRemoteWorkspaceTest(t, reviewDiffResultMessage{
			Type:      sessionLinkCommandAgentReviewDiffResult,
			AgentID:   command.AgentID,
			RequestID: command.RequestID,
			Diff:      &agent.AgentReviewDiff{Status: "ready", Path: command.Path},
		})))
	}()

	ctx, cancel := context.WithTimeout(context.Background(), time.Second)
	defer cancel()
	diff, err := hub.reviewDiff(ctx, client, "agt_1", "/tmp/review", "working", "README.md", "main", "feature", "direct")
	require.NoError(t, err)
	require.Equal(t, "ready", diff.Status)
	require.Equal(t, "README.md", diff.Path)
}

func TestOutputTargetValidateRemoteRequestUsesSessionLink(t *testing.T) {
	serverConn, desktopConn := newDaemonProtocolTestPair(t)
	client := newWorkspaceListRemoteReadyClient(serverConn)
	now := time.Date(2026, 6, 30, 10, 30, 0, 0, time.UTC)
	hub := &Hub{remoteEndpoints: defaultRemoteEndpointRegistry(), elasticRequestLinks: newTestElasticRequestLinkManager(t, &now, ElasticRequestLinkManagerConfig{})}

	go func() {
		var assignment RemoteRequestAssignment[json.RawMessage]
		require.NoError(t, desktopConn.ReadJSON(&assignment))
		require.Equal(t, sessionLinkRemoteRequestAssignType, assignment.Type)
		require.Equal(t, remoteEndpointOutputTargetValidate, assignment.EndpointID)
		var payload outputTargetValidateRemoteRequestPayload
		require.NoError(t, json.Unmarshal(assignment.Payload, &payload))
		require.Equal(t, "agt_1", payload.AgentID)
		require.Equal(t, "git_repo", payload.Kind)
		require.NoError(t, handleSessionLinkRemoteRequestResultCommand(hub, context.Background(), client, mustMarshalRemoteWorkspaceTest(t, RemoteRequestResult[json.RawMessage]{
			Type:      sessionLinkRemoteRequestResultType,
			RequestID: assignment.RequestID,
			Success:   true,
			Outcome:   "ok",
			Payload: mustMarshalRemoteWorkspaceRawTest(t, outputTargetValidateRemoteRequestResponse{
				AgentID: payload.AgentID,
				Valid:   true,
			}),
		})))
	}()

	ctx, cancel := context.WithTimeout(context.Background(), time.Second)
	defer cancel()
	require.NoError(t, hub.validateOutputTarget(ctx, client, "agt_1", "git_repo", "/tmp/review"))
}

func TestReviewSummaryRemoteRequestCapabilityMismatchFallsBack(t *testing.T) {
	serverConn, desktopConn := newDaemonProtocolTestPair(t)
	client := newWorkspaceListRemoteReadyClient(serverConn)
	delete(client.supportedEndpointVersions, remoteEndpointReviewSummary)
	now := time.Date(2026, 6, 30, 10, 40, 0, 0, time.UTC)
	hub := &Hub{remoteEndpoints: defaultRemoteEndpointRegistry(), elasticRequestLinks: newTestElasticRequestLinkManager(t, &now, ElasticRequestLinkManagerConfig{})}

	go func() {
		var command agentReviewSummaryCommand
		require.NoError(t, desktopConn.ReadJSON(&command))
		require.Equal(t, sessionLinkCommandAgentReviewSummary, command.Type)
		require.NoError(t, handleSessionLinkAgentReviewSummaryResultCommand(nil, context.Background(), client, mustMarshalRemoteWorkspaceTest(t, reviewSummaryResultMessage{
			Type:      sessionLinkCommandAgentReviewSummaryResult,
			AgentID:   command.AgentID,
			RequestID: command.RequestID,
			Summary:   &agent.AgentReviewSummary{Status: "ready"},
		})))
	}()

	ctx, cancel := context.WithTimeout(context.Background(), time.Second)
	defer cancel()
	summary, err := hub.reviewSummary(ctx, client, "agt_1", "/tmp/review", "", "", "")
	require.NoError(t, err)
	require.Equal(t, "ready", summary.Status)
}

func TestReviewRemoteRequestErrorsAreRedacted(t *testing.T) {
	var _, err = reviewSummaryFromRemoteResult(RemoteRequestResult[json.RawMessage]{
		Type:    sessionLinkRemoteRequestResultType,
		Success: false,
		Outcome: "endpoint_error",
		Error:   "/tmp/secret/path failed",
	})
	require.Error(t, err)
	require.Equal(t, "endpoint_error", err.Error())
}
