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

using Apollinare.API.Hubs;

namespace Apollinare.API.Modules.Warehouse.Services;

/// <summary>
/// Implementazione del servizio principale per il modulo Magazzino
/// </summary>
public class WarehouseService : IWarehouseService
{
    private readonly AppollinareDbContext _context;
    private readonly IMemoryCache _cache;
    private readonly ILogger<WarehouseService> _logger;
    private readonly AutoCodeService _autoCodeService;
    private readonly DataNotificationService _notificationService;

    private const string WAREHOUSES_CACHE_KEY = "warehouse_locations";
    private const string CATEGORIES_CACHE_KEY = "warehouse_categories";
    private const string REASONS_CACHE_KEY = "movement_reasons";
    private static readonly TimeSpan CacheDuration = TimeSpan.FromMinutes(10);

    public WarehouseService(
        AppollinareDbContext context,
        IMemoryCache cache,
        ILogger<WarehouseService> logger,
        AutoCodeService autoCodeService,
        DataNotificationService notificationService)
    {
        _context = context;
        _cache = cache;
        _logger = logger;
        _autoCodeService = autoCodeService;
        _notificationService = notificationService;
    }

    #region Articoli

    public async Task<List<WarehouseArticle>> GetArticlesAsync(ArticleFilter? filter = null)
    {
        var query = _context.WarehouseArticles
            .Include(a => a.Category)
            .AsQueryable();

        if (filter != null)
        {
            if (!string.IsNullOrWhiteSpace(filter.SearchText))
            {
                var search = filter.SearchText.ToLower();
                query = query.Where(a =>
                    a.Code.ToLower().Contains(search) ||
                    a.Description.ToLower().Contains(search) ||
                    (a.Barcode != null && a.Barcode.Contains(search)));
            }

            if (filter.CategoryId.HasValue)
                query = query.Where(a => a.CategoryId == filter.CategoryId);

            if (filter.IsActive.HasValue)
                query = query.Where(a => a.IsActive == filter.IsActive);

            if (filter.IsBatchManaged.HasValue)
                query = query.Where(a => a.IsBatchManaged == filter.IsBatchManaged);

            if (filter.IsSerialManaged.HasValue)
                query = query.Where(a => a.IsSerialManaged == filter.IsSerialManaged);

            if (filter.StockManagement.HasValue)
                query = query.Where(a => a.StockManagement == filter.StockManagement);

            // Ordinamento
            query = filter.OrderBy?.ToLower() switch
            {
                "code" => filter.OrderDescending ? query.OrderByDescending(a => a.Code) : query.OrderBy(a => a.Code),
                "description" => filter.OrderDescending ? query.OrderByDescending(a => a.Description) : query.OrderBy(a => a.Description),
                "category" => filter.OrderDescending ? query.OrderByDescending(a => a.Category!.Name) : query.OrderBy(a => a.Category!.Name),
                _ => query.OrderBy(a => a.Code)
            };

            query = query.Skip(filter.Skip).Take(filter.Take);
        }
        else
        {
            query = query.OrderBy(a => a.Code).Take(100);
        }

        return await query.ToListAsync();
    }

    public async Task<WarehouseArticle?> GetArticleByIdAsync(int id)
    {
        return await _context.WarehouseArticles
            .Include(a => a.Category)
            .Include(a => a.Barcodes)
            .FirstOrDefaultAsync(a => a.Id == id);
    }

    public async Task<WarehouseArticle?> GetArticleByCodeAsync(string code)
    {
        return await _context.WarehouseArticles
            .Include(a => a.Category)
            .FirstOrDefaultAsync(a => a.Code == code);
    }

    public async Task<WarehouseArticle?> GetArticleByBarcodeAsync(string barcode)
    {
        // Cerca nel barcode principale
        var article = await _context.WarehouseArticles
            .Include(a => a.Category)
            .FirstOrDefaultAsync(a => a.Barcode == barcode);

        if (article != null)
            return article;

        // Cerca nei barcode aggiuntivi
        var articleBarcode = await _context.ArticleBarcodes
            .Include(b => b.Article)
            .ThenInclude(a => a!.Category)
            .FirstOrDefaultAsync(b => b.Barcode == barcode && b.IsActive);

        return articleBarcode?.Article;
    }

    public async Task<WarehouseArticle> CreateArticleAsync(WarehouseArticle article)
    {
        // Genera codice automaticamente se non specificato
        if (string.IsNullOrWhiteSpace(article.Code))
        {
            var generatedCode = await _autoCodeService.GenerateNextCodeAsync("warehouse_article");
            if (generatedCode != null)
                article.Code = generatedCode;
            else
                throw new InvalidOperationException("Impossibile generare codice automatico per l'articolo");
        }

        // Verifica unicità codice
        if (await _context.WarehouseArticles.AnyAsync(a => a.Code == article.Code))
            throw new InvalidOperationException($"Esiste già un articolo con codice '{article.Code}'");

        article.CreatedAt = DateTime.UtcNow;
        _context.WarehouseArticles.Add(article);
        await _context.SaveChangesAsync();

        _logger.LogInformation("Creato articolo {Code} - {Description}", article.Code, article.Description);
        await _notificationService.NotifyCreated("warehouse_article", article);
        return article;
    }

    public async Task<WarehouseArticle> UpdateArticleAsync(WarehouseArticle article)
    {
        var existing = await _context.WarehouseArticles.FindAsync(article.Id);
        if (existing == null)
            throw new ArgumentException($"Articolo con ID {article.Id} non trovato");

        // Verifica unicità codice se cambiato
        if (existing.Code != article.Code &&
            await _context.WarehouseArticles.AnyAsync(a => a.Code == article.Code && a.Id != article.Id))
            throw new InvalidOperationException($"Esiste già un articolo con codice '{article.Code}'");

        _context.Entry(existing).CurrentValues.SetValues(article);
        existing.UpdatedAt = DateTime.UtcNow;
        await _context.SaveChangesAsync();

        await _notificationService.NotifyUpdated("warehouse_article", existing);
        return existing;
    }

    public async Task DeleteArticleAsync(int id)
    {
        var article = await _context.WarehouseArticles.FindAsync(id);
        if (article == null)
            throw new ArgumentException($"Articolo con ID {id} non trovato");

        // Verifica che non ci siano giacenze
        if (await _context.StockLevels.AnyAsync(s => s.ArticleId == id && s.Quantity > 0))
            throw new InvalidOperationException("Impossibile eliminare un articolo con giacenze attive");

        // Verifica che non ci siano movimenti
        if (await _context.StockMovementLines.AnyAsync(l => l.ArticleId == id))
            throw new InvalidOperationException("Impossibile eliminare un articolo con movimenti storici");

        _context.WarehouseArticles.Remove(article);
        await _context.SaveChangesAsync();

        _logger.LogInformation("Eliminato articolo {Code}", article.Code);
        await _notificationService.NotifyDeleted("warehouse_article", id);
    }

    #endregion

    #region Categorie

    public async Task<List<WarehouseArticleCategory>> GetCategoriesAsync(bool includeInactive = false)
    {
        var cacheKey = $"{CATEGORIES_CACHE_KEY}_{includeInactive}";

        return await _cache.GetOrCreateAsync(cacheKey, async entry =>
        {
            entry.AbsoluteExpirationRelativeToNow = CacheDuration;

            var query = _context.WarehouseArticleCategories.AsQueryable();

            if (!includeInactive)
                query = query.Where(c => c.IsActive);

            return await query
                .OrderBy(c => c.SortOrder)
                .ThenBy(c => c.Name)
                .ToListAsync();
        }) ?? new List<WarehouseArticleCategory>();
    }

    public async Task<List<WarehouseArticleCategory>> GetCategoryTreeAsync()
    {
        var categories = await _context.WarehouseArticleCategories
            .Where(c => c.IsActive)
            .OrderBy(c => c.SortOrder)
            .ThenBy(c => c.Name)
            .ToListAsync();

        // Costruisci l'albero
        var rootCategories = categories.Where(c => c.ParentCategoryId == null).ToList();
        foreach (var root in rootCategories)
        {
            BuildCategoryTree(root, categories);
        }

        return rootCategories;
    }

    private void BuildCategoryTree(WarehouseArticleCategory parent, List<WarehouseArticleCategory> allCategories)
    {
        var children = allCategories.Where(c => c.ParentCategoryId == parent.Id).ToList();
        parent.ChildCategories = children;

        foreach (var child in children)
        {
            BuildCategoryTree(child, allCategories);
        }
    }

