package machine

import (
	"bytes"
	"context"
	"crypto/ed25519"
	"crypto/rand"
	"encoding/json"
	"net/http"
	"net/http/httptest"
	"sync"
	"testing"

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

	"github.com/tidbcloud/AgentTeam/desktop/internal/agent"
	"github.com/tidbcloud/AgentTeam/desktop/internal/api/auth"
	"github.com/tidbcloud/AgentTeam/desktop/internal/crypto"
	"github.com/tidbcloud/AgentTeam/desktop/internal/metrics"
	"github.com/tidbcloud/AgentTeam/desktop/internal/profile"
	"github.com/tidbcloud/AgentTeam/desktop/internal/token"
	"github.com/tidbcloud/AgentTeam/desktop/internal/types"
	"github.com/tidbcloud/AgentTeam/desktop/internal/uisession"
	"github.com/tidbcloud/AgentTeam/desktop/internal/userauth"
)

func TestCurrentBindingEndpointsNeverExposeMachineKey(t *testing.T) {
	setMachineTestEncryptionKeys(t)
	upstream := httptest.NewServer(http.HandlerFunc(func(writer http.ResponseWriter, request *http.Request) {
		writer.Header().Set("Content-Type", "application/json")
		switch request.URL.Path {
		case "/api/v1/auth/login":
			_, _ = writer.Write([]byte(`{"accessToken":"access","refreshToken":"refresh","user":{"id":"usr_1","email":"user@example.com","name":"User"}}`))
		case "/api/v1/workspaces":
			_, _ = writer.Write([]byte(`[{"id":"wsp_1","name":"Workspace","slug":"workspace"}]`))
		case "/api/v1/workspaces/wsp_1/machines":
			require.Equal(t, http.MethodPost, request.Method)
			_, _ = writer.Write([]byte(`{"machine":{"id":"mch_local"},"apiKey":"machine-secret"}`))
		case "/api/v1/machine-auth-resolutions":
			require.Equal(t, http.MethodPost, request.Method)
			_, _ = writer.Write([]byte(`{"machineId":"mch_local","apiKey":"machine-secret","serverUrl":"` + upstreamURL(request) + `","workspaceId":"wsp_1"}`))
		default:
			http.NotFound(writer, request)
		}
	}))
	t.Cleanup(upstream.Close)

	authManager, err := userauth.New(t.TempDir())
	require.NoError(t, err)
	login, err := authManager.Login(context.Background(), upstream.URL, "user@example.com", "password")
	require.NoError(t, err)
	profileManager := profile.New(t.TempDir())
	sessions := uisession.NewStore(uisession.Options{})
	sessionID := token.CreateSession()
	expiresAt, ok := token.SessionExpiresAt(sessionID)
	require.True(t, ok)
	targetID := userauth.BuildTargetID(login.Server.ID, "wsp_1")
	_, err = sessions.Select(sessionID, expiresAt, targetID)
	require.NoError(t, err)

	gin.SetMode(gin.TestMode)
	router := gin.New()
	internal := router.Group("/api/internal", auth.Middleware())
	RegisterRoutes(internal, Dependencies{
		Auth:     authManager,
		Servers:  profileManager.Servers(),
		Sessions: sessions,
	})

	create := performMachineRequest(router, sessionID, http.MethodPost, "/api/internal/machines/current-bindings", map[string]string{"workspaceId": "wsp_1"})
	require.Equal(t, http.StatusOK, create.Code)
	require.JSONEq(t, `{"machineId":"mch_local","status":"created"}`, create.Body.String())
	require.NotContains(t, create.Body.String(), "machine-secret")

	status := performMachineRequest(router, sessionID, http.MethodGet, "/api/internal/machines/current-binding?workspaceId=wsp_1", nil)
	require.Equal(t, http.StatusOK, status.Code)
	require.JSONEq(t, `{"machineId":"mch_local","status":"added"}`, status.Body.String())
	require.NotContains(t, status.Body.String(), "machine-secret")
}

