package daemon

import (
	"context"
	"encoding/json"
	"errors"
	"strings"
	"time"

	"github.com/tidbcloud/AgentTeam/internal/runtimecontrol"
)

// ErrMemorySyncEndpointUnsupported means the connected Desktop does not expose
// the daemon-local Memory sync projection through the remote endpoint registry.
var ErrMemorySyncEndpointUnsupported = errors.New("memory sync endpoint unsupported")

// AgentMemorySyncStatus is the bounded daemon-local synchronization projection.
// It deliberately carries no repository details or raw errors.
type AgentMemorySyncStatus struct {
	State   string    `json:"state"`
	Reason  string    `json:"reason,omitempty"`
	RetryAt time.Time `json:"retryAt,omitzero"`
}

type memorySyncRemoteRequestPayload struct {
	AgentID string `json:"agentId"`
}

type memorySyncStatusRemoteResponse struct {
	Available bool                   `json:"available"`
	Status    *AgentMemorySyncStatus `json:"status,omitempty"`
}

type memorySyncStatusSnapshot struct {
	Status    AgentMemorySyncStatus
	Available bool
}

type memorySyncRetryRemoteResponse struct {
	RetryRequested bool `json:"retryRequested"`
}

// GetAgentMemorySyncStatus reads the connected daemon's local runtime state.
// It never reads the cloud repository or persists the transient projection.
func (hub *Hub) GetAgentMemorySyncStatus(ctx context.Context, machineID, agentID string) (AgentMemorySyncStatus, bool, error) {
	client := hub.client(machineID)
	if client == nil {
		return AgentMemorySyncStatus{}, false, runtimecontrol.ErrMachineOffline
	}
	descriptor, ok := hub.remoteEndpointDescriptor(remoteEndpointRuntimeMemorySyncStatus, 1)
	if !ok || hub.elasticRequestLinks == nil || !client.supportsRemoteEndpoint(descriptor.EndpointID, descriptor.Version, descriptor.RegistryVersion) {
		return AgentMemorySyncStatus{}, false, ErrMemorySyncEndpointUnsupported
	}
	snapshot, err := invokeRemoteEndpoint(RemoteEndpointInvocation[memorySyncStatusSnapshot]{
		Context:      ctx,
		Client:       client,
		Manager:      hub.elasticRequestLinks,
		Descriptor:   descriptor,
		RouteOptions: remoteRequestRouteOptionsFromContext(ctx),
		Fallback: func() (memorySyncStatusSnapshot, error) {
			return memorySyncStatusSnapshot{}, ErrMemorySyncEndpointUnsupported
		},
		BuildSpec: func() (RemoteRequestAssignmentSpec, error) {
			return newMemorySyncRemoteRequestSpec(ctx, descriptor, "memory_sync_status", agentID)
		},
		MapResult: func(result RemoteRequestResult[json.RawMessage]) (memorySyncStatusSnapshot, error) {
			if err := remoteEndpointResultError(result); err != nil {
				return memorySyncStatusSnapshot{}, err
			}
			var response memorySyncStatusRemoteResponse
			if err := json.Unmarshal(result.Payload, &response); err != nil {
				return memorySyncStatusSnapshot{}, err
			}
			if !response.Available || response.Status == nil {
				return memorySyncStatusSnapshot{}, nil
			}
			return memorySyncStatusSnapshot{Status: *response.Status, Available: true}, nil
		},
	})
	return snapshot.Status, snapshot.Available, err
}

// RetryAgentMemorySync asks the connected daemon to retry through its existing
// serialized Memory pipeline.
func (hub *Hub) RetryAgentMemorySync(ctx context.Context, machineID, agentID string) (bool, error) {
	client := hub.client(machineID)
	if client == nil {
		return false, runtimecontrol.ErrMachineOffline
	}
	descriptor, ok := hub.remoteEndpointDescriptor(remoteEndpointRuntimeMemorySyncRetry, 1)
	if !ok || hub.elasticRequestLinks == nil || !client.supportsRemoteEndpoint(descriptor.EndpointID, descriptor.Version, descriptor.RegistryVersion) {
		return false, ErrMemorySyncEndpointUnsupported
	}
	return invokeRemoteEndpoint(RemoteEndpointInvocation[bool]{
		Context:      ctx,
		Client:       client,
		Manager:      hub.elasticRequestLinks,
		Descriptor:   descriptor,
		RouteOptions: remoteRequestRouteOptionsFromContext(ctx),
		Fallback: func() (bool, error) {
			return false, ErrMemorySyncEndpointUnsupported
		},
		BuildSpec: func() (RemoteRequestAssignmentSpec, error) {
			return newMemorySyncRemoteRequestSpec(ctx, descriptor, "memory_sync_retry", agentID)
		},
		MapResult: func(result RemoteRequestResult[json.RawMessage]) (bool, error) {
			if err := remoteEndpointResultError(result); err != nil {
				return false, err
			}
			var response memorySyncRetryRemoteResponse
			if err := json.Unmarshal(result.Payload, &response); err != nil {
				return false, err
			}
			return response.RetryRequested, nil
		},
	})
}

func newMemorySyncRemoteRequestSpec(ctx context.Context, descriptor RemoteEndpointDescriptor, prefix, agentID string) (RemoteRequestAssignmentSpec, error) {
	payload, err := json.Marshal(memorySyncRemoteRequestPayload{AgentID: strings.TrimSpace(agentID)})
	if err != nil {
		return RemoteRequestAssignmentSpec{}, err
	}
	return newRemoteRequestAssignmentSpec(ctx, descriptor, prefix, payload)
}