    public async Task<WarehouseArticleCategory?> GetCategoryByIdAsync(int id)
    {
        return await _context.WarehouseArticleCategories
            .Include(c => c.ParentCategory)
            .FirstOrDefaultAsync(c => c.Id == id);
    }

    public async Task<WarehouseArticleCategory> CreateCategoryAsync(WarehouseArticleCategory category)
    {
        // Genera codice automaticamente se non specificato
        if (string.IsNullOrWhiteSpace(category.Code))
        {
            var generatedCode = await _autoCodeService.GenerateNextCodeAsync("warehouse_category");
            if (generatedCode != null)
                category.Code = generatedCode;
            else
                throw new InvalidOperationException("Impossibile generare codice automatico per la categoria");
        }

        if (await _context.WarehouseArticleCategories.AnyAsync(c => c.Code == category.Code))
            throw new InvalidOperationException($"Esiste già una categoria con codice '{category.Code}'");

        // Calcola livello e path
        if (category.ParentCategoryId.HasValue)
        {
            var parent = await _context.WarehouseArticleCategories.FindAsync(category.ParentCategoryId);
            if (parent == null)
                throw new ArgumentException("Categoria padre non trovata");

            category.Level = parent.Level + 1;
            category.FullPath = string.IsNullOrEmpty(parent.FullPath)
                ? $"{parent.Code}.{category.Code}"
                : $"{parent.FullPath}.{category.Code}";
        }
        else
        {
            category.Level = 0;
            category.FullPath = category.Code;
        }

        category.CreatedAt = DateTime.UtcNow;
        _context.WarehouseArticleCategories.Add(category);
        await _context.SaveChangesAsync();

        InvalidateCategoriesCache();
        await _notificationService.NotifyCreated("warehouse_category", category);
        return category;
    }

    public async Task<WarehouseArticleCategory> UpdateCategoryAsync(WarehouseArticleCategory category)
    {
        var existing = await _context.WarehouseArticleCategories.FindAsync(category.Id);
        if (existing == null)
            throw new ArgumentException($"Categoria con ID {category.Id} non trovata");

        _context.Entry(existing).CurrentValues.SetValues(category);
        existing.UpdatedAt = DateTime.UtcNow;
        await _context.SaveChangesAsync();

        InvalidateCategoriesCache();
        await _notificationService.NotifyUpdated("warehouse_category", existing);
        return existing;
    }

    public async Task DeleteCategoryAsync(int id)
    {
        var category = await _context.WarehouseArticleCategories.FindAsync(id);
        if (category == null)
            throw new ArgumentException($"Categoria con ID {id} non trovata");

        // Verifica che non abbia figli
        if (await _context.WarehouseArticleCategories.AnyAsync(c => c.ParentCategoryId == id))
            throw new InvalidOperationException("Impossibile eliminare una categoria con sottocategorie");

        // Verifica che non abbia articoli
        if (await _context.WarehouseArticles.AnyAsync(a => a.CategoryId == id))
            throw new InvalidOperationException("Impossibile eliminare una categoria con articoli associati");

        _context.WarehouseArticleCategories.Remove(category);
        await _context.SaveChangesAsync();

        InvalidateCategoriesCache();
        await _notificationService.NotifyDeleted("warehouse_category", id);
    }

    private void InvalidateCategoriesCache()
    {
        _cache.Remove($"{CATEGORIES_CACHE_KEY}_true");
        _cache.Remove($"{CATEGORIES_CACHE_KEY}_false");
    }

    #endregion

    #region Magazzini

    public async Task<List<WarehouseLocation>> GetWarehousesAsync(bool includeInactive = false)
    {
        var cacheKey = $"{WAREHOUSES_CACHE_KEY}_{includeInactive}";

        return await _cache.GetOrCreateAsync(cacheKey, async entry =>
        {
            entry.AbsoluteExpirationRelativeToNow = CacheDuration;

            var query = _context.WarehouseLocations.AsQueryable();

            if (!includeInactive)
                query = query.Where(w => w.IsActive);

            return await query
                .OrderBy(w => w.SortOrder)
                .ThenBy(w => w.Name)
                .ToListAsync();
        }) ?? new List<WarehouseLocation>();
    }

    public async Task<WarehouseLocation?> GetWarehouseByIdAsync(int id)
    {
        return await _context.WarehouseLocations.FindAsync(id);
    }

    public async Task<WarehouseLocation?> GetDefaultWarehouseAsync()
    {
        return await _context.WarehouseLocations
            .FirstOrDefaultAsync(w => w.IsDefault && w.IsActive);
    }

    public async Task<WarehouseLocation> CreateWarehouseAsync(WarehouseLocation warehouse)
    {
        // Genera codice automaticamente se non specificato
        if (string.IsNullOrWhiteSpace(warehouse.Code))
        {
            var generatedCode = await _autoCodeService.GenerateNextCodeAsync("warehouse_location");
            if (generatedCode != null)
                warehouse.Code = generatedCode;
            else
                throw new InvalidOperationException("Impossibile generare codice automatico per il magazzino");
        }

        if (await _context.WarehouseLocations.AnyAsync(w => w.Code == warehouse.Code))
            throw new InvalidOperationException($"Esiste già un magazzino con codice '{warehouse.Code}'");

        // Se è impostato come default, rimuovi il flag dagli altri
        if (warehouse.IsDefault)
        {
            await _context.WarehouseLocations
                .Where(w => w.IsDefault)
                .ExecuteUpdateAsync(s => s.SetProperty(w => w.IsDefault, false));
        }

        warehouse.CreatedAt = DateTime.UtcNow;
        _context.WarehouseLocations.Add(warehouse);
        await _context.SaveChangesAsync();

        InvalidateWarehousesCache();
        _logger.LogInformation("Creato magazzino {Code} - {Name}", warehouse.Code, warehouse.Name);
        await _notificationService.NotifyCreated("warehouse_location", warehouse);
        return warehouse;
    }

    public async Task<WarehouseLocation> UpdateWarehouseAsync(WarehouseLocation warehouse)
    {
        var existing = await _context.WarehouseLocations.FindAsync(warehouse.Id);
        if (existing == null)
            throw new ArgumentException($"Magazzino con ID {warehouse.Id} non trovato");

        // Se è impostato come default, rimuovi il flag dagli altri
        if (warehouse.IsDefault && !existing.IsDefault)
        {
            await _context.WarehouseLocations
                .Where(w => w.IsDefault && w.Id != warehouse.Id)
                .ExecuteUpdateAsync(s => s.SetProperty(w => w.IsDefault, false));
        }

        _context.Entry(existing).CurrentValues.SetValues(warehouse);
        existing.UpdatedAt = DateTime.UtcNow;
        await _context.SaveChangesAsync();

        InvalidateWarehousesCache();
        await _notificationService.NotifyUpdated("warehouse_location", existing);
        return existing;
    }

    public async Task DeleteWarehouseAsync(int id)
    {
        var warehouse = await _context.WarehouseLocations.FindAsync(id);
        if (warehouse == null)
            throw new ArgumentException($"Magazzino con ID {id} non trovato");

        // Verifica che non ci siano giacenze
        if (await _context.StockLevels.AnyAsync(s => s.WarehouseId == id && s.Quantity > 0))
            throw new InvalidOperationException("Impossibile eliminare un magazzino con giacenze attive");

        _context.WarehouseLocations.Remove(warehouse);
        await _context.SaveChangesAsync();

        InvalidateWarehousesCache();
        await _notificationService.NotifyDeleted("warehouse_location", id);
    }

    public async Task SetDefaultWarehouseAsync(int id)
    {
        var warehouse = await _context.WarehouseLocations.FindAsync(id);
        if (warehouse == null)
            throw new ArgumentException($"Magazzino con ID {id} non trovato");

        // Rimuovi default da tutti
        await _context.WarehouseLocations
            .Where(w => w.IsDefault)
            .ExecuteUpdateAsync(s => s.SetProperty(w => w.IsDefault, false));

        // Imposta il nuovo default
        warehouse.IsDefault = true;
        warehouse.UpdatedAt = DateTime.UtcNow;
        await _context.SaveChangesAsync();

        InvalidateWarehousesCache();
        await _notificationService.NotifyUpdated("warehouse_location", warehouse);
    }

    private void InvalidateWarehousesCache()
    {
        _cache.Remove($"{WAREHOUSES_CACHE_KEY}_true");
        _cache.Remove($"{WAREHOUSES_CACHE_KEY}_false");
    }

    #endregion

    #region Partite (Batch)

