using Apollinare.Domain.Entities;
using Apollinare.Infrastructure.Data;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Caching.Memory;

namespace Apollinare.API.Services;

/// <summary>
/// Service per la gestione dei moduli applicativi e delle relative subscription
/// </summary>
public class ModuleService
{
    private readonly AppollinareDbContext _context;
    private readonly IMemoryCache _cache;
    private readonly ILogger<ModuleService> _logger;

    private const string MODULES_CACHE_KEY = "modules_all";
    private const string ACTIVE_MODULES_CACHE_KEY = "modules_active";
    private static readonly TimeSpan CacheDuration = TimeSpan.FromMinutes(5);

    public ModuleService(
        AppollinareDbContext context,
        IMemoryCache cache,
        ILogger<ModuleService> logger)
    {
        _context = context;
        _cache = cache;
        _logger = logger;
    }

    /// <summary>
    /// Ottiene tutti i moduli con lo stato della subscription
    /// </summary>
    public async Task<List<AppModule>> GetAllModulesAsync()
    {
        return await _cache.GetOrCreateAsync(MODULES_CACHE_KEY, async entry =>
        {
            entry.AbsoluteExpirationRelativeToNow = CacheDuration;
            return await _context.AppModules
                .Include(m => m.Subscription)
                .Where(m => m.IsAvailable)
                .OrderBy(m => m.SortOrder)
                .ThenBy(m => m.Name)
                .ToListAsync();
        }) ?? new List<AppModule>();
    }

    /// <summary>
    /// Ottiene solo i moduli attivi (per la costruzione del menu)
    /// </summary>
    public async Task<List<AppModule>> GetActiveModulesAsync()
    {
        return await _cache.GetOrCreateAsync(ACTIVE_MODULES_CACHE_KEY, async entry =>
        {
            entry.AbsoluteExpirationRelativeToNow = CacheDuration;
            var modules = await _context.AppModules
                .Include(m => m.Subscription)
                .Where(m => m.IsAvailable)
                .OrderBy(m => m.SortOrder)
                .ThenBy(m => m.Name)
                .ToListAsync();

            return modules.Where(m => m.IsCore || (m.Subscription?.IsValid() ?? false)).ToList();
        }) ?? new List<AppModule>();
    }

    /// <summary>
    /// Ottiene un modulo specifico per codice
    /// </summary>
    public async Task<AppModule?> GetModuleByCodeAsync(string code)
    {
        return await _context.AppModules
            .Include(m => m.Subscription)
            .FirstOrDefaultAsync(m => m.Code == code);
    }

    /// <summary>
    /// Verifica se un modulo è attualmente abilitato
    /// </summary>
    public async Task<bool> IsModuleEnabledAsync(string code)
    {
        var module = await GetModuleByCodeAsync(code);
        if (module == null)
            return false;

        // I moduli core sono sempre abilitati
        if (module.IsCore)
            return true;

        return module.Subscription?.IsValid() ?? false;
    }

    /// <summary>
    /// Verifica se un modulo ha una subscription valida (non scaduta)
    /// </summary>
    public async Task<bool> HasValidSubscriptionAsync(string code)
    {
        var module = await GetModuleByCodeAsync(code);
        return module?.Subscription?.IsValid() ?? false;
    }