func TestRestoreCurrentBindingReusesExistingMachineWithoutExposingKey(t *testing.T) {
	setMachineTestEncryptionKeys(t)
	var tokenRequests int
	var exchangeRequests int
	upstream := newMachineTestServer(t, func(writer http.ResponseWriter, request *http.Request) {
		switch request.URL.Path {
		case "/api/v1/workspaces/wsp_1/machines/mch_existing/connect-tokens":
			require.Equal(t, http.MethodPost, request.Method)
			require.Equal(t, "Bearer access", request.Header.Get("Authorization"))
			tokenRequests++
			_, _ = writer.Write([]byte(`{"token":"one-time"}`))
		case "/api/v1/machine-connect-exchanges":
			require.Equal(t, http.MethodPost, request.Method)
			exchangeRequests++
			_, _ = writer.Write([]byte(`{"machineId":"mch_existing","apiKey":"restored-secret","serverUrl":"` + upstreamURL(request) + `","workspaceId":"wsp_1"}`))
		case "/api/v1/machine-auth-resolutions":
			_, _ = writer.Write([]byte(`{"machineId":"mch_existing","workspaceId":"wsp_1"}`))
		default:
			http.NotFound(writer, request)
		}
	})
	authManager, target := loginMachineTestUser(t, upstream.URL)
	profileManager := profile.New(t.TempDir())
	sessions := uisession.NewStore(uisession.Options{})
	sessionID := token.CreateSession()
	expiresAt, ok := token.SessionExpiresAt(sessionID)
	require.True(t, ok)
	_, err := sessions.Select(sessionID, expiresAt, target.ID)
	require.NoError(t, err)

	gin.SetMode(gin.TestMode)
	router := gin.New()
	internal := router.Group("/api/internal", auth.Middleware())
	RegisterRoutes(internal, Dependencies{
		Auth:     authManager,
		Servers:  profileManager.Servers(),
		Sessions: sessions,
	})

	restore := performMachineRequest(router, sessionID, http.MethodPost, "/api/internal/machines/current-bindings/restore", map[string]string{
		"workspaceId": "wsp_1",
		"machineId":   "mch_existing",
	})

	require.Equal(t, http.StatusOK, restore.Code)
	require.JSONEq(t, `{"machineId":"mch_existing","status":"restored"}`, restore.Body.String())
	require.NotContains(t, restore.Body.String(), "restored-secret")
	require.Equal(t, 1, tokenRequests)
	require.Equal(t, 1, exchangeRequests)
	servers := profileManager.Servers().All()
	require.Len(t, servers, 1)
	require.True(t, servers[0].Enabled)
	require.Equal(t, "mch_existing", servers[0].MachineID)
	require.Equal(t, "restored-secret", servers[0].APIKey)
}

type machineDashboardMetricsStub struct {
	requestedAgentIDs []string
	result            metrics.AgentDashboardMetrics
}

func (stub *machineDashboardMetricsStub) QueryAgentsDashboard(agentIDs []string, _ metrics.TimeWindow) metrics.AgentDashboardMetrics {
	stub.requestedAgentIDs = append([]string(nil), agentIDs...)
	return stub.result
}

type machineTokenTrackingStub struct {
	requested metrics.TokenSourceQuery
	result    metrics.TokenSourceResult
}

func (stub *machineTokenTrackingStub) QueryTokenTracking(query metrics.TokenSourceQuery) metrics.TokenSourceResult {
	stub.requested = query
	return stub.result
}

type machineDashboardSessionsStub struct {
	sessions []types.SessionSnapshot
}

func (stub machineDashboardSessionsStub) Sessions() []types.SessionSnapshot {
	return stub.sessions
}