    public async Task<List<ArticleBatch>> GetBatchesAsync(int? articleId = null, BatchStatus? status = null)
    {
        var query = _context.ArticleBatches
            .Include(b => b.Article)
            .AsQueryable();

        if (articleId.HasValue)
            query = query.Where(b => b.ArticleId == articleId);

        if (status.HasValue)
            query = query.Where(b => b.Status == status);

        return await query
            .OrderByDescending(b => b.CreatedAt)
            .ToListAsync();
    }

    public async Task<ArticleBatch?> GetBatchByIdAsync(int id)
    {
        return await _context.ArticleBatches
            .Include(b => b.Article)
            .FirstOrDefaultAsync(b => b.Id == id);
    }

    public async Task<ArticleBatch?> GetBatchByNumberAsync(int articleId, string batchNumber)
    {
        return await _context.ArticleBatches
            .Include(b => b.Article)
            .FirstOrDefaultAsync(b => b.ArticleId == articleId && b.BatchNumber == batchNumber);
    }

    public async Task<ArticleBatch> CreateBatchAsync(ArticleBatch batch)
    {
        // Verifica che l'articolo esista e sia gestito a lotti
        var article = await _context.WarehouseArticles.FindAsync(batch.ArticleId);
        if (article == null)
            throw new ArgumentException("Articolo non trovato");

        if (!article.IsBatchManaged)
            throw new InvalidOperationException("L'articolo non è gestito a lotti");

        // Genera numero lotto automaticamente se non specificato
        if (string.IsNullOrWhiteSpace(batch.BatchNumber))
        {
            var generatedCode = await _autoCodeService.GenerateNextCodeAsync("article_batch");
            if (generatedCode != null)
                batch.BatchNumber = generatedCode;
            else
                throw new InvalidOperationException("Impossibile generare numero lotto automatico");
        }

        // Verifica unicità batch number per articolo
        if (await _context.ArticleBatches.AnyAsync(b => b.ArticleId == batch.ArticleId && b.BatchNumber == batch.BatchNumber))
            throw new InvalidOperationException($"Esiste già un lotto '{batch.BatchNumber}' per questo articolo");

        batch.CreatedAt = DateTime.UtcNow;
        _context.ArticleBatches.Add(batch);
        await _context.SaveChangesAsync();

        _logger.LogInformation("Creato lotto {BatchNumber} per articolo {ArticleId}", batch.BatchNumber, batch.ArticleId);
        await _notificationService.NotifyCreated("article_batch", batch);
        return batch;
    }

    public async Task<ArticleBatch> UpdateBatchAsync(ArticleBatch batch)
    {
        var existing = await _context.ArticleBatches.FindAsync(batch.Id);
        if (existing == null)
            throw new ArgumentException($"Lotto con ID {batch.Id} non trovato");

        _context.Entry(existing).CurrentValues.SetValues(batch);
        existing.UpdatedAt = DateTime.UtcNow;
        await _context.SaveChangesAsync();

        await _notificationService.NotifyUpdated("article_batch", existing);
        return existing;
    }

    public async Task<List<ArticleBatch>> GetExpiringBatchesAsync(int daysThreshold = 30)
    {
        var thresholdDate = DateTime.UtcNow.AddDays(daysThreshold);

        return await _context.ArticleBatches
            .Include(b => b.Article)
            .Where(b => b.ExpiryDate.HasValue &&
                       b.ExpiryDate.Value <= thresholdDate &&
                       b.Status == BatchStatus.Available &&
                       b.CurrentQuantity > 0)
            .OrderBy(b => b.ExpiryDate)
            .ToListAsync();
    }

    public async Task UpdateBatchStatusAsync(int id, BatchStatus status)
    {
        var batch = await _context.ArticleBatches.FindAsync(id);
        if (batch == null)
            throw new ArgumentException($"Lotto con ID {id} non trovato");

        batch.Status = status;
        batch.UpdatedAt = DateTime.UtcNow;
        await _context.SaveChangesAsync();

        _logger.LogInformation("Aggiornato stato lotto {Id} a {Status}", id, status);
        await _notificationService.NotifyUpdated("article_batch", batch);
    }

    #endregion

    #region Seriali

    public async Task<List<ArticleSerial>> GetSerialsAsync(int? articleId = null, SerialStatus? status = null)
    {
        var query = _context.ArticleSerials
            .Include(s => s.Article)
            .Include(s => s.Batch)
            .Include(s => s.CurrentWarehouse)
            .AsQueryable();

        if (articleId.HasValue)
            query = query.Where(s => s.ArticleId == articleId);

        if (status.HasValue)
            query = query.Where(s => s.Status == status);

        return await query
            .OrderByDescending(s => s.CreatedAt)
            .ToListAsync();
    }

    public async Task<ArticleSerial?> GetSerialByIdAsync(int id)
    {
        return await _context.ArticleSerials
            .Include(s => s.Article)
            .Include(s => s.Batch)
            .Include(s => s.CurrentWarehouse)
            .FirstOrDefaultAsync(s => s.Id == id);
    }

    public async Task<ArticleSerial?> GetSerialByNumberAsync(int articleId, string serialNumber)
    {
        return await _context.ArticleSerials
            .Include(s => s.Article)
            .FirstOrDefaultAsync(s => s.ArticleId == articleId && s.SerialNumber == serialNumber);
    }

    public async Task<ArticleSerial> CreateSerialAsync(ArticleSerial serial)
    {
        // Verifica che l'articolo esista e sia gestito a seriali
        var article = await _context.WarehouseArticles.FindAsync(serial.ArticleId);
        if (article == null)
            throw new ArgumentException("Articolo non trovato");

        if (!article.IsSerialManaged)
            throw new InvalidOperationException("L'articolo non è gestito a seriali");

        // Verifica unicità serial number per articolo
        if (await _context.ArticleSerials.AnyAsync(s => s.ArticleId == serial.ArticleId && s.SerialNumber == serial.SerialNumber))
            throw new InvalidOperationException($"Esiste già un seriale '{serial.SerialNumber}' per questo articolo");

        serial.CreatedAt = DateTime.UtcNow;
        _context.ArticleSerials.Add(serial);
        await _context.SaveChangesAsync();

        _logger.LogInformation("Creato seriale {SerialNumber} per articolo {ArticleId}", serial.SerialNumber, serial.ArticleId);
        await _notificationService.NotifyCreated("article_serial", serial);
        return serial;
    }

    public async Task<ArticleSerial> UpdateSerialAsync(ArticleSerial serial)
    {
        var existing = await _context.ArticleSerials.FindAsync(serial.Id);
        if (existing == null)
            throw new ArgumentException($"Seriale con ID {serial.Id} non trovato");

        _context.Entry(existing).CurrentValues.SetValues(serial);
        existing.UpdatedAt = DateTime.UtcNow;
        await _context.SaveChangesAsync();

        await _notificationService.NotifyUpdated("article_serial", existing);
        return existing;
    }

    public async Task UpdateSerialStatusAsync(int id, SerialStatus status)
    {
        var serial = await _context.ArticleSerials.FindAsync(id);
        if (serial == null)
            throw new ArgumentException($"Seriale con ID {id} non trovato");

        serial.Status = status;
        serial.UpdatedAt = DateTime.UtcNow;
        await _context.SaveChangesAsync();

        _logger.LogInformation("Aggiornato stato seriale {Id} a {Status}", id, status);
        await _notificationService.NotifyUpdated("article_serial", serial);
    }

    #endregion

    #region Giacenze

    public async Task<List<StockLevel>> GetStockLevelsAsync(StockLevelFilter? filter = null)
    {
        var query = _context.StockLevels
            .Include(s => s.Article)
            .ThenInclude(a => a!.Category)
            .Include(s => s.Warehouse)
            .Include(s => s.Batch)
            .AsQueryable();

        if (filter != null)
        {
            if (filter.ArticleId.HasValue)
                query = query.Where(s => s.ArticleId == filter.ArticleId);

            if (filter.WarehouseId.HasValue)
                query = query.Where(s => s.WarehouseId == filter.WarehouseId);

            if (filter.BatchId.HasValue)
                query = query.Where(s => s.BatchId == filter.BatchId);

            if (filter.CategoryId.HasValue)
                query = query.Where(s => s.Article!.CategoryId == filter.CategoryId);

            if (filter.OnlyWithStock == true)
                query = query.Where(s => s.Quantity > 0);

            if (filter.OnlyLowStock == true)
                query = query.Where(s =>
                    s.Article!.MinimumStock.HasValue &&
                    s.Quantity <= s.Article.MinimumStock.Value);

            query = query.Skip(filter.Skip).Take(filter.Take);
        }

        return await query
            .OrderBy(s => s.Article!.Code)
            .ThenBy(s => s.Warehouse!.Code)
            .ToListAsync();
    }

