//go:build e2e

package e2e

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

	"github.com/tidbcloud/AgentTeam/desktop/internal/profile"
	"github.com/tidbcloud/AgentTeam/desktop/internal/types"
	"github.com/tidbcloud/AgentTeam/desktop/internal/wire"
)

// ---------------------------------------------------------------------------
// Stub dispatcher — captures calls for assertion, provides minimal responses
// ---------------------------------------------------------------------------

// stubDispatcher provides a fully-featured test double for e2e tests.
// It records all Dispatch calls and provides no-op implementations
// for methods that the agentteam handlers invoke.
type stubDispatcher struct {
	mu             sync.Mutex
	dispatchedMsgs []*types.Message
	startedAgents  []string
	stoppedAgents  []string
	sleepedAgents  []string
	restoredAgents []string
}

func (s *stubDispatcher) Session(string) (types.SessionSnapshot, bool) {
	return types.SessionSnapshot{}, false
}
func (s *stubDispatcher) Sessions() []types.SessionSnapshot { return nil }
func (s *stubDispatcher) History(string, int, int64, int64) ([]types.HistoryMessage, error) {
	return nil, nil
}
func (s *stubDispatcher) PrepareVisibleReply(string, ...string) string     { return "" }
func (s *stubDispatcher) ConfirmReactionReply(string, string, string) bool { return false }
func (s *stubDispatcher) ReconcileDeferredDeliveries(string, []string) ([]string, error) {
	return nil, nil
}
func (s *stubDispatcher) HasDeferredDeliveries(string) bool { return false }
func (s *stubDispatcher) AppendDeferredDelivery(string, int, json.RawMessage) error {
	return nil
}

func (s *stubDispatcher) Dispatch(_ context.Context, _ types.ServerConnection, msg *types.Message) {
	s.mu.Lock()
	defer s.mu.Unlock()
	s.dispatchedMsgs = append(s.dispatchedMsgs, msg)
}

func (s *stubDispatcher) DispatchForDeliveryAck(ctx context.Context, ch types.ServerConnection, msg *types.Message) error {
	s.Dispatch(ctx, ch, msg)
	return nil
}

func (s *stubDispatcher) StartAgentForAgentTeam(_, serverAgentID string, _ types.AgentStartConfig, _ *types.Message) error {
	s.mu.Lock()
	defer s.mu.Unlock()
	s.startedAgents = append(s.startedAgents, serverAgentID)
	return nil
}

func (s *stubDispatcher) StopByServerAgentID(serverAgentID string) bool {
	s.mu.Lock()
	defer s.mu.Unlock()
	s.stoppedAgents = append(s.stoppedAgents, serverAgentID)
	return true
}

func (s *stubDispatcher) InterruptByServerAgentID(serverAgentID string) bool { return false }

func (s *stubDispatcher) InterruptActiveRunByServerAgentID(serverAgentID string) bool {
	return false
}

func (s *stubDispatcher) SleepByServerAgentID(serverAgentID string) bool {
	s.mu.Lock()
	defer s.mu.Unlock()
	s.sleepedAgents = append(s.sleepedAgents, serverAgentID)
	return true
}

func (s *stubDispatcher) RestoreAgentRecoveryPoint(_, serverAgentID, _, _, _, _, _, _ string) error {
	s.mu.Lock()
	defer s.mu.Unlock()
	s.restoredAgents = append(s.restoredAgents, serverAgentID)
	return nil
}

func (s *stubDispatcher) GCRecoveryPoints(string, []types.RecoveryArtifactRequest) error {
	return nil
}
func (s *stubDispatcher) UpdateAgentIdentity(string, string, string) {}
func (s *stubDispatcher) SubscribeTrajectory(string) <-chan types.TrajectoryLogItem {
	ch := make(chan types.TrajectoryLogItem)
	close(ch)
	return ch
}
func (s *stubDispatcher) UnsubscribeTrajectory(string, <-chan types.TrajectoryLogItem) {}
func (s *stubDispatcher) ServerConnections() types.ServerConnectionAccessor            { return nil }
func (s *stubDispatcher) TrajectoryHistory(string, int, int64) []types.TrajectoryLogItem {
	return nil
}

// getDispatchedMsgs returns a snapshot of all dispatched messages.
func (s *stubDispatcher) getDispatchedMsgs() []*types.Message {
	s.mu.Lock()
	defer s.mu.Unlock()
	cp := make([]*types.Message, len(s.dispatchedMsgs))
	copy(cp, s.dispatchedMsgs)
	return cp
}

// getStartedAgents returns a snapshot of all started agent IDs.
func (s *stubDispatcher) getStartedAgents() []string {
	s.mu.Lock()
	defer s.mu.Unlock()
	cp := make([]string, len(s.startedAgents))
	copy(cp, s.startedAgents)
	return cp
}

// getStoppedAgents returns a snapshot of all stopped agent IDs.
func (s *stubDispatcher) getStoppedAgents() []string {
	s.mu.Lock()
	defer s.mu.Unlock()
	cp := make([]string, len(s.stoppedAgents))
	copy(cp, s.stoppedAgents)
	return cp
}

// getSleepedAgents returns a snapshot of all slept agent IDs.
func (s *stubDispatcher) getSleepedAgents() []string {
	s.mu.Lock()
	defer s.mu.Unlock()
	cp := make([]string, len(s.sleepedAgents))
	copy(cp, s.sleepedAgents)
	return cp
}

// ---------------------------------------------------------------------------
// Channel factory — creates a real wire.Protocol pointing at fakeServer
// ---------------------------------------------------------------------------

// startChannel creates and starts a wire.Protocol connected to the given
// fakeServer. Returns the channel, the stub dispatcher, and registers cleanup.
func startChannel(t *testing.T, fs *fakeServer) (types.ServerConnection, *stubDispatcher) {
	t.Helper()

	disp := &stubDispatcher{}

	cfgYAML := fmt.Sprintf("server_url: %s\napi_key: test-api-key\nagent_id: test-agent\nworkspace_id: %s\n", fs.URL(), fakeWorkspaceID)

	chCfg := profile.AgentTeamServer{
		ID:      "e2e-agentteam",
		Enabled: true,
		Config:  types.RawMessage(cfgYAML),
	}

	ch, err := wire.New(chCfg, disp)
	if err != nil {
		t.Fatalf("wire.New: %v", err)
	}

	ctx, cancel := context.WithCancel(context.Background())
	if err := ch.Start(ctx); err != nil {
		cancel()
		t.Fatalf("channel.Start: %v", err)
	}

	t.Cleanup(func() {
		cancel()
		stopCtx, stopCancel := context.WithTimeout(context.Background(), 3*time.Second)
		defer stopCancel()
		_ = ch.Stop(stopCtx)
	})

	return ch, disp
}