func TestMachineDashboardScopesMetricsToCurrentDesktopAgents(t *testing.T) {
	setMachineTestEncryptionKeys(t)
	upstream := newMachineTestServer(t, func(writer http.ResponseWriter, request *http.Request) {
		http.NotFound(writer, request)
	})
	profileManager := profile.New(t.TempDir())
	server := profile.AgentTeamServer{
		ID:          "server-current",
		Enabled:     true,
		ServerURL:   upstream.URL,
		WorkspaceID: "wsp_1",
		MachineID:   "mch_local",
		APIKey:      "machine-secret",
	}
	require.NoError(t, profileManager.Servers().Add(&server))
	require.NoError(t, profileManager.Agents().Add(agent.Config{ID: "agent-a", Name: "Alpha", Runtime: "codex", Source: agent.NewAgentTeamSource(server.ID)}))
	require.NoError(t, profileManager.Agents().Add(agent.Config{ID: "agent-other", Name: "Other", Runtime: "codex", Source: agent.NewAgentTeamSource("another-server")}))

	authManager, target := loginMachineTestUser(t, upstream.URL)
	targetID := target.ID
	sessions := uisession.NewStore(uisession.Options{})
	sessionID := token.CreateSession()
	expiresAt, ok := token.SessionExpiresAt(sessionID)
	require.True(t, ok)
	_, err := sessions.Select(sessionID, expiresAt, targetID)
	require.NoError(t, err)

	metricsStub := &machineDashboardMetricsStub{result: metrics.AgentDashboardMetrics{
		MessageSummary: metrics.AgentMessageSummary{Received: 2, Sent: 1, Total: 3},
		MessageTrend:   metrics.ServerMessageDirectionalTimeSeriesResponse{Interval: 300, Data: []metrics.ServerMessageDirectionalTimeSeriesPoint{}},
		MessageBar:     metrics.BarSeriesResponse{Interval: 1800, Series: []metrics.BarSeriesEntry{{Key: "inbound", Label: "Inbound"}, {Key: "outbound", Label: "Outbound"}}, Data: []metrics.BarSeriesPoint{}},
		TokenSummary:   metrics.AgentTokenSummary{Input: 10, Cached: 2, Output: 3, Total: 15},
		TokenTrend:     metrics.AgentTokenTimeSeriesResponse{Interval: 300, Data: []metrics.TimeSeriesDataPoint{}},
		InputTokenBar:  metrics.BarSeriesResponse{Interval: 1800, Series: []metrics.BarSeriesEntry{{Key: "gpt", Label: "gpt"}}, Data: []metrics.BarSeriesPoint{}},
		CachedTokenBar: metrics.BarSeriesResponse{Interval: 1800, Series: []metrics.BarSeriesEntry{{Key: "gpt", Label: "gpt"}}, Data: []metrics.BarSeriesPoint{}},
		OutputTokenBar: metrics.BarSeriesResponse{Interval: 1800, Series: []metrics.BarSeriesEntry{{Key: "gpt", Label: "gpt"}}, Data: []metrics.BarSeriesPoint{}},
		Agents:         []metrics.AgentDashboardRow{{AgentID: "agent-a", Received: 2, Sent: 1, Input: 10, Cached: 2, Output: 3, Total: 15}},
	}}
	tokenTrackingStub := &machineTokenTrackingStub{result: metrics.TokenSourceResult{
		Available: true,
		Trend: metrics.TokenSourceTrend{
			Series: []metrics.TokenSourceTrendSeries{
				{Key: "source_0", Label: "task:get", SourceKind: metrics.TokenSourceKindCommand},
			},
			Data: []metrics.TokenSourceTrendPoint{},
		},
	}}

	gin.SetMode(gin.TestMode)
	router := gin.New()
	internal := router.Group("/api/internal", auth.Middleware())
	RegisterRoutes(internal, Dependencies{
		Auth:             authManager,
		Agents:           profileManager.Agents(),
		DashboardMetrics: metricsStub,
		TokenTracking:    tokenTrackingStub,
		Servers:          profileManager.Servers(),
		Sessions:         sessions,
		AgentSessions:    machineDashboardSessionsStub{sessions: []types.SessionSnapshot{{AgentConfigID: "agent-a"}}},
	})

	response := performMachineRequest(router, sessionID, http.MethodGet, "/api/internal/bundles/machines/mch_local/dashboard?workspaceId=wsp_1&window=1d&runtime=codex", nil)
	require.Equal(t, http.StatusOK, response.Code)
	require.Equal(t, []string{"agent-a"}, metricsStub.requestedAgentIDs)
	require.Equal(t, []string{"agent-a"}, tokenTrackingStub.requested.AgentIDs)
	require.Equal(t, metrics.TimeWindow1Day, tokenTrackingStub.requested.Window)
	require.JSONEq(t, `{
		"machineId":"mch_local","workspaceId":"wsp_1","window":"1d",
		"agentCount":1,"activeAgents":1,
		"messageSummary":{"agentId":"","received":2,"sent":1,"total":3},
		"messageTrend":{"interval":300,"data":[]},
		"messageBar":{"interval":1800,"series":[{"key":"inbound","label":"Inbound"},{"key":"outbound","label":"Outbound"}],"data":[]},
		"tokenSummary":{"agentId":"","count":0,"input":10,"cached":2,"output":3,"total":15},
		"tokenTrend":{"interval":300,"data":[]},
		"inputTokenBar":{"interval":1800,"series":[{"key":"gpt","label":"gpt"}],"data":[]},
		"cachedTokenBar":{"interval":1800,"series":[{"key":"gpt","label":"gpt"}],"data":[]},
		"outputTokenBar":{"interval":1800,"series":[{"key":"gpt","label":"gpt"}],"data":[]},
		"tokenSources":{
			"available":true,
			"trend":{"series":[{"key":"source_0","label":"task:get","sourceKind":"command"}],"data":[]}
		},
		"agents":[{"agentId":"agent-a","name":"Alpha","runtime":"codex","active":true,"received":2,"sent":1,"input":10,"cached":2,"output":3,"total":15}]
	}`, response.Body.String())

	wrongMachine := performMachineRequest(router, sessionID, http.MethodGet, "/api/internal/bundles/machines/mch_other/dashboard?workspaceId=wsp_1&window=1d", nil)
	require.Equal(t, http.StatusConflict, wrongMachine.Code)

	filtered := performMachineRequest(router, sessionID, http.MethodGet, "/api/internal/bundles/machines/mch_local/dashboard?workspaceId=wsp_1&window=1d&agentId=agent-a&agentId=agent-a", nil)
	require.Equal(t, http.StatusOK, filtered.Code)
	require.Equal(t, []string{"agent-a"}, metricsStub.requestedAgentIDs)

	foreignAgent := performMachineRequest(router, sessionID, http.MethodGet, "/api/internal/bundles/machines/mch_local/dashboard?workspaceId=wsp_1&window=1d&agentId=agent-other", nil)
	require.Equal(t, http.StatusBadRequest, foreignAgent.Code)
	require.Contains(t, foreignAgent.Body.String(), "does not belong")
}