    public async Task<StockLevel?> GetStockLevelAsync(int articleId, int warehouseId, int? batchId = null)
    {
        return await _context.StockLevels
            .Include(s => s.Article)
            .Include(s => s.Warehouse)
            .Include(s => s.Batch)
            .FirstOrDefaultAsync(s =>
                s.ArticleId == articleId &&
                s.WarehouseId == warehouseId &&
                s.BatchId == batchId);
    }

    public async Task<decimal> GetTotalStockAsync(int articleId)
    {
        return await _context.StockLevels
            .Where(s => s.ArticleId == articleId)
            .SumAsync(s => s.Quantity);
    }

    public async Task<decimal> GetAvailableStockAsync(int articleId, int? warehouseId = null)
    {
        var query = _context.StockLevels.Where(s => s.ArticleId == articleId);

        if (warehouseId.HasValue)
            query = query.Where(s => s.WarehouseId == warehouseId);

        return await query.SumAsync(s => s.Quantity - s.ReservedQuantity);
    }

    public async Task<List<StockLevel>> GetLowStockArticlesAsync()
    {
        return await _context.StockLevels
            .Include(s => s.Article)
            .ThenInclude(a => a!.Category)
            .Include(s => s.Warehouse)
            .Where(s =>
                s.Article!.MinimumStock.HasValue &&
                s.Quantity <= s.Article.MinimumStock.Value &&
                s.Article.IsActive)
            .OrderBy(s => s.Quantity)
            .ToListAsync();
    }

    public async Task UpdateStockLevelAsync(int articleId, int warehouseId, decimal quantity, int? batchId = null, decimal? unitCost = null)
    {
        var stockLevel = await _context.StockLevels
            .FirstOrDefaultAsync(s =>
                s.ArticleId == articleId &&
                s.WarehouseId == warehouseId &&
                s.BatchId == batchId);

        if (stockLevel == null)
        {
            stockLevel = new StockLevel
            {
                ArticleId = articleId,
                WarehouseId = warehouseId,
                BatchId = batchId,
                Quantity = quantity,
                UnitCost = unitCost,
                CreatedAt = DateTime.UtcNow
            };
            _context.StockLevels.Add(stockLevel);
        }
        else
        {
            stockLevel.Quantity = quantity;
            if (unitCost.HasValue)
                stockLevel.UnitCost = unitCost;
            stockLevel.UpdatedAt = DateTime.UtcNow;
        }

        stockLevel.LastMovementDate = DateTime.UtcNow;
        stockLevel.StockValue = stockLevel.Quantity * (stockLevel.UnitCost ?? 0);

        await _context.SaveChangesAsync();
        await _notificationService.NotifyUpdated("stock_level", stockLevel);
    }

    #endregion

    #region Movimenti

    public async Task<List<StockMovement>> GetMovementsAsync(MovementFilter? filter = null)
    {
        var query = _context.StockMovements
            .Include(m => m.SourceWarehouse)
            .Include(m => m.DestinationWarehouse)
            .Include(m => m.Reason)
            .Include(m => m.Lines)
            .ThenInclude(l => l.Article)
            .AsQueryable();

        if (filter != null)
        {
            if (filter.DateFrom.HasValue)
                query = query.Where(m => m.MovementDate >= filter.DateFrom);

            if (filter.DateTo.HasValue)
                query = query.Where(m => m.MovementDate <= filter.DateTo);

            if (filter.Type.HasValue)
                query = query.Where(m => m.Type == filter.Type);

            if (filter.Status.HasValue)
                query = query.Where(m => m.Status == filter.Status);

            if (filter.WarehouseId.HasValue)
                query = query.Where(m =>
                    m.SourceWarehouseId == filter.WarehouseId ||
                    m.DestinationWarehouseId == filter.WarehouseId);

            if (filter.ArticleId.HasValue)
                query = query.Where(m => m.Lines.Any(l => l.ArticleId == filter.ArticleId));

            if (filter.ReasonId.HasValue)
                query = query.Where(m => m.ReasonId == filter.ReasonId);

            if (!string.IsNullOrWhiteSpace(filter.ExternalReference))
                query = query.Where(m => m.ExternalReference != null && m.ExternalReference.Contains(filter.ExternalReference));

            // Ordinamento
            query = filter.OrderBy?.ToLower() switch
            {
                "documentnumber" => filter.OrderDescending ? query.OrderByDescending(m => m.DocumentNumber) : query.OrderBy(m => m.DocumentNumber),
                "type" => filter.OrderDescending ? query.OrderByDescending(m => m.Type) : query.OrderBy(m => m.Type),
                _ => filter.OrderDescending ? query.OrderByDescending(m => m.MovementDate) : query.OrderBy(m => m.MovementDate)
            };

            query = query.Skip(filter.Skip).Take(filter.Take);
        }
        else
        {
            query = query.OrderByDescending(m => m.MovementDate).Take(100);
        }

        return await query.ToListAsync();
    }

    public async Task<StockMovement?> GetMovementByIdAsync(int id)
    {
        return await _context.StockMovements
            .Include(m => m.SourceWarehouse)
            .Include(m => m.DestinationWarehouse)
            .Include(m => m.Reason)
            .Include(m => m.Lines)
            .ThenInclude(l => l.Article)
            .Include(m => m.Lines)
            .ThenInclude(l => l.Batch)
            .Include(m => m.Lines)
            .ThenInclude(l => l.Serial)
            .FirstOrDefaultAsync(m => m.Id == id);
    }

    public async Task<StockMovement?> GetMovementByDocumentNumberAsync(string documentNumber)
    {
        return await _context.StockMovements
            .Include(m => m.Lines)
            .FirstOrDefaultAsync(m => m.DocumentNumber == documentNumber);
    }

    public async Task<StockMovement> CreateMovementAsync(StockMovement movement)
    {
        // Genera numero documento automaticamente se non specificato
        if (string.IsNullOrEmpty(movement.DocumentNumber))
        {
            var generatedCode = await _autoCodeService.GenerateNextCodeAsync("stock_movement");
            if (generatedCode != null)
                movement.DocumentNumber = generatedCode;
            else
                movement.DocumentNumber = await GenerateDocumentNumberAsync(movement.Type); // Fallback
        }

        // Verifica unicità documento
        if (await _context.StockMovements.AnyAsync(m => m.DocumentNumber == movement.DocumentNumber))
            throw new InvalidOperationException($"Esiste già un movimento con numero documento '{movement.DocumentNumber}'");

        // Numera le righe
        var lineNumber = 1;
        foreach (var line in movement.Lines)
        {
            line.LineNumber = lineNumber++;
        }

        movement.CreatedAt = DateTime.UtcNow;
        _context.StockMovements.Add(movement);
        await _context.SaveChangesAsync();

        _logger.LogInformation("Creato movimento {DocumentNumber} tipo {Type}", movement.DocumentNumber, movement.Type);
        await _notificationService.NotifyCreated("stock_movement", movement);
        return movement;
    }

    public async Task<StockMovement> UpdateMovementAsync(StockMovement movement)
    {
        var existing = await _context.StockMovements
            .Include(m => m.Lines)
            .FirstOrDefaultAsync(m => m.Id == movement.Id);

        if (existing == null)
            throw new ArgumentException($"Movimento con ID {movement.Id} non trovato");

        if (existing.Status != MovementStatus.Draft)
            throw new InvalidOperationException("È possibile modificare solo movimenti in bozza");

        _context.Entry(existing).CurrentValues.SetValues(movement);
        existing.UpdatedAt = DateTime.UtcNow;

        // Aggiorna le righe
        _context.StockMovementLines.RemoveRange(existing.Lines);
        var lineNumber = 1;
        foreach (var line in movement.Lines)
        {
            line.MovementId = movement.Id;
            line.LineNumber = lineNumber++;
            _context.StockMovementLines.Add(line);
        }

        await _context.SaveChangesAsync();
        await _notificationService.NotifyUpdated("stock_movement", existing);
        return existing;
    }

