using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using Serilog;

namespace ZB.MOM.WW.LmxProxy.Host.Sessions
{
    /// <summary>
    ///     Tracks active client sessions in memory.
    ///     Thread-safe via ConcurrentDictionary.
    /// </summary>
    public sealed class SessionManager : IDisposable
    {
        private static readonly ILogger Log = Serilog.Log.ForContext<SessionManager>();

        private readonly ConcurrentDictionary<string, SessionInfo> _sessions
            = new ConcurrentDictionary<string, SessionInfo>(StringComparer.OrdinalIgnoreCase);

        private readonly Timer? _scavengingTimer;
        private readonly TimeSpan _inactivityTimeout;
        private Action<string>? _onSessionScavenged;

        /// <summary>
        ///     Creates a SessionManager with optional inactivity scavenging.
        /// </summary>
        /// <param name="inactivityTimeoutMinutes">
        ///     Sessions inactive for this many minutes are automatically terminated.
        ///     Set to 0 to disable scavenging.
        /// </param>
        public SessionManager(int inactivityTimeoutMinutes = 5)
        {
            _inactivityTimeout = TimeSpan.FromMinutes(inactivityTimeoutMinutes);

            if (inactivityTimeoutMinutes > 0)
            {
                // Check every 60 seconds
                _scavengingTimer = new Timer(ScavengeInactiveSessions, null,
                    TimeSpan.FromSeconds(60), TimeSpan.FromSeconds(60));
            }
        }

        /// <summary>
        ///     Register a callback invoked when a session is scavenged due to inactivity.
        ///     The callback receives the session ID.
        /// </summary>
        public void OnSessionScavenged(Action<string> callback)
        {
            _onSessionScavenged = callback;
        }

        /// <summary>Gets the count of active sessions.</summary>
        public int ActiveSessionCount => _sessions.Count;

        /// <summary>
        ///     Creates a new session.
        ///     Returns the 32-character hex GUID session ID.
        /// </summary>
        public string CreateSession(string clientId, string apiKey)
        {
            var sessionId = Guid.NewGuid().ToString("N"); // 32-char lowercase hex, no hyphens
            var sessionInfo = new SessionInfo(sessionId, clientId, apiKey);
            _sessions[sessionId] = sessionInfo;

            Log.Information("Session created: {SessionId} for client {ClientId}", sessionId, clientId);
            return sessionId;
        }

        /// <summary>
        ///     Validates a session ID. Updates LastActivity on success.
        ///     Returns true if the session exists.
        /// </summary>
        public bool ValidateSession(string sessionId)
        {
            if (_sessions.TryGetValue(sessionId, out var session))
            {
                session.TouchLastActivity();
                return true;
            }
            return false;
        }

        /// <summary>
        ///     Terminates a session. Returns true if the session existed.
        /// </summary>
        public bool TerminateSession(string sessionId)
        {
            if (_sessions.TryRemove(sessionId, out _))
            {
                Log.Information("Session terminated: {SessionId}", sessionId);
                return true;
            }
            return false;
        }

        /// <summary>Gets session info by ID, or null if not found.</summary>
        public SessionInfo? GetSession(string sessionId)
        {
            _sessions.TryGetValue(sessionId, out var session);
            return session;
        }

        /// <summary>Gets a snapshot of all active sessions.</summary>
        public IReadOnlyList<SessionInfo> GetAllSessions()
        {
            return _sessions.Values.ToList().AsReadOnly();
        }

        /// <summary>
        ///     Scavenges sessions that have been inactive for longer than the timeout.
        /// </summary>
        private void ScavengeInactiveSessions(object? state)
        {
            if (_inactivityTimeout <= TimeSpan.Zero) return;

            var cutoff = DateTime.UtcNow - _inactivityTimeout;
            var expired = _sessions.Where(kvp => kvp.Value.LastActivity < cutoff).ToList();

            foreach (var kvp in expired)
            {
                if (_sessions.TryRemove(kvp.Key, out _))
                {
                    Log.Information("Session {SessionId} scavenged (inactive since {LastActivity})",
                        kvp.Key, kvp.Value.LastActivity);

                    try
                    {
                        _onSessionScavenged?.Invoke(kvp.Key);
                    }
                    catch (Exception ex)
                    {
                        Log.Warning(ex, "Error in session scavenge callback for {SessionId}", kvp.Key);
                    }
                }
            }
        }

        public void Dispose()
        {
            _scavengingTimer?.Dispose();
            _sessions.Clear();
        }
    }

    /// <summary>
    ///     Information about an active client session.
    /// </summary>
    public class SessionInfo
    {
        public SessionInfo(string sessionId, string clientId, string apiKey)
        {
            SessionId = sessionId;
            ClientId = clientId;
            ApiKey = apiKey;
            ConnectedAt = DateTime.UtcNow;
            LastActivity = DateTime.UtcNow;
        }

        public string SessionId { get; }
        public string ClientId { get; }
        public string ApiKey { get; }
        public DateTime ConnectedAt { get; }
        public DateTime LastActivity { get; private set; }
        public long ConnectedSinceUtcTicks => ConnectedAt.Ticks;

        /// <summary>Updates the last activity timestamp to now.</summary>
        public void TouchLastActivity()
        {
            LastActivity = DateTime.UtcNow;
        }
    }
}