    /// <summary>
    /// Attiva un modulo creando o aggiornando la subscription
    /// </summary>
    public async Task<ModuleSubscription> EnableModuleAsync(
        string code,
        SubscriptionType subscriptionType,
        DateTime? startDate = null,
        DateTime? endDate = null,
        bool autoRenew = false,
        decimal? paidPrice = null,
        string? notes = null)
    {
        var module = await _context.AppModules
            .Include(m => m.Subscription)
            .FirstOrDefaultAsync(m => m.Code == code);

        if (module == null)
            throw new ArgumentException($"Modulo con codice '{code}' non trovato");

        if (module.IsCore)
            throw new InvalidOperationException("I moduli core non possono essere attivati/disattivati manualmente");

        // Verifica dipendenze
        var missingDeps = await CheckDependenciesAsync(module);
        if (missingDeps.Any())
            throw new InvalidOperationException(
                $"Il modulo richiede i seguenti moduli attivi: {string.Join(", ", missingDeps)}");

        var now = DateTime.UtcNow;
        var effectiveStartDate = startDate ?? now;

        // Calcola data fine se non specificata
        DateTime? effectiveEndDate = endDate;
        if (!effectiveEndDate.HasValue && subscriptionType != SubscriptionType.None)
        {
            effectiveEndDate = subscriptionType switch
            {
                SubscriptionType.Monthly => effectiveStartDate.AddMonths(1),
                SubscriptionType.Annual => effectiveStartDate.AddYears(1),
                _ => null
            };
        }

        if (module.Subscription == null)
        {
            // Crea nuova subscription
            module.Subscription = new ModuleSubscription
            {
                ModuleId = module.Id,
                IsEnabled = true,
                SubscriptionType = subscriptionType,
                StartDate = effectiveStartDate,
                EndDate = effectiveEndDate,
                AutoRenew = autoRenew,
                PaidPrice = paidPrice ?? module.BasePrice,
                Notes = notes,
                CreatedAt = now,
                UpdatedAt = now
            };
            _context.ModuleSubscriptions.Add(module.Subscription);
        }
        else
        {
            // Aggiorna subscription esistente
            module.Subscription.IsEnabled = true;
            module.Subscription.SubscriptionType = subscriptionType;
            module.Subscription.StartDate = effectiveStartDate;
            module.Subscription.EndDate = effectiveEndDate;
            module.Subscription.AutoRenew = autoRenew;
            module.Subscription.PaidPrice = paidPrice ?? module.Subscription.PaidPrice ?? module.BasePrice;
            if (notes != null) module.Subscription.Notes = notes;
            module.Subscription.UpdatedAt = now;
        }

        await _context.SaveChangesAsync();
        InvalidateCache();

        _logger.LogInformation(
            "Modulo {ModuleCode} attivato con subscription {Type} fino a {EndDate}",
            code, subscriptionType, effectiveEndDate);

        return module.Subscription;
    }

    /// <summary>
    /// Disattiva un modulo (mantiene i dati ma rimuove l'accesso)
    /// </summary>
    public async Task DisableModuleAsync(string code)
    {
        var module = await _context.AppModules
            .Include(m => m.Subscription)
            .FirstOrDefaultAsync(m => m.Code == code);

        if (module == null)
            throw new ArgumentException($"Modulo con codice '{code}' non trovato");

        if (module.IsCore)
            throw new InvalidOperationException("I moduli core non possono essere disattivati");

        // Verifica se altri moduli dipendono da questo
        var dependentModules = await GetDependentModulesAsync(code);
        var activeDependents = dependentModules.Where(m => m.Subscription?.IsValid() ?? false).ToList();
        if (activeDependents.Any())
            throw new InvalidOperationException(
                $"I seguenti moduli attivi dipendono da questo modulo: {string.Join(", ", activeDependents.Select(m => m.Name))}");

        if (module.Subscription != null)
        {
            module.Subscription.IsEnabled = false;
            module.Subscription.UpdatedAt = DateTime.UtcNow;
            await _context.SaveChangesAsync();
        }

        InvalidateCache();
        _logger.LogInformation("Modulo {ModuleCode} disattivato", code);
    }

    /// <summary>
    /// Aggiorna i dettagli della subscription
    /// </summary>
    public async Task<ModuleSubscription> UpdateSubscriptionAsync(
        string code,
        SubscriptionType? subscriptionType = null,
        DateTime? endDate = null,
        bool? autoRenew = null,
        string? notes = null)
    {
        var module = await _context.AppModules
            .Include(m => m.Subscription)
            .FirstOrDefaultAsync(m => m.Code == code);

        if (module == null)
            throw new ArgumentException($"Modulo con codice '{code}' non trovato");

        if (module.Subscription == null)
            throw new InvalidOperationException($"Il modulo '{code}' non ha una subscription attiva");

        if (subscriptionType.HasValue)
            module.Subscription.SubscriptionType = subscriptionType.Value;
        if (endDate.HasValue)
            module.Subscription.EndDate = endDate.Value;
        if (autoRenew.HasValue)
            module.Subscription.AutoRenew = autoRenew.Value;
        if (notes != null)
            module.Subscription.Notes = notes;

        module.Subscription.UpdatedAt = DateTime.UtcNow;

        await _context.SaveChangesAsync();
        InvalidateCache();

        return module.Subscription;
    }

