using System.Collections.Concurrent;
using Microsoft.AspNetCore.SignalR;

namespace Apollinare.API.Hubs;

/// <summary>
/// Hub SignalR generico per la collaborazione in tempo reale su qualsiasi entità/pagina
/// Supporta: presenza utenti, cursori, selezioni, modifiche dati, chat
/// </summary>
public class CollaborationHub : Hub
{
    // Stato in-memory: roomKey -> lista collaboratori
    // roomKey format: "{entityType}:{entityId}" es. "report-template:123", "evento:456", "page:dashboard"
    private static readonly ConcurrentDictionary<string, ConcurrentDictionary<string, CollaboratorInfo>> _rooms = new();

    // Mapping connectionId -> roomKey per cleanup su disconnect
    private static readonly ConcurrentDictionary<string, string> _connectionRooms = new();

    #region Room Management

    /// <summary>
    /// Un utente entra in una room (pagina/entità)
    /// </summary>
    public async Task JoinRoom(string roomKey, string userName, string userColor, object? metadata = null)
    {
        try
        {
            var connectionId = Context.ConnectionId;

            // Validate inputs
            if (string.IsNullOrEmpty(roomKey))
            {
                Console.WriteLine($"[Collaboration] JoinRoom failed: roomKey is null or empty");
                return;
            }

            // Aggiungi alla room SignalR
            await Groups.AddToGroupAsync(connectionId, roomKey);

            // Crea info collaboratore
            var collaborator = new CollaboratorInfo
            {
                ConnectionId = connectionId,
                UserName = userName ?? "Anonymous",
                Color = userColor ?? "#888888",
                JoinedAt = DateTime.UtcNow,
                SelectedItemId = null,
                CursorX = null,
                CursorY = null,
                CurrentViewId = null,
                Metadata = metadata,
                IsActive = true,
                LastActivityAt = DateTime.UtcNow
            };

            // Aggiungi al dizionario collaboratori della room
            var roomCollabs = _rooms.GetOrAdd(roomKey, _ => new ConcurrentDictionary<string, CollaboratorInfo>());
            roomCollabs[connectionId] = collaborator;

            // Traccia mapping connection -> room
            _connectionRooms[connectionId] = roomKey;

            // Notifica tutti gli altri nella room
            await Clients.OthersInGroup(roomKey).SendAsync("UserJoined", collaborator);

            // Invia al nuovo utente la lista dei collaboratori già presenti
            var existingCollaborators = roomCollabs.Values
                .Where(c => c.ConnectionId != connectionId)
                .ToList();
            await Clients.Caller.SendAsync("RoomState", new RoomStateMessage
            {
                RoomKey = roomKey,
                Collaborators = existingCollaborators,
                JoinedAt = DateTime.UtcNow
            });

            Console.WriteLine($"[Collaboration] {userName} joined room {roomKey}");
        }
        catch (Exception ex)
        {
            Console.WriteLine($"[Collaboration] ERROR in JoinRoom: {ex.Message}");
            Console.WriteLine($"[Collaboration] Stack: {ex.StackTrace}");
            throw; // Re-throw to let SignalR handle it
        }
    }

    /// <summary>
    /// Un utente esce da una room
    /// </summary>
    public async Task LeaveRoom(string roomKey)
    {
        var connectionId = Context.ConnectionId;
        await RemoveFromRoom(connectionId, roomKey);
    }

    /// <summary>
    /// Cambia room (es. navigazione tra pagine)
    /// </summary>
    public async Task SwitchRoom(string newRoomKey, string userName, string userColor, object? metadata = null)
    {
        var connectionId = Context.ConnectionId;

        // Esci dalla room corrente se presente
        if (_connectionRooms.TryGetValue(connectionId, out var currentRoom) && currentRoom != newRoomKey)
        {
            await RemoveFromRoom(connectionId, currentRoom);
        }

        // Entra nella nuova room
        await JoinRoom(newRoomKey, userName, userColor, metadata);
    }

    #endregion

    #region Data Sync

    /// <summary>
    /// Notifica modifica di un oggetto/campo
    /// </summary>
    public async Task DataChanged(string roomKey, DataChangeMessage change)
    {
        change.SenderConnectionId = Context.ConnectionId;
        change.Timestamp = DateTime.UtcNow;

        UpdateUserActivity(Context.ConnectionId);

        await Clients.OthersInGroup(roomKey).SendAsync("DataChanged", change);
    }