func TestMachineDashboardReturnsUnavailableTokenTrackingShape(t *testing.T) {
	setMachineTestEncryptionKeys(t)
	upstream := newMachineTestServer(t, func(writer http.ResponseWriter, request *http.Request) {
		http.NotFound(writer, request)
	})
	profileManager := profile.New(t.TempDir())
	server := profile.AgentTeamServer{
		ID:          "server-current",
		Enabled:     true,
		ServerURL:   upstream.URL,
		WorkspaceID: "wsp_1",
		MachineID:   "mch_local",
		APIKey:      "machine-secret",
	}
	require.NoError(t, profileManager.Servers().Add(&server))
	require.NoError(t, profileManager.Agents().Add(agent.Config{
		ID: "agent-a", Name: "Alpha", Runtime: "codex", Source: agent.NewAgentTeamSource(server.ID),
	}))

	authManager, target := loginMachineTestUser(t, upstream.URL)
	sessions := uisession.NewStore(uisession.Options{})
	sessionID := token.CreateSession()
	expiresAt, ok := token.SessionExpiresAt(sessionID)
	require.True(t, ok)
	_, err := sessions.Select(sessionID, expiresAt, target.ID)
	require.NoError(t, err)

	gin.SetMode(gin.TestMode)
	router := gin.New()
	internal := router.Group("/api/internal", auth.Middleware())
	RegisterRoutes(internal, Dependencies{
		Auth:             authManager,
		Agents:           profileManager.Agents(),
		DashboardMetrics: &machineDashboardMetricsStub{},
		Servers:          profileManager.Servers(),
		Sessions:         sessions,
	})

	response := performMachineRequest(router, sessionID, http.MethodGet, "/api/internal/bundles/machines/mch_local/dashboard?workspaceId=wsp_1&window=1d", nil)
	require.Equal(t, http.StatusOK, response.Code)
	var body struct {
		TokenSources metrics.TokenSourceResult `json:"tokenSources"`
	}
	require.NoError(t, json.Unmarshal(response.Body.Bytes(), &body))
	require.False(t, body.TokenSources.Available)
	require.NotNil(t, body.TokenSources.Trend.Data)
}