    public async Task<StockMovement> ConfirmMovementAsync(int id)
    {
        var movement = await GetMovementByIdAsync(id);
        if (movement == null)
            throw new ArgumentException($"Movimento con ID {id} non trovato");

        if (movement.Status != MovementStatus.Draft)
            throw new InvalidOperationException("Solo i movimenti in bozza possono essere confermati");

        if (!movement.Lines.Any())
            throw new InvalidOperationException("Il movimento non contiene righe");

        // Applica il movimento alle giacenze
        await ApplyMovementToStockAsync(movement);

        movement.Status = MovementStatus.Confirmed;
        movement.ConfirmedDate = DateTime.UtcNow;
        movement.UpdatedAt = DateTime.UtcNow;

        // Calcola valore totale
        movement.TotalValue = movement.Lines.Sum(l => l.LineValue ?? 0);

        await _context.SaveChangesAsync();

        _logger.LogInformation("Confermato movimento {DocumentNumber}", movement.DocumentNumber);
        await _notificationService.NotifyUpdated("stock_movement", movement);
        return movement;
    }

    public async Task<StockMovement> CancelMovementAsync(int id)
    {
        var movement = await GetMovementByIdAsync(id);
        if (movement == null)
            throw new ArgumentException($"Movimento con ID {id} non trovato");

        if (movement.Status == MovementStatus.Cancelled)
            throw new InvalidOperationException("Il movimento è già annullato");

        // Se era confermato, storna le giacenze
        if (movement.Status == MovementStatus.Confirmed)
        {
            await ReverseMovementFromStockAsync(movement);
        }

        movement.Status = MovementStatus.Cancelled;
        movement.UpdatedAt = DateTime.UtcNow;
        await _context.SaveChangesAsync();

        _logger.LogWarning("Annullato movimento {DocumentNumber}", movement.DocumentNumber);
        await _notificationService.NotifyUpdated("stock_movement", movement);
        return movement;
    }

    public async Task<string> GenerateDocumentNumberAsync(MovementType type)
    {
        var prefix = type switch
        {
            MovementType.Inbound => "CAR",
            MovementType.Outbound => "SCA",
            MovementType.Transfer => "TRA",
            MovementType.Adjustment => "RET",
            MovementType.Production => "PRO",
            MovementType.Consumption => "CON",
            MovementType.SupplierReturn => "RFO",
            MovementType.CustomerReturn => "RCL",
            _ => "MOV"
        };

        var year = DateTime.UtcNow.Year;
        var lastNumber = await _context.StockMovements
            .Where(m => m.DocumentNumber.StartsWith($"{prefix}/{year}/"))
            .OrderByDescending(m => m.DocumentNumber)
            .Select(m => m.DocumentNumber)
            .FirstOrDefaultAsync();

        var nextNumber = 1;
        if (lastNumber != null)
        {
            var parts = lastNumber.Split('/');
            if (parts.Length == 3 && int.TryParse(parts[2], out var num))
                nextNumber = num + 1;
        }

        return $"{prefix}/{year}/{nextNumber:D6}";
    }

    private async Task ApplyMovementToStockAsync(StockMovement movement)
    {
        foreach (var line in movement.Lines)
        {
            var stockSign = GetStockSign(movement.Type);

            if (movement.Type == MovementType.Transfer)
            {
                // Trasferimento: scarica da origine e carica su destinazione
                if (movement.SourceWarehouseId.HasValue)
                {
                    await UpdateStockFromMovementLineAsync(
                        line,
                        movement.SourceWarehouseId.Value,
                        -line.Quantity);
                }

                if (movement.DestinationWarehouseId.HasValue)
                {
                    await UpdateStockFromMovementLineAsync(
                        line,
                        movement.DestinationWarehouseId.Value,
                        line.Quantity);
                }
            }
            else
            {
                // Altri movimenti: usa il magazzino appropriato
                var warehouseId = stockSign > 0
                    ? movement.DestinationWarehouseId
                    : movement.SourceWarehouseId;

                if (warehouseId.HasValue)
                {
                    await UpdateStockFromMovementLineAsync(
                        line,
                        warehouseId.Value,
                        line.Quantity * stockSign);
                }
            }

            // Aggiorna stato seriale se presente
            if (line.SerialId.HasValue)
            {
                await UpdateSerialFromMovementAsync(line, movement.Type, stockSign > 0
                    ? movement.DestinationWarehouseId
                    : null);
            }

            // Aggiorna quantità batch se presente
            if (line.BatchId.HasValue)
            {
                await UpdateBatchQuantityAsync(line.BatchId.Value, line.Quantity * stockSign);
            }
        }

        // Aggiorna costo medio ponderato per gli articoli interessati
        var articleIds = movement.Lines.Select(l => l.ArticleId).Distinct();
        foreach (var articleId in articleIds)
        {
            await UpdateWeightedAverageCostAsync(articleId);
        }
    }

    private async Task ReverseMovementFromStockAsync(StockMovement movement)
    {
        foreach (var line in movement.Lines)
        {
            var stockSign = GetStockSign(movement.Type);

            if (movement.Type == MovementType.Transfer)
            {
                // Storna trasferimento
                if (movement.SourceWarehouseId.HasValue)
                {
                    await UpdateStockFromMovementLineAsync(
                        line,
                        movement.SourceWarehouseId.Value,
                        line.Quantity); // Ricarica su origine
                }

                if (movement.DestinationWarehouseId.HasValue)
                {
                    await UpdateStockFromMovementLineAsync(
                        line,
                        movement.DestinationWarehouseId.Value,
                        -line.Quantity); // Scarica da destinazione
                }
            }
            else
            {
                var warehouseId = stockSign > 0
                    ? movement.DestinationWarehouseId
                    : movement.SourceWarehouseId;

                if (warehouseId.HasValue)
                {
                    await UpdateStockFromMovementLineAsync(
                        line,
                        warehouseId.Value,
                        -line.Quantity * stockSign); // Inverti il segno
                }
            }

            // Ripristina batch quantity
            if (line.BatchId.HasValue)
            {
                await UpdateBatchQuantityAsync(line.BatchId.Value, -line.Quantity * stockSign);
            }
        }
    }

    private async Task UpdateStockFromMovementLineAsync(StockMovementLine line, int warehouseId, decimal quantityChange)
    {
        var stockLevel = await _context.StockLevels
            .FirstOrDefaultAsync(s =>
                s.ArticleId == line.ArticleId &&
                s.WarehouseId == warehouseId &&
                s.BatchId == line.BatchId);

        if (stockLevel == null)
        {
            stockLevel = new StockLevel
            {
                ArticleId = line.ArticleId,
                WarehouseId = warehouseId,
                BatchId = line.BatchId,
                Quantity = 0,
                CreatedAt = DateTime.UtcNow
            };
            _context.StockLevels.Add(stockLevel);
        }

        stockLevel.Quantity += quantityChange;
        stockLevel.LastMovementDate = DateTime.UtcNow;

        // Aggiorna costo unitario e valore se è un carico con costo
        if (quantityChange > 0 && line.UnitCost.HasValue)
        {
            // Media ponderata del costo
            var oldValue = (stockLevel.Quantity - quantityChange) * (stockLevel.UnitCost ?? 0);
            var newValue = quantityChange * line.UnitCost.Value;
            stockLevel.UnitCost = stockLevel.Quantity > 0
                ? (oldValue + newValue) / stockLevel.Quantity
                : line.UnitCost;
        }

        stockLevel.StockValue = stockLevel.Quantity * (stockLevel.UnitCost ?? 0);
        stockLevel.UpdatedAt = DateTime.UtcNow;
    }

    private async Task UpdateSerialFromMovementAsync(StockMovementLine line, MovementType movementType, int? destinationWarehouseId)
    {
        if (!line.SerialId.HasValue) return;

        var serial = await _context.ArticleSerials.FindAsync(line.SerialId);
        if (serial == null) return;

        serial.CurrentWarehouseId = destinationWarehouseId;

        serial.Status = movementType switch
        {
            MovementType.Outbound => SerialStatus.Sold,
            MovementType.CustomerReturn => SerialStatus.Returned,
            MovementType.Inbound or MovementType.Transfer => SerialStatus.Available,
            _ => serial.Status
        };

        serial.UpdatedAt = DateTime.UtcNow;
    }

    private async Task UpdateBatchQuantityAsync(int batchId, decimal quantityChange)
    {
        var batch = await _context.ArticleBatches.FindAsync(batchId);
        if (batch == null) return;

        batch.CurrentQuantity += quantityChange;

        if (batch.CurrentQuantity <= 0)
        {
            batch.Status = BatchStatus.Depleted;
            batch.CurrentQuantity = 0;
        }

        batch.UpdatedAt = DateTime.UtcNow;
    }