    /// <summary>
    /// Notifica creazione di un nuovo oggetto
    /// </summary>
    public async Task ItemCreated(string roomKey, ItemCreatedMessage message)
    {
        message.SenderConnectionId = Context.ConnectionId;
        message.Timestamp = DateTime.UtcNow;

        UpdateUserActivity(Context.ConnectionId);

        await Clients.OthersInGroup(roomKey).SendAsync("ItemCreated", message);
    }

    /// <summary>
    /// Notifica eliminazione di un oggetto
    /// </summary>
    public async Task ItemDeleted(string roomKey, ItemDeletedMessage message)
    {
        message.SenderConnectionId = Context.ConnectionId;
        message.Timestamp = DateTime.UtcNow;

        UpdateUserActivity(Context.ConnectionId);

        await Clients.OthersInGroup(roomKey).SendAsync("ItemDeleted", message);
    }

    /// <summary>
    /// Notifica operazione batch (es. riordino, bulk update)
    /// </summary>
    public async Task BatchOperation(string roomKey, BatchOperationMessage message)
    {
        message.SenderConnectionId = Context.ConnectionId;
        message.Timestamp = DateTime.UtcNow;

        UpdateUserActivity(Context.ConnectionId);

        await Clients.OthersInGroup(roomKey).SendAsync("BatchOperation", message);
    }

    #endregion

    #region Presence & Awareness

    /// <summary>
    /// Notifica cambio selezione (quale elemento sta modificando l'utente)
    /// </summary>
    public async Task SelectionChanged(string roomKey, string? itemId)
    {
        var connectionId = Context.ConnectionId;

        // Aggiorna stato locale
        UpdateCollaboratorState(roomKey, connectionId, c => c.SelectedItemId = itemId);
        UpdateUserActivity(connectionId);

        await Clients.OthersInGroup(roomKey).SendAsync("SelectionChanged", new SelectionChangedMessage
        {
            ConnectionId = connectionId,
            ItemId = itemId,
            Timestamp = DateTime.UtcNow
        });
    }

    /// <summary>
    /// Notifica movimento cursore
    /// </summary>
    public async Task CursorMoved(string roomKey, float x, float y, string? viewId = null)
    {
        var connectionId = Context.ConnectionId;

        // Aggiorna stato locale
        UpdateCollaboratorState(roomKey, connectionId, c =>
        {
            c.CursorX = x;
            c.CursorY = y;
            c.CurrentViewId = viewId;
        });

        // Non aggiorniamo LastActivityAt per ogni movimento cursore (troppo frequente)

        await Clients.OthersInGroup(roomKey).SendAsync("CursorMoved", new CursorMovedMessage
        {
            ConnectionId = connectionId,
            X = x,
            Y = y,
            ViewId = viewId,
            Timestamp = DateTime.UtcNow
        });
    }

    /// <summary>
    /// Notifica cambio vista/sezione (es. cambio tab, pagina, scroll)
    /// </summary>
    public async Task ViewChanged(string roomKey, string viewId)
    {
        var connectionId = Context.ConnectionId;

        UpdateCollaboratorState(roomKey, connectionId, c => c.CurrentViewId = viewId);
        UpdateUserActivity(connectionId);

        await Clients.OthersInGroup(roomKey).SendAsync("ViewChanged", new ViewChangedMessage
        {
            ConnectionId = connectionId,
            ViewId = viewId,
            Timestamp = DateTime.UtcNow
        });
    }

    /// <summary>
    /// Indica che l'utente sta digitando/modificando
    /// </summary>
    public async Task UserTyping(string roomKey, string? itemId, bool isTyping)
    {
        var connectionId = Context.ConnectionId;
        UpdateUserActivity(connectionId);

        await Clients.OthersInGroup(roomKey).SendAsync("UserTyping", new UserTypingMessage
        {
            ConnectionId = connectionId,
            ItemId = itemId,
            IsTyping = isTyping,
            Timestamp = DateTime.UtcNow
        });
    }

    #endregion

    #region Sync & Recovery

    /// <summary>
    /// Richiesta sync completo (per nuovo utente o dopo reconnect)
    /// </summary>
    public async Task RequestSync(string roomKey)
    {
        // Chiedi al primo collaboratore (host) di inviare lo stato completo
        if (_rooms.TryGetValue(roomKey, out var collabs))
        {
            var host = collabs.Values
                .Where(c => c.ConnectionId != Context.ConnectionId && c.IsActive)
                .OrderBy(c => c.JoinedAt)
                .FirstOrDefault();

            if (host != null)
            {
                await Clients.Client(host.ConnectionId).SendAsync("SyncRequested", new SyncRequestMessage
                {
                    RequesterId = Context.ConnectionId,
                    RoomKey = roomKey,
                    Timestamp = DateTime.UtcNow
                });
            }
        }
    }