    /// <summary>
    /// Rinnova una subscription esistente
    /// </summary>
    public async Task<ModuleSubscription> RenewSubscriptionAsync(string code, decimal? paidPrice = null)
    {
        var module = await _context.AppModules
            .Include(m => m.Subscription)
            .FirstOrDefaultAsync(m => m.Code == code);

        if (module == null)
            throw new ArgumentException($"Modulo con codice '{code}' non trovato");

        if (module.Subscription == null)
            throw new InvalidOperationException($"Il modulo '{code}' non ha una subscription da rinnovare");

        var now = DateTime.UtcNow;
        var currentEnd = module.Subscription.EndDate ?? now;
        var newStart = currentEnd > now ? currentEnd : now;

        var newEnd = module.Subscription.SubscriptionType switch
        {
            SubscriptionType.Monthly => newStart.AddMonths(1),
            SubscriptionType.Annual => newStart.AddYears(1),
            _ => newStart.AddYears(1) // Default to annual
        };

        module.Subscription.StartDate = newStart;
        module.Subscription.EndDate = newEnd;
        module.Subscription.LastRenewalDate = now;
        module.Subscription.IsEnabled = true;
        module.Subscription.PaidPrice = paidPrice ?? module.Subscription.PaidPrice;
        module.Subscription.UpdatedAt = now;

        await _context.SaveChangesAsync();
        InvalidateCache();

        _logger.LogInformation(
            "Modulo {ModuleCode} rinnovato fino a {EndDate}",
            code, newEnd);

        return module.Subscription;
    }

    /// <summary>
    /// Ottiene tutte le subscription
    /// </summary>
    public async Task<List<ModuleSubscription>> GetAllSubscriptionsAsync()
    {
        return await _context.ModuleSubscriptions
            .Include(s => s.Module)
            .OrderBy(s => s.Module.SortOrder)
            .ToListAsync();
    }

    /// <summary>
    /// Verifica e disattiva i moduli con subscription scaduta (per job schedulato)
    /// </summary>
    public async Task<int> CheckExpiredSubscriptionsAsync()
    {
        var expiredSubscriptions = await _context.ModuleSubscriptions
            .Include(s => s.Module)
            .Where(s => s.IsEnabled &&
                       s.EndDate.HasValue &&
                       s.EndDate.Value < DateTime.UtcNow &&
                       !s.AutoRenew)
            .ToListAsync();

        foreach (var subscription in expiredSubscriptions)
        {
            subscription.IsEnabled = false;
            subscription.UpdatedAt = DateTime.UtcNow;
            _logger.LogWarning(
                "Modulo {ModuleCode} disattivato per scadenza subscription",
                subscription.Module.Code);
        }

        if (expiredSubscriptions.Any())
        {
            await _context.SaveChangesAsync();
            InvalidateCache();
        }

        return expiredSubscriptions.Count;
    }

    /// <summary>
    /// Ottiene i moduli in scadenza entro N giorni
    /// </summary>
    public async Task<List<AppModule>> GetExpiringModulesAsync(int daysThreshold = 30)
    {
        var thresholdDate = DateTime.UtcNow.AddDays(daysThreshold);

        return await _context.AppModules
            .Include(m => m.Subscription)
            .Where(m => m.Subscription != null &&
                       m.Subscription.IsEnabled &&
                       m.Subscription.EndDate.HasValue &&
                       m.Subscription.EndDate.Value <= thresholdDate &&
                       m.Subscription.EndDate.Value > DateTime.UtcNow)
            .OrderBy(m => m.Subscription!.EndDate)
            .ToListAsync();
    }