    private int GetStockSign(MovementType type)
    {
        return type switch
        {
            MovementType.Inbound => 1,
            MovementType.Outbound => -1,
            MovementType.Production => 1,
            MovementType.Consumption => -1,
            MovementType.CustomerReturn => 1,
            MovementType.SupplierReturn => -1,
            MovementType.Adjustment => 1, // Il segno dipende dalla quantità (positiva o negativa)
            MovementType.Transfer => 0, // Gestito separatamente
            _ => 0
        };
    }

    #endregion

    #region Causali

    public async Task<List<MovementReason>> GetMovementReasonsAsync(MovementType? type = null, bool includeInactive = false)
    {
        var query = _context.MovementReasons.AsQueryable();

        if (type.HasValue)
            query = query.Where(r => r.MovementType == type);

        if (!includeInactive)
            query = query.Where(r => r.IsActive);

        return await query
            .OrderBy(r => r.SortOrder)
            .ThenBy(r => r.Description)
            .ToListAsync();
    }

    public async Task<MovementReason?> GetMovementReasonByIdAsync(int id)
    {
        return await _context.MovementReasons.FindAsync(id);
    }

    public async Task<MovementReason> CreateMovementReasonAsync(MovementReason reason)
    {
        if (await _context.MovementReasons.AnyAsync(r => r.Code == reason.Code))
            throw new InvalidOperationException($"Esiste già una causale con codice '{reason.Code}'");

        reason.CreatedAt = DateTime.UtcNow;
        _context.MovementReasons.Add(reason);
        await _context.SaveChangesAsync();

        return reason;
    }

    public async Task<MovementReason> UpdateMovementReasonAsync(MovementReason reason)
    {
        var existing = await _context.MovementReasons.FindAsync(reason.Id);
        if (existing == null)
            throw new ArgumentException($"Causale con ID {reason.Id} non trovata");

        if (existing.IsSystem)
            throw new InvalidOperationException("Le causali di sistema non possono essere modificate");

        _context.Entry(existing).CurrentValues.SetValues(reason);
        existing.UpdatedAt = DateTime.UtcNow;
        await _context.SaveChangesAsync();

        return existing;
    }

    #endregion

    #region Valorizzazione

    public async Task<decimal> CalculateArticleValueAsync(int articleId, ValuationMethod? method = null)
    {
        var article = await _context.WarehouseArticles.FindAsync(articleId);
        if (article == null)
            throw new ArgumentException("Articolo non trovato");

        var valuationMethod = method ?? article.ValuationMethod ?? ValuationMethod.WeightedAverage;

        return valuationMethod switch
        {
            ValuationMethod.WeightedAverage => await CalculateWeightedAverageValueAsync(articleId),
            ValuationMethod.FIFO => await CalculateFIFOValueAsync(articleId),
            ValuationMethod.LIFO => await CalculateLIFOValueAsync(articleId),
            ValuationMethod.StandardCost => await CalculateStandardCostValueAsync(articleId),
            ValuationMethod.SpecificCost => await CalculateSpecificCostValueAsync(articleId),
            _ => 0
        };
    }

    private async Task<decimal> CalculateWeightedAverageValueAsync(int articleId)
    {
        var stockLevels = await _context.StockLevels
            .Where(s => s.ArticleId == articleId && s.Quantity > 0)
            .ToListAsync();

        return stockLevels.Sum(s => s.Quantity * (s.UnitCost ?? 0));
    }

    private async Task<decimal> CalculateFIFOValueAsync(int articleId)
    {
        var layers = await _context.StockValuationLayers
            .Where(l => l.ArticleId == articleId && !l.IsExhausted && l.RemainingQuantity > 0)
            .OrderBy(l => l.LayerDate)
            .ToListAsync();

        return layers.Sum(l => l.RemainingValue);
    }

    private async Task<decimal> CalculateLIFOValueAsync(int articleId)
    {
        var layers = await _context.StockValuationLayers
            .Where(l => l.ArticleId == articleId && !l.IsExhausted && l.RemainingQuantity > 0)
            .OrderByDescending(l => l.LayerDate)
            .ToListAsync();

        return layers.Sum(l => l.RemainingValue);
    }

    private async Task<decimal> CalculateStandardCostValueAsync(int articleId)
    {
        var article = await _context.WarehouseArticles.FindAsync(articleId);
        if (article?.StandardCost == null)
            return 0;

        var totalQty = await GetTotalStockAsync(articleId);
        return totalQty * article.StandardCost.Value;
    }

    private async Task<decimal> CalculateSpecificCostValueAsync(int articleId)
    {
        // Per costo specifico, somma il valore di tutti i batch
        var batches = await _context.ArticleBatches
            .Where(b => b.ArticleId == articleId && b.CurrentQuantity > 0)
            .ToListAsync();

        return batches.Sum(b => b.CurrentQuantity * (b.UnitCost ?? 0));
    }

    public async Task<StockValuation> CalculatePeriodValuationAsync(int articleId, int period, int? warehouseId = null)
    {
        var article = await _context.WarehouseArticles.FindAsync(articleId);
        if (article == null)
            throw new ArgumentException("Articolo non trovato");

        var method = article.ValuationMethod ?? ValuationMethod.WeightedAverage;

        // Calcola quantità a fine periodo
        var stockQuery = _context.StockLevels.Where(s => s.ArticleId == articleId);
        if (warehouseId.HasValue)
            stockQuery = stockQuery.Where(s => s.WarehouseId == warehouseId);

        var quantity = await stockQuery.SumAsync(s => s.Quantity);

        // Calcola movimenti del periodo
        var periodStart = new DateTime(period / 100, period % 100, 1);
        var periodEnd = periodStart.AddMonths(1);

        var movements = await _context.StockMovementLines
            .Include(l => l.Movement)
            .Where(l => l.ArticleId == articleId &&
                       l.Movement!.Status == MovementStatus.Confirmed &&
                       l.Movement.MovementDate >= periodStart &&
                       l.Movement.MovementDate < periodEnd)
            .ToListAsync();

        var inboundQty = movements
            .Where(l => GetStockSign(l.Movement!.Type) > 0)
            .Sum(l => l.Quantity);

        var inboundValue = movements
            .Where(l => GetStockSign(l.Movement!.Type) > 0)
            .Sum(l => l.LineValue ?? 0);

        var outboundQty = movements
            .Where(l => GetStockSign(l.Movement!.Type) < 0)
            .Sum(l => l.Quantity);

        var outboundValue = movements
            .Where(l => GetStockSign(l.Movement!.Type) < 0)
            .Sum(l => l.LineValue ?? 0);

        var unitCost = await GetWeightedAverageCostAsync(articleId);

        var valuation = new StockValuation
        {
            ArticleId = articleId,
            WarehouseId = warehouseId,
            Period = period,
            ValuationDate = DateTime.UtcNow,
            Method = method,
            Quantity = quantity,
            UnitCost = unitCost,
            TotalValue = quantity * unitCost,
            InboundQuantity = inboundQty,
            InboundValue = inboundValue,
            OutboundQuantity = outboundQty,
            OutboundValue = outboundValue,
            CreatedAt = DateTime.UtcNow
        };

        return valuation;
    }

    public async Task<List<StockValuation>> GetValuationsAsync(int period, int? warehouseId = null)
    {
        var query = _context.StockValuations
            .Include(v => v.Article)
            .Include(v => v.Warehouse)
            .Where(v => v.Period == period);

        if (warehouseId.HasValue)
            query = query.Where(v => v.WarehouseId == warehouseId);

        return await query
            .OrderBy(v => v.Article!.Code)
            .ToListAsync();
    }

    public async Task ClosePeriodAsync(int period)
    {
        var valuations = await _context.StockValuations
            .Where(v => v.Period == period && !v.IsClosed)
            .ToListAsync();

        foreach (var valuation in valuations)
        {
            valuation.IsClosed = true;
            valuation.ClosedDate = DateTime.UtcNow;
        }

        await _context.SaveChangesAsync();
        _logger.LogInformation("Chiuso periodo {Period} con {Count} valorizzazioni", period, valuations.Count);
    }

    public async Task<decimal> GetWeightedAverageCostAsync(int articleId)
    {
        var article = await _context.WarehouseArticles.FindAsync(articleId);
        return article?.WeightedAverageCost ?? 0;
    }

    public async Task UpdateWeightedAverageCostAsync(int articleId)
    {
        var article = await _context.WarehouseArticles.FindAsync(articleId);
        if (article == null) return;

        var stockLevels = await _context.StockLevels
            .Where(s => s.ArticleId == articleId && s.Quantity > 0)
            .ToListAsync();

        var totalQty = stockLevels.Sum(s => s.Quantity);
        var totalValue = stockLevels.Sum(s => s.Quantity * (s.UnitCost ?? 0));

        article.WeightedAverageCost = totalQty > 0 ? totalValue / totalQty : 0;
        article.UpdatedAt = DateTime.UtcNow;

        await _context.SaveChangesAsync();
    }