func TestCurrentBindingRecognizesDisabledLocalBinding(t *testing.T) {
	setMachineTestEncryptionKeys(t)
	upstream := newMachineTestServer(t, func(writer http.ResponseWriter, request *http.Request) {
		switch request.URL.Path {
		case "/api/v1/machine-auth-resolutions":
			_, _ = writer.Write([]byte(`{"machineId":"mch_local","workspaceId":"wsp_1"}`))
		default:
			http.NotFound(writer, request)
		}
	})
	authManager, target := loginMachineTestUser(t, upstream.URL)
	profileManager := profile.New(t.TempDir())
	require.NoError(t, profileManager.Servers().Add(&profile.AgentTeamServer{
		Enabled:     false,
		ServerURL:   upstream.URL + "/",
		WorkspaceID: "wsp_1",
		MachineID:   "mch_local",
		APIKey:      "machine-secret",
	}))

	service := NewService(Dependencies{Auth: authManager, Servers: profileManager.Servers()})
	reused, created, status, err := service.EnsureCurrentBinding(context.Background(), target, false)
	require.NoError(t, err)
	require.False(t, created)
	require.Equal(t, http.StatusOK, status)
	require.Equal(t, "mch_local", reused.MachineID)
	servers := profileManager.Servers().All()
	require.Len(t, servers, 1)
	require.True(t, servers[0].Enabled)
}

func TestCurrentBindingPrefersEnabledBinding(t *testing.T) {
	setMachineTestEncryptionKeys(t)
	upstream := newMachineTestServer(t, func(writer http.ResponseWriter, request *http.Request) {
		var body map[string]string
		require.NoError(t, json.NewDecoder(request.Body).Decode(&body))
		machineID := "mch_disabled"
		if body["apiKey"] == "key-enabled" {
			machineID = "mch_enabled"
		}
		_, _ = writer.Write([]byte(`{"machineId":"` + machineID + `","workspaceId":"wsp_1"}`))
	})
	authManager, target := loginMachineTestUser(t, upstream.URL)
	profileManager := profile.New(t.TempDir())
	for _, server := range []profile.AgentTeamServer{
		{
			ID:          "disabled",
			Enabled:     false,
			ServerURL:   upstream.URL,
			WorkspaceID: "wsp_1",
			MachineID:   "mch_disabled",
			APIKey:      "key-disabled",
		},
		{
			ID:          "enabled",
			Enabled:     true,
			ServerURL:   upstream.URL,
			WorkspaceID: "wsp_1",
			MachineID:   "mch_enabled",
			APIKey:      "key-enabled",
		},
	} {
		server := server
		require.NoError(t, profileManager.Servers().Add(&server))
	}

	service := NewService(Dependencies{Auth: authManager, Servers: profileManager.Servers()})
	binding, ok, status, err := service.ReconcileCurrentBinding(context.Background(), target)

	require.NoError(t, err)
	require.Equal(t, http.StatusOK, status)
	require.True(t, ok)
	require.Equal(t, "mch_enabled", binding.MachineID)
}