    /// <summary>
    /// Verifica le dipendenze mancanti per un modulo
    /// </summary>
    private async Task<List<string>> CheckDependenciesAsync(AppModule module)
    {
        var dependencies = module.GetDependencies().ToList();
        if (!dependencies.Any())
            return new List<string>();

        var missingDeps = new List<string>();

        foreach (var depCode in dependencies)
        {
            if (!await IsModuleEnabledAsync(depCode))
            {
                var depModule = await GetModuleByCodeAsync(depCode);
                missingDeps.Add(depModule?.Name ?? depCode);
            }
        }

        return missingDeps;
    }

    /// <summary>
    /// Ottiene i moduli che dipendono da un determinato modulo
    /// </summary>
    private async Task<List<AppModule>> GetDependentModulesAsync(string code)
    {
        var allModules = await GetAllModulesAsync();
        return allModules
            .Where(m => m.GetDependencies().Contains(code))
            .ToList();
    }

    /// <summary>
    /// Invalida la cache dei moduli
    /// </summary>
    public void InvalidateCache()
    {
        _cache.Remove(MODULES_CACHE_KEY);
        _cache.Remove(ACTIVE_MODULES_CACHE_KEY);
        _logger.LogDebug("Cache moduli invalidata");
    }

    /// <summary>
    /// Inizializza i moduli di default se non esistono
    /// </summary>
    public async Task SeedDefaultModulesAsync()
    {
        if (await _context.AppModules.AnyAsync())
            return;

        var defaultModules = new List<AppModule>
        {
            new AppModule
            {
                Code = "warehouse",
                Name = "Magazzino",
                Description = "Gestione inventario, movimenti di magazzino, giacenze e valorizzazione scorte",
                Icon = "Warehouse",
                BasePrice = 1200m,
                MonthlyMultiplier = 1.2m,
                SortOrder = 10,
                IsCore = false,
                RoutePath = "/warehouse",
                IsAvailable = true,
                CreatedAt = DateTime.UtcNow
            },
            new AppModule
            {
                Code = "purchases",
                Name = "Acquisti",
                Description = "Gestione ordini fornitori, DDT in entrata, fatture passive e analisi acquisti",
                Icon = "ShoppingCart",
                BasePrice = 1500m,
                MonthlyMultiplier = 1.2m,
                SortOrder = 20,
                IsCore = false,
                Dependencies = "warehouse",
                RoutePath = "/purchases",
                IsAvailable = true,
                CreatedAt = DateTime.UtcNow
            },
            new AppModule
            {
                Code = "sales",
                Name = "Vendite",
                Description = "Gestione ordini clienti, DDT in uscita, fatture attive e analisi vendite",
                Icon = "PointOfSale",
                BasePrice = 1500m,
                MonthlyMultiplier = 1.2m,
                SortOrder = 30,
                IsCore = false,
                Dependencies = "warehouse",
                RoutePath = "/sales",
                IsAvailable = true,
                CreatedAt = DateTime.UtcNow
            },
            new AppModule
            {
                Code = "production",
                Name = "Produzione",
                Description = "Cicli produttivi, distinte base, pianificazione MRP e controllo avanzamento",
                Icon = "Precision Manufacturing",
                BasePrice = 2500m,
                MonthlyMultiplier = 1.2m,
                SortOrder = 40,
                IsCore = false,
                Dependencies = "warehouse",
                RoutePath = "/production",
                IsAvailable = true,
                CreatedAt = DateTime.UtcNow
            },
            new AppModule
            {
                Code = "quality",
                Name = "Qualità",
                Description = "Controlli qualità, gestione non conformità, certificazioni e audit",
                Icon = "VerifiedUser",
                BasePrice = 1800m,
                MonthlyMultiplier = 1.2m,
                SortOrder = 50,
                IsCore = false,
                RoutePath = "/quality",
                IsAvailable = true,
                CreatedAt = DateTime.UtcNow
            }
        };

        _context.AppModules.AddRange(defaultModules);
        await _context.SaveChangesAsync();

        _logger.LogInformation("Seed {Count} moduli di default completato", defaultModules.Count);
    }
}