    #endregion

    #region Inventario

    public async Task<List<InventoryCount>> GetInventoryCountsAsync(InventoryStatus? status = null)
    {
        var query = _context.InventoryCounts
            .Include(i => i.Warehouse)
            .Include(i => i.Category)
            .AsQueryable();

        if (status.HasValue)
            query = query.Where(i => i.Status == status);

        return await query
            .OrderByDescending(i => i.InventoryDate)
            .ToListAsync();
    }

    public async Task<InventoryCount?> GetInventoryCountByIdAsync(int id)
    {
        return await _context.InventoryCounts
            .Include(i => i.Warehouse)
            .Include(i => i.Category)
            .Include(i => i.Lines)
            .ThenInclude(l => l.Article)
            .Include(i => i.Lines)
            .ThenInclude(l => l.Warehouse)
            .Include(i => i.Lines)
            .ThenInclude(l => l.Batch)
            .FirstOrDefaultAsync(i => i.Id == id);
    }

    public async Task<InventoryCount> CreateInventoryCountAsync(InventoryCount inventory)
    {
        // Genera codice automaticamente se non specificato
        if (string.IsNullOrEmpty(inventory.Code))
        {
            var generatedCode = await _autoCodeService.GenerateNextCodeAsync("inventory_count");
            if (generatedCode != null)
                inventory.Code = generatedCode;
            else
                inventory.Code = $"INV/{DateTime.UtcNow:yyyyMMdd}/{await GenerateInventorySequenceAsync()}"; // Fallback
        }

        inventory.CreatedAt = DateTime.UtcNow;
        _context.InventoryCounts.Add(inventory);
        await _context.SaveChangesAsync();

        _logger.LogInformation("Creato inventario {Code}", inventory.Code);
        await _notificationService.NotifyCreated("inventory_count", inventory);
        return inventory;
    }

    public async Task<InventoryCount> UpdateInventoryCountAsync(InventoryCount inventory)
    {
        var existing = await _context.InventoryCounts.FindAsync(inventory.Id);
        if (existing == null)
            throw new ArgumentException($"Inventario con ID {inventory.Id} non trovato");

        if (existing.Status != InventoryStatus.Draft)
            throw new InvalidOperationException("È possibile modificare solo inventari in bozza");

        _context.Entry(existing).CurrentValues.SetValues(inventory);
        existing.UpdatedAt = DateTime.UtcNow;
        await _context.SaveChangesAsync();

        await _notificationService.NotifyUpdated("inventory_count", existing);
        return existing;
    }

    public async Task<InventoryCount> StartInventoryCountAsync(int id)
    {
        var inventory = await GetInventoryCountByIdAsync(id);
        if (inventory == null)
            throw new ArgumentException($"Inventario con ID {id} non trovato");

        if (inventory.Status != InventoryStatus.Draft)
            throw new InvalidOperationException("Solo gli inventari in bozza possono essere avviati");

        // Genera le righe da contare
        await GenerateInventoryLinesAsync(inventory);

        inventory.Status = InventoryStatus.InProgress;
        inventory.StartDate = DateTime.UtcNow;
        inventory.UpdatedAt = DateTime.UtcNow;

        await _context.SaveChangesAsync();

        _logger.LogInformation("Avviato inventario {Code} con {Count} righe", inventory.Code, inventory.Lines.Count);
        await _notificationService.NotifyUpdated("inventory_count", inventory);
        return inventory;
    }

    public async Task<InventoryCount> CompleteInventoryCountAsync(int id)
    {
        var inventory = await GetInventoryCountByIdAsync(id);
        if (inventory == null)
            throw new ArgumentException($"Inventario con ID {id} non trovato");

        if (inventory.Status != InventoryStatus.InProgress)
            throw new InvalidOperationException("Solo gli inventari in corso possono essere completati");

        // Verifica che tutte le righe siano state contate
        if (inventory.Lines.Any(l => !l.CountedQuantity.HasValue))
            throw new InvalidOperationException("Alcune righe non sono state ancora contate");

        // Calcola le differenze
        inventory.PositiveDifferenceValue = inventory.Lines
            .Where(l => l.Difference > 0)
            .Sum(l => l.DifferenceValue ?? 0);

        inventory.NegativeDifferenceValue = inventory.Lines
            .Where(l => l.Difference < 0)
            .Sum(l => Math.Abs(l.DifferenceValue ?? 0));

        inventory.Status = InventoryStatus.Completed;
        inventory.EndDate = DateTime.UtcNow;
        inventory.UpdatedAt = DateTime.UtcNow;

        await _context.SaveChangesAsync();

        _logger.LogInformation("Completato inventario {Code}", inventory.Code);
        await _notificationService.NotifyUpdated("inventory_count", inventory);
        return inventory;
    }

    public async Task<InventoryCount> ConfirmInventoryCountAsync(int id)
    {
        var inventory = await GetInventoryCountByIdAsync(id);
        if (inventory == null)
            throw new ArgumentException($"Inventario con ID {id} non trovato");

        if (inventory.Status != InventoryStatus.Completed)
            throw new InvalidOperationException("Solo gli inventari completati possono essere confermati");

        // Crea movimento di rettifica
        var adjustmentMovement = new StockMovement
        {
            DocumentNumber = await GenerateDocumentNumberAsync(MovementType.Adjustment),
            MovementDate = DateTime.UtcNow,
            Type = MovementType.Adjustment,
            Status = MovementStatus.Draft,
            Notes = $"Rettifica da inventario {inventory.Code}",
            CreatedAt = DateTime.UtcNow
        };

        var lineNumber = 1;
        foreach (var line in inventory.Lines.Where(l => l.Difference != 0))
        {
            var movementLine = new StockMovementLine
            {
                LineNumber = lineNumber++,
                ArticleId = line.ArticleId,
                BatchId = line.BatchId,
                Quantity = line.Difference!.Value,
                UnitOfMeasure = line.Article?.UnitOfMeasure ?? "PZ",
                UnitCost = line.UnitCost,
                LineValue = line.DifferenceValue
            };
            adjustmentMovement.Lines.Add(movementLine);
        }

        if (adjustmentMovement.Lines.Any())
        {
            // Determina il magazzino per il movimento
            var warehouseId = inventory.WarehouseId ??
                              inventory.Lines.FirstOrDefault()?.WarehouseId;

            if (warehouseId.HasValue)
            {
                // Per le rettifiche, usa il magazzino come destinazione per qta positive, origine per negative
                adjustmentMovement.DestinationWarehouseId = warehouseId;
            }

            _context.StockMovements.Add(adjustmentMovement);
            await _context.SaveChangesAsync();

            // Conferma il movimento per applicare le rettifiche
            await ConfirmMovementAsync(adjustmentMovement.Id);

            inventory.AdjustmentMovementId = adjustmentMovement.Id;
        }

        inventory.Status = InventoryStatus.Confirmed;
        inventory.ConfirmedDate = DateTime.UtcNow;
        inventory.UpdatedAt = DateTime.UtcNow;

        await _context.SaveChangesAsync();

        _logger.LogInformation("Confermato inventario {Code}, movimento rettifica: {MovementId}",
            inventory.Code, inventory.AdjustmentMovementId);

        await _notificationService.NotifyUpdated("inventory_count", inventory);
        return inventory;
    }

    public async Task<InventoryCount> CancelInventoryCountAsync(int id)
    {
        var inventory = await _context.InventoryCounts.FindAsync(id);
        if (inventory == null)
            throw new ArgumentException($"Inventario con ID {id} non trovato");

        if (inventory.Status == InventoryStatus.Confirmed)
            throw new InvalidOperationException("Gli inventari confermati non possono essere annullati");

        inventory.Status = InventoryStatus.Cancelled;
        inventory.UpdatedAt = DateTime.UtcNow;

        await _context.SaveChangesAsync();

        _logger.LogWarning("Annullato inventario {Code}", inventory.Code);
        await _notificationService.NotifyUpdated("inventory_count", inventory);
        return inventory;
    }