    /// <summary>
    /// Invio sync completo a un utente specifico
    /// </summary>
    public async Task SendSync(string roomKey, string targetConnectionId, string dataJson)
    {
        await Clients.Client(targetConnectionId).SendAsync("SyncReceived", new SyncDataMessage
        {
            RoomKey = roomKey,
            DataJson = dataJson,
            SenderConnectionId = Context.ConnectionId,
            Timestamp = DateTime.UtcNow
        });
    }

    /// <summary>
    /// Notifica salvataggio dati
    /// </summary>
    public async Task DataSaved(string roomKey, string savedBy)
    {
        Console.WriteLine($"[Collaboration] DataSaved received from {savedBy} for room {roomKey}");
        await Clients.OthersInGroup(roomKey).SendAsync("DataSaved", new DataSavedMessage
        {
            SavedBy = savedBy,
            RoomKey = roomKey,
            Timestamp = DateTime.UtcNow
        });
        Console.WriteLine($"[Collaboration] DataSaved sent to others in room {roomKey}");
    }

    /// <summary>
    /// Sincronizza l'intero template con tutti gli altri collaboratori nella room
    /// Usato per sync efficienti - invia il template completo invece di richiedere reload dal server
    /// Supporta compressione opzionale per template grandi
    /// </summary>
    public async Task BroadcastTemplateSync(string roomKey, string templateJson, int version, bool compressed = false)
    {
        var senderId = Context.ConnectionId;
        Console.WriteLine($"[Collaboration] BroadcastTemplateSync from {senderId} for room {roomKey}, version {version}, size: {templateJson.Length} bytes, compressed: {compressed}");

        UpdateUserActivity(senderId);

        await Clients.OthersInGroup(roomKey).SendAsync("TemplateSync", new TemplateSyncMessage
        {
            TemplateJson = templateJson,
            SenderConnectionId = senderId,
            Version = version,
            Compressed = compressed,
            Timestamp = DateTime.UtcNow
        });

        Console.WriteLine($"[Collaboration] TemplateSync broadcast complete for room {roomKey}");
    }

    #endregion

    #region Lifecycle

    public override async Task OnConnectedAsync()
    {
        Console.WriteLine($"[Collaboration] Client connected: {Context.ConnectionId}");
        await base.OnConnectedAsync();
        Console.WriteLine($"[Collaboration] Client connected (done): {Context.ConnectionId}");
    }

    public override async Task OnDisconnectedAsync(Exception? exception)
    {
        var connectionId = Context.ConnectionId;

        if (exception != null)
        {
            Console.WriteLine($"[Collaboration] Client disconnected with ERROR: {connectionId} - {exception.Message}");
        }
        else
        {
            Console.WriteLine($"[Collaboration] Client disconnected: {connectionId}");
        }

        // Rimuovi da eventuali room
        if (_connectionRooms.TryRemove(connectionId, out var roomKey))
        {
            await RemoveFromRoom(connectionId, roomKey);
        }

        await base.OnDisconnectedAsync(exception);
    }

    #endregion

    #region Helpers

    private async Task RemoveFromRoom(string connectionId, string roomKey)
    {
        // Rimuovi dalla room SignalR
        await Groups.RemoveFromGroupAsync(connectionId, roomKey);

        // Rimuovi dal dizionario
        if (_rooms.TryGetValue(roomKey, out var collabs))
        {
            if (collabs.TryRemove(connectionId, out var removedCollab))
            {
                // Notifica gli altri
                await Clients.OthersInGroup(roomKey).SendAsync("UserLeft", new UserLeftMessage
                {
                    ConnectionId = connectionId,
                    UserName = removedCollab.UserName,
                    Timestamp = DateTime.UtcNow
                });

                Console.WriteLine($"[Collaboration] {removedCollab.UserName} left room {roomKey}");
            }

            // Pulisci dizionario se vuoto
            if (collabs.IsEmpty)
            {
                _rooms.TryRemove(roomKey, out _);
            }
        }

        _connectionRooms.TryRemove(connectionId, out _);
    }

    private void UpdateCollaboratorState(string roomKey, string connectionId, Action<CollaboratorInfo> update)
    {
        if (_rooms.TryGetValue(roomKey, out var collabs) &&
            collabs.TryGetValue(connectionId, out var collaborator))
        {
            update(collaborator);
        }
    }

