using System;
using System.Collections.Concurrent;
using System.Linq;
using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
using LiteDB;
using Microsoft.Extensions.Caching.Distributed;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using TeleBot.Models;

namespace TeleBot.Services
{
    public interface ISessionManager
    {
        Task<UserSession> GetOrCreateSessionAsync(long telegramUserId);
        Task UpdateSessionAsync(UserSession session);
        Task DeleteSessionAsync(string sessionId);
        Task DeleteUserDataAsync(long telegramUserId);
        Task CleanupExpiredSessionsAsync();
        IEnumerable<UserSession> GetActiveSessions();
    }
    
    public class SessionManager : ISessionManager, IHostedService
    {
        private readonly IConfiguration _configuration;
        private readonly ILogger<SessionManager> _logger;
        private readonly IPrivacyService _privacyService;
        private readonly IDistributedCache? _cache;
        private readonly LiteDatabase? _database;
        private readonly ConcurrentDictionary<string, UserSession> _inMemorySessions;
        private readonly int _sessionTimeoutMinutes;
        private readonly bool _ephemeralByDefault;
        private readonly bool _useRedis;
        private readonly bool _useLiteDb;
        private Timer? _cleanupTimer;
        
        public SessionManager(
            IConfiguration configuration,
            ILogger<SessionManager> logger,
            IPrivacyService privacyService,
            IDistributedCache? cache = null)
        {
            _configuration = configuration;
            _logger = logger;
            _privacyService = privacyService;
            _cache = cache;
            _inMemorySessions = new ConcurrentDictionary<string, UserSession>();
            
            _sessionTimeoutMinutes = configuration.GetValue<int>("Privacy:SessionTimeoutMinutes", 30);
            _ephemeralByDefault = configuration.GetValue<bool>("Privacy:EphemeralByDefault", true);
            _useRedis = configuration.GetValue<bool>("Redis:Enabled", false) && cache != null;
            _useLiteDb = !_ephemeralByDefault && !string.IsNullOrEmpty(configuration["Database:ConnectionString"]);
            
            if (_useLiteDb)
            {
                var dbPath = configuration["Database:ConnectionString"] ?? "telebot.db";
                _database = new LiteDatabase(dbPath);
                
                // Ensure collection exists
                var sessions = _database.GetCollection<UserSession>("sessions");
                sessions.EnsureIndex(x => x.HashedUserId);
                sessions.EnsureIndex(x => x.ExpiresAt);
            }
        }
        
        public async Task<UserSession> GetOrCreateSessionAsync(long telegramUserId)
        {
            var hashedUserId = _privacyService.HashIdentifier(telegramUserId);
            
            // Try to get existing session
            UserSession? session = null;
            
            // Check in-memory first (fastest)
            session = _inMemorySessions.Values.FirstOrDefault(s => s.HashedUserId == hashedUserId && !s.IsExpired());
            
            // Check Redis if enabled
            if (session == null && _useRedis && _cache != null)
            {
                try
                {
                    var cacheKey = $"session:{hashedUserId}";
                    var cachedData = await _cache.GetStringAsync(cacheKey);
                    if (!string.IsNullOrEmpty(cachedData))
                    {
                        session = System.Text.Json.JsonSerializer.Deserialize<UserSession>(cachedData);
                        if (session != null && !session.IsExpired())
                        {
                            _inMemorySessions.TryAdd(session.Id, session);
                        }
                        else
                        {
                            session = null;
                        }
                    }
                }
                catch (Exception ex)
                {
                    _logger.LogError(ex, "Failed to retrieve session from Redis");
                }
            }
            
            // Check LiteDB if enabled
            if (session == null && _useLiteDb && _database != null)
            {
                try
                {
                    var sessions = _database.GetCollection<UserSession>("sessions");
                    session = sessions.FindOne(s => s.HashedUserId == hashedUserId && s.ExpiresAt > DateTime.UtcNow);
                    if (session != null)
                    {
                        _inMemorySessions.TryAdd(session.Id, session);
                    }
                }
                catch (Exception ex)
                {
                    _logger.LogError(ex, "Failed to retrieve session from LiteDB");
                }
            }
            
            // Create new session if none found
            if (session == null)
            {
                session = new UserSession
                {
                    HashedUserId = hashedUserId,
                    ExpiresAt = DateTime.UtcNow.AddMinutes(_sessionTimeoutMinutes),
                    IsEphemeral = _ephemeralByDefault,
                    Privacy = new PrivacySettings
                    {
                        UseEphemeralMode = _ephemeralByDefault,
                        DisableAnalytics = true,
                        EnableDisappearingMessages = _configuration.GetValue<bool>("Features:EnableDisappearingMessages", true)
                    }
                };
                
                _inMemorySessions.TryAdd(session.Id, session);
                await UpdateSessionAsync(session);
                
                _logger.LogInformation("Created new session for user");
            }
            else
            {
                session.UpdateActivity();
            }
            
            return session;
        }
        