func TestEnsureFirstBindingReplacesServerDeletedLocalMachine(t *testing.T) {
	setMachineTestEncryptionKeys(t)
	var created int
	upstream := newMachineTestServer(t, func(writer http.ResponseWriter, request *http.Request) {
		switch {
		case request.URL.Path == "/api/v1/machine-auth-resolutions":
			writer.WriteHeader(http.StatusUnauthorized)
			_, _ = writer.Write([]byte(`{"error":"invalid machine API key"}`))
		case request.Method == http.MethodGet && request.URL.Path == "/api/v1/workspaces/wsp_1/machines":
			require.Equal(t, "own", request.URL.Query().Get("scope"))
			_, _ = writer.Write([]byte(`{"machines":[],"hasMore":false}`))
		case request.Method == http.MethodPost && request.URL.Path == "/api/v1/workspaces/wsp_1/machines":
			created++
			_, _ = writer.Write([]byte(`{"machine":{"id":"mch_replacement"},"apiKey":"replacement-secret"}`))
		default:
			http.NotFound(writer, request)
		}
	})
	authManager, target := loginMachineTestUser(t, upstream.URL)
	profileManager := profile.New(t.TempDir())
	stale := addMachineTestBinding(t, profileManager, upstream.URL, "mch_deleted", "deleted-secret")
	runtime := activateMachineTestRuntime(t, profileManager)
	service := NewService(Dependencies{Auth: authManager, Servers: profileManager.Servers()})

	binding, wasCreated, status, err := service.EnsureCurrentBinding(context.Background(), target, true)

	require.NoError(t, err)
	require.Equal(t, http.StatusOK, status)
	require.True(t, wasCreated)
	require.Equal(t, "mch_replacement", binding.MachineID)
	require.Equal(t, 1, created)
	servers := profileManager.Servers().All()
	require.Len(t, servers, 1)
	require.Equal(t, "mch_replacement", servers[0].MachineID)
	require.True(t, runtime.deleted(stale.ID), "stale binding must disconnect through runtime reconciliation")
}

func TestEnsureFirstBindingRemovesStaleLocalMachineWithoutCreatingWhenUserOwnsAnother(t *testing.T) {
	setMachineTestEncryptionKeys(t)
	var created int
	upstream := newMachineTestServer(t, func(writer http.ResponseWriter, request *http.Request) {
		switch {
		case request.URL.Path == "/api/v1/machine-auth-resolutions":
			writer.WriteHeader(http.StatusUnauthorized)
			_, _ = writer.Write([]byte(`{"error":"invalid machine API key"}`))
		case request.Method == http.MethodGet && request.URL.Path == "/api/v1/workspaces/wsp_1/machines":
			_, _ = writer.Write([]byte(`{"machines":[{"id":"mch_other"}],"hasMore":false}`))
		case request.Method == http.MethodPost && request.URL.Path == "/api/v1/workspaces/wsp_1/machines":
			created++
		default:
			http.NotFound(writer, request)
		}
	})
	authManager, target := loginMachineTestUser(t, upstream.URL)
	profileManager := profile.New(t.TempDir())
	stale := addMachineTestBinding(t, profileManager, upstream.URL, "mch_deleted", "deleted-secret")
	runtime := activateMachineTestRuntime(t, profileManager)
	service := NewService(Dependencies{Auth: authManager, Servers: profileManager.Servers()})

	binding, wasCreated, status, err := service.EnsureCurrentBinding(context.Background(), target, true)

	require.NoError(t, err)
	require.Equal(t, http.StatusOK, status)
	require.False(t, wasCreated)
	require.Empty(t, binding.MachineID)
	require.Zero(t, created)
	require.Empty(t, profileManager.Servers().All())
	require.True(t, runtime.deleted(stale.ID))
}

func TestExplicitAddReplacesStaleBindingEvenWhenUserOwnsAnotherMachine(t *testing.T) {
	setMachineTestEncryptionKeys(t)
	var listRequests, created int
	upstream := newMachineTestServer(t, func(writer http.ResponseWriter, request *http.Request) {
		switch {
		case request.URL.Path == "/api/v1/machine-auth-resolutions":
			writer.WriteHeader(http.StatusUnauthorized)
			_, _ = writer.Write([]byte(`{"error":"invalid machine API key"}`))
		case request.Method == http.MethodGet && request.URL.Path == "/api/v1/workspaces/wsp_1/machines":
			listRequests++
		case request.Method == http.MethodPost && request.URL.Path == "/api/v1/workspaces/wsp_1/machines":
			created++
			_, _ = writer.Write([]byte(`{"machine":{"id":"mch_explicit"},"apiKey":"explicit-secret"}`))
		default:
			http.NotFound(writer, request)
		}
	})
	authManager, target := loginMachineTestUser(t, upstream.URL)
	profileManager := profile.New(t.TempDir())
	addMachineTestBinding(t, profileManager, upstream.URL, "mch_deleted", "deleted-secret")
	service := NewService(Dependencies{Auth: authManager, Servers: profileManager.Servers()})

	binding, wasCreated, status, err := service.EnsureCurrentBinding(context.Background(), target, false)

	require.NoError(t, err)
	require.Equal(t, http.StatusOK, status)
	require.True(t, wasCreated)
	require.Equal(t, "mch_explicit", binding.MachineID)
	require.Zero(t, listRequests, "explicit add must not be gated by the user's other Machines")
	require.Equal(t, 1, created)
	servers := profileManager.Servers().All()
	require.Len(t, servers, 1)
	require.Equal(t, "mch_explicit", servers[0].MachineID)
}