    private void UpdateUserActivity(string connectionId)
    {
        if (_connectionRooms.TryGetValue(connectionId, out var roomKey) &&
            _rooms.TryGetValue(roomKey, out var collabs) &&
            collabs.TryGetValue(connectionId, out var collaborator))
        {
            collaborator.LastActivityAt = DateTime.UtcNow;
            collaborator.IsActive = true;
        }
    }

    #endregion
}

#region DTOs / Models

public class CollaboratorInfo
{
    public string ConnectionId { get; set; } = string.Empty;
    public string UserName { get; set; } = string.Empty;
    public string Color { get; set; } = string.Empty;
    public DateTime JoinedAt { get; set; }
    public string? SelectedItemId { get; set; }
    public float? CursorX { get; set; }
    public float? CursorY { get; set; }
    public string? CurrentViewId { get; set; }
    public object? Metadata { get; set; }
    public bool IsActive { get; set; }
    public DateTime LastActivityAt { get; set; }
}

public class RoomStateMessage
{
    public string RoomKey { get; set; } = string.Empty;
    public List<CollaboratorInfo> Collaborators { get; set; } = new();
    public DateTime JoinedAt { get; set; }
}

public class DataChangeMessage
{
    public string ItemId { get; set; } = string.Empty;
    public string ItemType { get; set; } = string.Empty;
    public string ChangeType { get; set; } = string.Empty; // "update", "partial", "field"
    public string? FieldPath { get; set; } // es. "position.x", "style.color"
    public object? NewValue { get; set; }
    public object? OldValue { get; set; }
    public string? SenderConnectionId { get; set; }
    public DateTime Timestamp { get; set; }
}

public class ItemCreatedMessage
{
    public string ItemId { get; set; } = string.Empty;
    public string ItemType { get; set; } = string.Empty;
    public object Item { get; set; } = null!;
    public string? ParentId { get; set; }
    public int? Index { get; set; }
    public string? SenderConnectionId { get; set; }
    public DateTime Timestamp { get; set; }
}

public class ItemDeletedMessage
{
    public string ItemId { get; set; } = string.Empty;
    public string ItemType { get; set; } = string.Empty;
    public string? SenderConnectionId { get; set; }
    public DateTime Timestamp { get; set; }
}

public class BatchOperationMessage
{
    public string OperationType { get; set; } = string.Empty; // "reorder", "bulk-update", "bulk-delete"
    public string ItemType { get; set; } = string.Empty;
    public object Data { get; set; } = null!;
    public string? SenderConnectionId { get; set; }
    public DateTime Timestamp { get; set; }
}

public class SelectionChangedMessage
{
    public string ConnectionId { get; set; } = string.Empty;
    public string? ItemId { get; set; }
    public DateTime Timestamp { get; set; }
}

public class CursorMovedMessage
{
    public string ConnectionId { get; set; } = string.Empty;
    public float X { get; set; }
    public float Y { get; set; }
    public string? ViewId { get; set; }
    public DateTime Timestamp { get; set; }
}

public class ViewChangedMessage
{
    public string ConnectionId { get; set; } = string.Empty;
    public string ViewId { get; set; } = string.Empty;
    public DateTime Timestamp { get; set; }
}

public class UserTypingMessage
{
    public string ConnectionId { get; set; } = string.Empty;
    public string? ItemId { get; set; }
    public bool IsTyping { get; set; }
    public DateTime Timestamp { get; set; }
}

public class SyncRequestMessage
{
    public string RequesterId { get; set; } = string.Empty;
    public string RoomKey { get; set; } = string.Empty;
    public DateTime Timestamp { get; set; }
}

public class SyncDataMessage
{
    public string RoomKey { get; set; } = string.Empty;
    public string DataJson { get; set; } = string.Empty;
    public string SenderConnectionId { get; set; } = string.Empty;
    public DateTime Timestamp { get; set; }
}

public class DataSavedMessage
{
    public string SavedBy { get; set; } = string.Empty;
    public string RoomKey { get; set; } = string.Empty;
    public DateTime Timestamp { get; set; }
}

/// <summary>
/// Message for efficient template sync - sends full template JSON (compressed optional)
/// Used for initial sync and full resync requests
/// </summary>
public class TemplateSyncMessage
{
    public string TemplateJson { get; set; } = string.Empty;
    public string SenderConnectionId { get; set; } = string.Empty;
    public int Version { get; set; } // Incrementing version number for conflict detection
    public bool Compressed { get; set; } // Whether TemplateJson is base64-encoded gzip
    public DateTime Timestamp { get; set; }
}

public class UserLeftMessage
{
    public string ConnectionId { get; set; } = string.Empty;
    public string UserName { get; set; } = string.Empty;
    public DateTime Timestamp { get; set; }
}

#endregion