        public async Task UpdateSessionAsync(UserSession session)
        {
            session.LastActivityAt = DateTime.UtcNow;
            _inMemorySessions.AddOrUpdate(session.Id, session, (key, old) => session);
            
            // Don't persist ephemeral sessions, except when in checkout flow
            if (session.IsEphemeral && session.State != SessionState.CheckoutFlow)
            {
                return;
            }
            
            // Save to Redis if enabled
            if (_useRedis && _cache != null)
            {
                try
                {
                    var cacheKey = $"session:{session.HashedUserId}";
                    var json = System.Text.Json.JsonSerializer.Serialize(session);
                    var options = new DistributedCacheEntryOptions
                    {
                        SlidingExpiration = TimeSpan.FromMinutes(_sessionTimeoutMinutes)
                    };
                    await _cache.SetStringAsync(cacheKey, json, options);
                }
                catch (Exception ex)
                {
                    _logger.LogError(ex, "Failed to update session in Redis");
                }
            }
            
            // Save to LiteDB if enabled
            if (_useLiteDb && _database != null)
            {
                try
                {
                    var sessions = _database.GetCollection<UserSession>("sessions");
                    sessions.Upsert(session);
                }
                catch (Exception ex)
                {
                    _logger.LogError(ex, "Failed to update session in LiteDB");
                }
            }
        }
        
        public async Task DeleteSessionAsync(string sessionId)
        {
            if (_inMemorySessions.TryRemove(sessionId, out var session))
            {
                // Remove from Redis
                if (_useRedis && _cache != null)
                {
                    try
                    {
                        var cacheKey = $"session:{session.HashedUserId}";
                        await _cache.RemoveAsync(cacheKey);
                    }
                    catch (Exception ex)
                    {
                        _logger.LogError(ex, "Failed to delete session from Redis");
                    }
                }
                
                // Remove from LiteDB
                if (_useLiteDb && _database != null)
                {
                    try
                    {
                        var sessions = _database.GetCollection<UserSession>("sessions");
                        sessions.Delete(session.Id);
                    }
                    catch (Exception ex)
                    {
                        _logger.LogError(ex, "Failed to delete session from LiteDB");
                    }
                }
            }
        }
        
        public async Task DeleteUserDataAsync(long telegramUserId)
        {
            var hashedUserId = _privacyService.HashIdentifier(telegramUserId);
            
            // Remove all sessions for this user
            var userSessions = _inMemorySessions.Values.Where(s => s.HashedUserId == hashedUserId).ToList();
            foreach (var session in userSessions)
            {
                await DeleteSessionAsync(session.Id);
            }
            
            _logger.LogInformation("Deleted all user data for privacy request");
        }
        
        public async Task CleanupExpiredSessionsAsync()
        {
            var expiredSessions = _inMemorySessions.Values.Where(s => s.IsExpired()).ToList();
            
            foreach (var session in expiredSessions)
            {
                await DeleteSessionAsync(session.Id);
            }
            
            if (expiredSessions.Any())
            {
                _logger.LogInformation($"Cleaned up {expiredSessions.Count} expired sessions");
            }
        }
        
        public IEnumerable<UserSession> GetActiveSessions()
        {
            return _inMemorySessions.Values.Where(s => !s.IsExpired());
        }
        
        public Task StartAsync(CancellationToken cancellationToken)
        {
            // Start cleanup timer
            _cleanupTimer = new Timer(
                async _ => await CleanupExpiredSessionsAsync(),
                null,
                TimeSpan.FromMinutes(5),
                TimeSpan.FromMinutes(5)
            );
            
            return Task.CompletedTask;
        }
        
        public Task StopAsync(CancellationToken cancellationToken)
        {
            _cleanupTimer?.Dispose();
            _database?.Dispose();
            return Task.CompletedTask;
        }
    }
}