func TestReconcileCurrentBindingKeepsLocalStateOnServerFailure(t *testing.T) {
	setMachineTestEncryptionKeys(t)
	upstream := newMachineTestServer(t, func(writer http.ResponseWriter, request *http.Request) {
		writer.WriteHeader(http.StatusServiceUnavailable)
		_, _ = writer.Write([]byte(`{"error":"temporarily unavailable"}`))
	})
	authManager, target := loginMachineTestUser(t, upstream.URL)
	profileManager := profile.New(t.TempDir())
	addMachineTestBinding(t, profileManager, upstream.URL, "mch_local", "machine-secret")
	service := NewService(Dependencies{Auth: authManager, Servers: profileManager.Servers()})

	_, exists, status, err := service.ReconcileCurrentBinding(context.Background(), target)

	require.ErrorContains(t, err, "temporarily unavailable")
	require.False(t, exists)
	require.Equal(t, http.StatusServiceUnavailable, status)
	require.Len(t, profileManager.Servers().All(), 1, "transient failures must not delete local credentials")
}

func TestEnsureCurrentBindingWaitsForDaemonConnection(t *testing.T) {
	setMachineTestEncryptionKeys(t)
	upstream := newMachineTestServer(t, func(writer http.ResponseWriter, request *http.Request) {
		switch request.URL.Path {
		case "/api/v1/workspaces/wsp_1/machines":
			_, _ = writer.Write([]byte(`{"machine":{"id":"mch_local"},"apiKey":"machine-secret"}`))
		default:
			http.NotFound(writer, request)
		}
	})
	authManager, target := loginMachineTestUser(t, upstream.URL)
	profileManager := profile.New(t.TempDir())
	readiness := &machineTestReadiness{}
	service := NewService(Dependencies{
		Auth:                authManager,
		ConnectionReadiness: readiness,
		Servers:             profileManager.Servers(),
	})

	binding, created, status, err := service.EnsureCurrentBinding(context.Background(), target, false)

	require.NoError(t, err)
	require.True(t, created)
	require.Equal(t, http.StatusOK, status)
	require.Equal(t, "mch_local", binding.MachineID)
	servers := profileManager.Servers().All()
	require.Len(t, servers, 1)
	require.Equal(t, servers[0].ID, readiness.serverID)
}

func TestEnsureCurrentBindingReportsConnectionFailure(t *testing.T) {
	setMachineTestEncryptionKeys(t)
	upstream := newMachineTestServer(t, func(writer http.ResponseWriter, request *http.Request) {
		switch request.URL.Path {
		case "/api/v1/workspaces/wsp_1/machines":
			_, _ = writer.Write([]byte(`{"machine":{"id":"mch_local"},"apiKey":"machine-secret"}`))
		default:
			http.NotFound(writer, request)
		}
	})
	authManager, target := loginMachineTestUser(t, upstream.URL)
	profileManager := profile.New(t.TempDir())
	readiness := &machineTestReadiness{err: context.DeadlineExceeded}
	service := NewService(Dependencies{
		Auth:                authManager,
		ConnectionReadiness: readiness,
		Servers:             profileManager.Servers(),
	})

	_, created, status, err := service.EnsureCurrentBinding(context.Background(), target, false)

	require.ErrorContains(t, err, "could not connect")
	require.False(t, created)
	require.Equal(t, http.StatusGatewayTimeout, status)
	require.Len(t, profileManager.Servers().All(), 1, "the saved credential remains available for retry")
}

type machineTestTransition struct{}

func (machineTestTransition) Complete()   {}
func (machineTestTransition) FailClosed() {}

type machineTestRuntime struct {
	mu         sync.Mutex
	deletedIDs []string
}

type machineTestReadiness struct {
	serverID string
	err      error
}