    public async Task<InventoryCountLine> UpdateCountLineAsync(int lineId, decimal countedQuantity, string? countedBy = null)
    {
        var line = await _context.InventoryCountLines
            .Include(l => l.InventoryCount)
            .FirstOrDefaultAsync(l => l.Id == lineId);

        if (line == null)
            throw new ArgumentException($"Riga inventario con ID {lineId} non trovata");

        if (line.InventoryCount?.Status != InventoryStatus.InProgress)
            throw new InvalidOperationException("È possibile contare solo inventari in corso");

        line.CountedQuantity = countedQuantity;
        line.CountedAt = DateTime.UtcNow;
        line.CountedBy = countedBy;
        line.UpdatedAt = DateTime.UtcNow;

        await _context.SaveChangesAsync();

        return line;
    }

    private async Task GenerateInventoryLinesAsync(InventoryCount inventory)
    {
        var stockQuery = _context.StockLevels
            .Include(s => s.Article)
            .AsQueryable();

        if (inventory.WarehouseId.HasValue)
            stockQuery = stockQuery.Where(s => s.WarehouseId == inventory.WarehouseId);

        if (inventory.CategoryId.HasValue)
            stockQuery = stockQuery.Where(s => s.Article!.CategoryId == inventory.CategoryId);

        var stockLevels = await stockQuery.ToListAsync();

        foreach (var stock in stockLevels)
        {
            var line = new InventoryCountLine
            {
                InventoryCountId = inventory.Id,
                ArticleId = stock.ArticleId,
                WarehouseId = stock.WarehouseId,
                BatchId = stock.BatchId,
                TheoreticalQuantity = stock.Quantity,
                UnitCost = stock.UnitCost ?? stock.Article?.WeightedAverageCost ?? 0,
                CreatedAt = DateTime.UtcNow
            };
            _context.InventoryCountLines.Add(line);
        }

        await _context.SaveChangesAsync();
    }

    private async Task<int> GenerateInventorySequenceAsync()
    {
        var today = DateTime.UtcNow.Date;
        var count = await _context.InventoryCounts
            .CountAsync(i => i.CreatedAt >= today);
        return count + 1;
    }

    #endregion

    #region Seed Data

    public async Task SeedDefaultDataAsync()
    {
        // Seed magazzini
        if (!await _context.WarehouseLocations.AnyAsync())
        {
            var warehouses = new List<WarehouseLocation>
            {
                new WarehouseLocation
                {
                    Code = "MAG01",
                    Name = "Magazzino Principale",
                    Description = "Magazzino centrale",
                    Type = WarehouseType.Physical,
                    IsDefault = true,
                    IsActive = true,
                    SortOrder = 10,
                    CreatedAt = DateTime.UtcNow
                },
                new WarehouseLocation
                {
                    Code = "TRANS",
                    Name = "Magazzino Transito",
                    Description = "Merce in transito",
                    Type = WarehouseType.Transit,
                    IsDefault = false,
                    IsActive = true,
                    SortOrder = 20,
                    CreatedAt = DateTime.UtcNow
                }
            };
            _context.WarehouseLocations.AddRange(warehouses);
            _logger.LogInformation("Seed {Count} magazzini di default", warehouses.Count);
        }

        // Seed categorie
        if (!await _context.WarehouseArticleCategories.AnyAsync())
        {
            var categories = new List<WarehouseArticleCategory>
            {
                new WarehouseArticleCategory
                {
                    Code = "MAT",
                    Name = "Materie Prime",
                    Level = 0,
                    FullPath = "MAT",
                    Icon = "Inventory2",
                    IsActive = true,
                    SortOrder = 10,
                    CreatedAt = DateTime.UtcNow
                },
                new WarehouseArticleCategory
                {
                    Code = "SEM",
                    Name = "Semilavorati",
                    Level = 0,
                    FullPath = "SEM",
                    Icon = "Build",
                    IsActive = true,
                    SortOrder = 20,
                    CreatedAt = DateTime.UtcNow
                },
                new WarehouseArticleCategory
                {
                    Code = "PF",
                    Name = "Prodotti Finiti",
                    Level = 0,
                    FullPath = "PF",
                    Icon = "CheckBox",
                    IsActive = true,
                    SortOrder = 30,
                    CreatedAt = DateTime.UtcNow
                },
                new WarehouseArticleCategory
                {
                    Code = "CONS",
                    Name = "Consumabili",
                    Level = 0,
                    FullPath = "CONS",
                    Icon = "LocalOffer",
                    IsActive = true,
                    SortOrder = 40,
                    CreatedAt = DateTime.UtcNow
                }
            };
            _context.WarehouseArticleCategories.AddRange(categories);
            _logger.LogInformation("Seed {Count} categorie di default", categories.Count);
        }

        // Seed causali movimento
        if (!await _context.MovementReasons.AnyAsync())
        {
            var reasons = new List<MovementReason>
            {
                // Causali carico
                new MovementReason
                {
                    Code = "ACQ",
                    Description = "Acquisto da fornitore",
                    MovementType = MovementType.Inbound,
                    StockSign = 1,
                    RequiresExternalReference = true,
                    RequiresValuation = true,
                    UpdatesAverageCost = true,
                    IsSystem = true,
                    IsActive = true,
                    SortOrder = 10,
                    CreatedAt = DateTime.UtcNow
                },
                new MovementReason
                {
                    Code = "RCLI",
                    Description = "Reso da cliente",
                    MovementType = MovementType.CustomerReturn,
                    StockSign = 1,
                    RequiresExternalReference = true,
                    RequiresValuation = true,
                    UpdatesAverageCost = false,
                    IsSystem = true,
                    IsActive = true,
                    SortOrder = 20,
                    CreatedAt = DateTime.UtcNow
                },
                new MovementReason
                {
                    Code = "PROD",
                    Description = "Ingresso da produzione",
                    MovementType = MovementType.Production,
                    StockSign = 1,
                    RequiresExternalReference = true,
                    RequiresValuation = true,
                    UpdatesAverageCost = true,
                    IsSystem = true,
                    IsActive = true,
                    SortOrder = 30,
                    CreatedAt = DateTime.UtcNow
                },
                // Causali scarico
                new MovementReason
                {
                    Code = "VEN",
                    Description = "Vendita a cliente",
                    MovementType = MovementType.Outbound,
                    StockSign = -1,
                    RequiresExternalReference = true,
                    RequiresValuation = true,
                    UpdatesAverageCost = false,
                    IsSystem = true,
                    IsActive = true,
                    SortOrder = 40,
                    CreatedAt = DateTime.UtcNow
                },
                new MovementReason
                {
                    Code = "RFOR",
                    Description = "Reso a fornitore",
                    MovementType = MovementType.SupplierReturn,
                    StockSign = -1,
                    RequiresExternalReference = true,
                    RequiresValuation = true,
                    UpdatesAverageCost = false,
                    IsSystem = true,
                    IsActive = true,
                    SortOrder = 50,
                    CreatedAt = DateTime.UtcNow
                },
                new MovementReason
                {
                    Code = "CONS",
                    Description = "Consumo per produzione",
                    MovementType = MovementType.Consumption,
                    StockSign = -1,
                    RequiresExternalReference = true,
                    RequiresValuation = true,
                    UpdatesAverageCost = false,
                    IsSystem = true,
                    IsActive = true,
                    SortOrder = 60,
                    CreatedAt = DateTime.UtcNow
                },
                // Causali trasferimento
                new MovementReason
                {
                    Code = "TRAS",
                    Description = "Trasferimento tra magazzini",
                    MovementType = MovementType.Transfer,
                    StockSign = 0,
                    RequiresExternalReference = false,
                    RequiresValuation = false,
                    UpdatesAverageCost = false,
                    IsSystem = true,
                    IsActive = true,
                    SortOrder = 70,
                    CreatedAt = DateTime.UtcNow
                },
                // Causali rettifica
                new MovementReason
                {
                    Code = "RET+",
                    Description = "Rettifica positiva inventario",
                    MovementType = MovementType.Adjustment,
                    StockSign = 1,
                    RequiresExternalReference = false,
                    RequiresValuation = true,
                    UpdatesAverageCost = false,
                    IsSystem = true,
                    IsActive = true,
                    SortOrder = 80,
                    CreatedAt = DateTime.UtcNow
                },
                new MovementReason
                {
                    Code = "RET-",
                    Description = "Rettifica negativa inventario",
                    MovementType = MovementType.Adjustment,
                    StockSign = -1,
                    RequiresExternalReference = false,
                    RequiresValuation = true,
                    UpdatesAverageCost = false,
                    IsSystem = true,
                    IsActive = true,
                    SortOrder = 90,
                    CreatedAt = DateTime.UtcNow
                }
            };
            _context.MovementReasons.AddRange(reasons);
            _logger.LogInformation("Seed {Count} causali movimento di default", reasons.Count);
        }

        await _context.SaveChangesAsync();
    }

    #endregion
}
