using ControlCenter.Api.Dtos;
using Microsoft.AspNetCore.SignalR;

namespace ControlCenter.Api.Hubs;

/// <summary>
/// SignalR hub for broadcasting agent status updates to connected clients.
///
/// <para>
/// Clients call <see cref="SendStatusUpdate"/> to broadcast a status change,
/// and the hub relays it to all connected clients via the
/// <see cref="IAgentStatusClient.AgentStatusChanged"/> callback.
/// </para>
///
/// <para>
/// Server-side code should use <see cref="AgentStatusHubExtensions.PushStatusUpdateAsync"/>
/// via <c>IHubContext&lt;AgentStatusHub, IAgentStatusClient&gt;</c> for background-service broadcasts.
/// </para>
///
/// <para>
/// Architecture note: This hub bridges OpenClaw Gateway events to SignalR clients.
/// A background service subscribes to Gateway events and pushes them through
/// this hub's extension methods.
/// </para>
/// </summary>
public class AgentStatusHub : Hub<IAgentStatusClient>
{
    private readonly ILogger<AgentStatusHub> _logger;

    /// <summary>
    /// Initializes a new instance of the <see cref="AgentStatusHub"/> class.
    /// </summary>
    /// <param name="logger">Logger for diagnostic output.</param>
    public AgentStatusHub(ILogger<AgentStatusHub> logger)
    {
        _logger = logger;
    }

    /// <summary>
    /// Broadcasts an agent status update to all connected clients.
    ///
    /// <para>
    /// Any connected client (or server-side caller) can invoke this method
    /// to push a status update to every subscriber. The update is relayed
    /// through the <see cref="IAgentStatusClient.AgentStatusChanged"/> callback.
    /// </para>
    /// </summary>
    /// <param name="update">The agent status update payload to broadcast.</param>
    public async Task SendStatusUpdate(AgentStatusUpdateDto update)
    {
        _logger.LogInformation(
            "Broadcasting status update for agent {AgentId}: {Status}",
            update.AgentId, update.Status);

        await Clients.All.AgentStatusChanged(update);
    }

    /// <summary>
    /// Adds the calling connection to the fleet group.
    /// Once joined, the client will receive all agent status updates.
    /// </summary>
    public async Task JoinFleet()
    {
        await Groups.AddToGroupAsync(Context.ConnectionId, FleetGroupName);
        _logger.LogDebug("Connection {ConnectionId} joined fleet group", Context.ConnectionId);
    }

    /// <summary>
    /// Removes the calling connection from the fleet group.
    /// </summary>
    public async Task LeaveFleet()
    {
        await Groups.RemoveFromGroupAsync(Context.ConnectionId, FleetGroupName);
        _logger.LogDebug("Connection {ConnectionId} left fleet group", Context.ConnectionId);
    }

    /// <summary>
    /// Overrides <see cref="Hub{T}.OnDisconnectedAsync"/> to log disconnections.
    /// SignalR automatically removes disconnected connections from all groups.
    /// </summary>
    /// <param name="exception">Exception that caused the disconnection, if any.</param>
    public override Task OnDisconnectedAsync(Exception? exception)
    {
        _logger.LogDebug("Connection {ConnectionId} disconnected", Context.ConnectionId);
        return base.OnDisconnectedAsync(exception);
    }

    /// <summary>
    /// The SignalR group name for the entire fleet (all agents).
    /// </summary>
    internal const string FleetGroupName = "fleet";
}

/// <summary>
/// Strongly-typed client interface for the AgentStatus SignalR hub.
/// Defines the methods the server can invoke on connected clients
/// to push real-time agent status updates.
/// </summary>
public interface IAgentStatusClient
{
    /// <summary>
    /// Pushes an agent status change to all subscribed clients.
    /// Fired whenever an agent's operational status changes
    /// (e.g., idle → active, active → thinking, active → error).
    /// </summary>
    /// <param name="update">The full status update payload.</param>
    /// <returns>A Task that completes when the client has processed the update.</returns>
    Task AgentStatusChanged(AgentStatusUpdateDto update);
}

/// <summary>
/// Extension methods for pushing real-time agent updates through
/// the <see cref="IHubContext{T}"/> of <see cref="AgentStatusHub"/>.
///
/// <para>
/// These methods are intended to be called from background services
/// or other server-side code that detects an agent state change,
/// using the injected <c>IHubContext&lt;AgentStatusHub, IAgentStatusClient&gt;</c>.
/// </para>
/// </summary>
public static class AgentStatusHubExtensions
{
    /// <summary>
    /// Pushes an agent status update to all connected clients.
    ///
    /// <para>
    /// Call this from any background service when an agent's
    /// operational status changes (e.g., the Gateway reports a
    /// session transition from "running" to "done").
    /// </para>
    /// </summary>
    /// <param name="hubContext">The hub context injected via DI.</param>
    /// <param name="update">The agent status update payload.</param>
    /// <returns>A Task that completes when the message has been sent to all clients.</returns>
    public static async Task PushStatusUpdateAsync(
        this IHubContext<AgentStatusHub, IAgentStatusClient> hubContext,
        AgentStatusUpdateDto update)
    {
        await hubContext.Clients.All.AgentStatusChanged(update);
    }

    /// <summary>
    /// Pushes an agent status update to clients subscribed to the fleet group.
    /// </summary>
    /// <param name="hubContext">The hub context injected via DI.</param>
    /// <param name="update">The agent status update payload.</param>
    /// <returns>A Task that completes when the message has been sent to the fleet group.</returns>
    public static async Task PushStatusUpdateToFleetAsync(
        this IHubContext<AgentStatusHub, IAgentStatusClient> hubContext,
        AgentStatusUpdateDto update)
    {
        await hubContext.Clients.Group(AgentStatusHub.FleetGroupName)
            .AgentStatusChanged(update);
    }
}