func (readiness *machineTestReadiness) WaitForServerConnection(_ context.Context, serverID string) error {
	readiness.serverID = serverID
	return readiness.err
}

func (runtime *machineTestRuntime) BeginServerTransition(string) profile.ServerTransition {
	return machineTestTransition{}
}

func (runtime *machineTestRuntime) ReconcileServer(_ context.Context, previous, current *profile.AgentTeamServer) error {
	if previous != nil && current == nil {
		runtime.mu.Lock()
		runtime.deletedIDs = append(runtime.deletedIDs, previous.ID)
		runtime.mu.Unlock()
	}
	return nil
}

func (runtime *machineTestRuntime) RepairServer(context.Context, profile.AgentTeamServer) error {
	return nil
}
func (runtime *machineTestRuntime) InvalidateServer(string) {}

func (runtime *machineTestRuntime) deleted(id string) bool {
	runtime.mu.Lock()
	defer runtime.mu.Unlock()
	for _, deletedID := range runtime.deletedIDs {
		if deletedID == id {
			return true
		}
	}
	return false
}

func activateMachineTestRuntime(t *testing.T, manager *profile.Manager) *machineTestRuntime {
	t.Helper()
	runtime := &machineTestRuntime{}
	require.NoError(t, manager.Servers().PrepareRuntimeMode())
	require.NoError(t, manager.Servers().ActivateRuntimeMode(runtime))
	t.Cleanup(manager.Servers().DeactivateRuntimeMode)
	return runtime
}

func addMachineTestBinding(t *testing.T, manager *profile.Manager, serverURL, machineID, apiKey string) profile.AgentTeamServer {
	t.Helper()
	server := profile.AgentTeamServer{
		Enabled:     true,
		ServerURL:   serverURL,
		WorkspaceID: "wsp_1",
		MachineID:   machineID,
		APIKey:      apiKey,
	}
	require.NoError(t, manager.Servers().Add(&server))
	return server
}

func newMachineTestServer(t *testing.T, machineHandler http.HandlerFunc) *httptest.Server {
	t.Helper()
	server := httptest.NewServer(http.HandlerFunc(func(writer http.ResponseWriter, request *http.Request) {
		writer.Header().Set("Content-Type", "application/json")
		switch request.URL.Path {
		case "/api/v1/auth/login":
			_, _ = writer.Write([]byte(`{"accessToken":"access","refreshToken":"refresh","user":{"id":"usr_1","email":"user@example.com","name":"User"}}`))
		case "/api/v1/workspaces":
			_, _ = writer.Write([]byte(`[{"id":"wsp_1","name":"Workspace","slug":"workspace"}]`))
		default:
			machineHandler(writer, request)
		}
	}))
	t.Cleanup(server.Close)
	return server
}

func loginMachineTestUser(t *testing.T, serverURL string) (*userauth.Manager, userauth.Target) {
	t.Helper()
	authManager, err := userauth.New(t.TempDir())
	require.NoError(t, err)
	login, err := authManager.Login(context.Background(), serverURL, "user@example.com", "password")
	require.NoError(t, err)
	targetID := userauth.BuildTargetID(login.Server.ID, "wsp_1")
	target, ok := authManager.Target(targetID)
	require.True(t, ok)
	return authManager, target
}

func upstreamURL(request *http.Request) string {
	return "http://" + request.Host
}

func performMachineRequest(handler http.Handler, sessionID, method, path string, body any) *httptest.ResponseRecorder {
	var payload bytes.Buffer
	if body != nil {
		_ = json.NewEncoder(&payload).Encode(body)
	}
	request := httptest.NewRequest(method, path, &payload)
	request.Header.Set("Authorization", "Bearer "+sessionID)
	request.Header.Set("Content-Type", "application/json")
	recorder := httptest.NewRecorder()
	handler.ServeHTTP(recorder, request)
	return recorder
}

func setMachineTestEncryptionKeys(t *testing.T) {
	t.Helper()
	publicKey, privateKey, err := ed25519.GenerateKey(rand.Reader)
	require.NoError(t, err)
	require.NoError(t, crypto.SetKeys(publicKey, privateKey))
	t.Cleanup(crypto.ResetKeys)
}
