using System.Text.RegularExpressions;
using Apollinare.Domain.Entities;
using Apollinare.Infrastructure.Data;
using Microsoft.EntityFrameworkCore;

namespace Apollinare.API.Services;

/// <summary>
/// Servizio per la generazione automatica di codici univoci.
/// Thread-safe grazie all'uso di transazioni database.
/// </summary>
public class AutoCodeService
{
    private readonly AppollinareDbContext _db;
    private readonly ILogger<AutoCodeService> _logger;
    private static readonly Regex SequencePattern = new(@"\{SEQ:(\d+)\}", RegexOptions.Compiled);

    public AutoCodeService(AppollinareDbContext db, ILogger<AutoCodeService> logger)
    {
        _db = db;
        _logger = logger;
    }

    /// <summary>
    /// Genera il prossimo codice per un'entità specifica.
    /// Incrementa automaticamente la sequenza e gestisce i reset periodici.
    /// </summary>
    /// <param name="entityCode">Codice dell'entità (es. "warehouse_article")</param>
    /// <returns>Il nuovo codice generato, o null se la generazione automatica è disabilitata</returns>
    public async Task<string?> GenerateNextCodeAsync(string entityCode)
    {
        // Usa una transazione per garantire atomicità
        await using var transaction = await _db.Database.BeginTransactionAsync();

        try
        {
            var config = await _db.AutoCodes
                .FirstOrDefaultAsync(c => c.EntityCode == entityCode);

            if (config == null)
            {
                _logger.LogWarning("Configurazione AutoCode non trovata per entità: {EntityCode}", entityCode);
                return null;
            }

            if (!config.IsEnabled)
            {
                _logger.LogDebug("Generazione automatica disabilitata per entità: {EntityCode}", entityCode);
                return null;
            }

            var now = DateTime.Now;

            // Gestione reset sequenza
            if (ShouldResetSequence(config, now))
            {
                config.LastSequence = 0;
                config.LastResetYear = now.Year;
                config.LastResetMonth = now.Month;
                _logger.LogInformation("Sequenza resettata per entità {EntityCode} (Anno: {Year}, Mese: {Month})",
                    entityCode, now.Year, now.Month);
            }

            // Incrementa sequenza
            config.LastSequence++;
            config.UpdatedAt = now;

            // Genera codice dal pattern
            var code = GenerateCodeFromPattern(config.Pattern, config.Prefix, config.LastSequence, now);

            await _db.SaveChangesAsync();
            await transaction.CommitAsync();

            _logger.LogDebug("Codice generato per {EntityCode}: {Code}", entityCode, code);
            return code;
        }
        catch (Exception ex)
        {
            await transaction.RollbackAsync();
            _logger.LogError(ex, "Errore durante la generazione del codice per {EntityCode}", entityCode);
            throw;
        }
    }

    /// <summary>
    /// Genera un codice di anteprima senza incrementare la sequenza.
    /// Utile per mostrare all'utente cosa verrà generato.
    /// </summary>
    public async Task<string?> PreviewNextCodeAsync(string entityCode)
    {
        var config = await _db.AutoCodes
            .AsNoTracking()
            .FirstOrDefaultAsync(c => c.EntityCode == entityCode);

        if (config == null || !config.IsEnabled)
            return null;

        var now = DateTime.Now;
        var nextSequence = config.LastSequence + 1;

        // Simula reset se necessario
        if (ShouldResetSequence(config, now))
            nextSequence = 1;

        return GenerateCodeFromPattern(config.Pattern, config.Prefix, nextSequence, now);
    }

    /// <summary>
    /// Verifica se un codice è già utilizzato per un'entità.
    /// </summary>
    public async Task<bool> IsCodeUniqueAsync(string entityCode, string code, int? excludeId = null)
    {
        return entityCode switch
        {
            "warehouse_article" => !await _db.WarehouseArticles
                .AnyAsync(a => a.Code == code && (excludeId == null || a.Id != excludeId)),

            "warehouse_location" => !await _db.WarehouseLocations
                .AnyAsync(w => w.Code == code && (excludeId == null || w.Id != excludeId)),

            "warehouse_category" => !await _db.WarehouseArticleCategories
                .AnyAsync(c => c.Code == code && (excludeId == null || c.Id != excludeId)),

            "stock_movement" => !await _db.StockMovements
                .AnyAsync(m => m.DocumentNumber == code && (excludeId == null || m.Id != excludeId)),

            "inventory_count" => !await _db.InventoryCounts
                .AnyAsync(i => i.Code == code && (excludeId == null || i.Id != excludeId)),

            "cliente" => !await _db.Clienti
                .AnyAsync(c => c.Codice == code && (excludeId == null || c.Id != excludeId)),

            "evento" => !await _db.Eventi
                .AnyAsync(e => e.Codice == code && (excludeId == null || e.Id != excludeId)),

            "articolo" => !await _db.Articoli
                .AnyAsync(a => a.Codice == code && (excludeId == null || a.Id != excludeId)),

            _ => true // Entità non gestita, assume codice unico
        };
    }

    /// <summary>
    /// Ottiene tutte le configurazioni AutoCode.
    /// </summary>
    public async Task<List<AutoCode>> GetAllConfigurationsAsync()
    {
        return await _db.AutoCodes
            .OrderBy(c => c.ModuleCode)
            .ThenBy(c => c.SortOrder)
            .ThenBy(c => c.EntityName)
            .ToListAsync();
    }

    /// <summary>
    /// Ottiene le configurazioni AutoCode per un modulo specifico.
    /// </summary>
    public async Task<List<AutoCode>> GetConfigurationsByModuleAsync(string moduleCode)
    {
        return await _db.AutoCodes
            .Where(c => c.ModuleCode == moduleCode)
            .OrderBy(c => c.SortOrder)
            .ThenBy(c => c.EntityName)
            .ToListAsync();
    }

    /// <summary>
    /// Ottiene una configurazione AutoCode per codice entità.
    /// </summary>
    public async Task<AutoCode?> GetConfigurationAsync(string entityCode)
    {
        return await _db.AutoCodes
            .FirstOrDefaultAsync(c => c.EntityCode == entityCode);
    }

    /// <summary>
    /// Aggiorna una configurazione AutoCode.
    /// </summary>
    public async Task<AutoCode> UpdateConfigurationAsync(int id, AutoCodeUpdateDto dto)
    {
        var config = await _db.AutoCodes.FindAsync(id)
            ?? throw new KeyNotFoundException($"Configurazione AutoCode con ID {id} non trovata");

        if (dto.Prefix != null)
            config.Prefix = dto.Prefix;

        if (dto.Pattern != null)
        {
            // Valida il pattern
            ValidatePattern(dto.Pattern);
            config.Pattern = dto.Pattern;
        }

        if (dto.ResetSequenceYearly.HasValue)
            config.ResetSequenceYearly = dto.ResetSequenceYearly.Value;

        if (dto.ResetSequenceMonthly.HasValue)
            config.ResetSequenceMonthly = dto.ResetSequenceMonthly.Value;

        if (dto.IsEnabled.HasValue)
            config.IsEnabled = dto.IsEnabled.Value;

        if (dto.IsReadOnly.HasValue)
            config.IsReadOnly = dto.IsReadOnly.Value;

        if (dto.Description != null)
            config.Description = dto.Description;

        config.UpdatedAt = DateTime.Now;

        await _db.SaveChangesAsync();
        return config;
    }

    /// <summary>
    /// Resetta manualmente la sequenza per un'entità.
    /// </summary>
    public async Task ResetSequenceAsync(string entityCode, long newValue = 0)
    {
        var config = await _db.AutoCodes
            .FirstOrDefaultAsync(c => c.EntityCode == entityCode)
            ?? throw new KeyNotFoundException($"Configurazione AutoCode per '{entityCode}' non trovata");

        config.LastSequence = newValue;
        config.LastResetYear = DateTime.Now.Year;
        config.LastResetMonth = DateTime.Now.Month;
        config.UpdatedAt = DateTime.Now;

        await _db.SaveChangesAsync();

        _logger.LogInformation("Sequenza resettata manualmente per {EntityCode} a {Value}", entityCode, newValue);
    }

    /// <summary>
    /// Inizializza le configurazioni di default per tutte le entità.
    /// Chiamato al seed dell'applicazione.
    /// </summary>
    public async Task SeedDefaultConfigurationsAsync()
    {
        var defaults = new List<AutoCode>
        {
            // Core
            new()
            {
                EntityCode = "cliente",
                EntityName = "Cliente",
                Prefix = "CLI",
                Pattern = "{PREFIX}-{SEQ:5}",
                ModuleCode = "core",
                Description = "Codice cliente (es. CLI-00001)",
                SortOrder = 10
            },
            new()
            {
                EntityCode = "evento",
                EntityName = "Evento",
                Prefix = "EVT",
                Pattern = "{PREFIX}{YYYY}-{SEQ:5}",
                ResetSequenceYearly = true,
                ModuleCode = "core",
                Description = "Codice evento con anno (es. EVT2025-00001)",
                SortOrder = 20
            },
            new()
            {
                EntityCode = "articolo",
                EntityName = "Articolo (Legacy)",
                Prefix = "ART",
                Pattern = "{PREFIX}-{SEQ:5}",
                ModuleCode = "core",
                Description = "Codice articolo legacy (es. ART-00001)",
                SortOrder = 30
            },

            // Warehouse module
            new()
            {
                EntityCode = "warehouse_location",
                EntityName = "Magazzino",
                Prefix = "MAG",
                Pattern = "{PREFIX}-{SEQ:3}",
                ModuleCode = "warehouse",
                Description = "Codice magazzino (es. MAG-001)",
                SortOrder = 10
            },
            new()
            {
                EntityCode = "warehouse_article",
                EntityName = "Articolo Magazzino",
                Prefix = "WA",
                Pattern = "{PREFIX}{SEQ:6}",
                ModuleCode = "warehouse",
                Description = "Codice articolo magazzino (es. WA000001)",
                SortOrder = 20
            },
            new()
            {
                EntityCode = "warehouse_category",
                EntityName = "Categoria Articolo",
                Prefix = "CAT",
                Pattern = "{PREFIX}-{SEQ:4}",
                ModuleCode = "warehouse",
                Description = "Codice categoria (es. CAT-0001)",
                SortOrder = 30
            },
            new()
            {
                EntityCode = "stock_movement",
                EntityName = "Movimento Magazzino",
                Prefix = "MOV",
                Pattern = "{PREFIX}{YYYY}{MM}-{SEQ:5}",
                ResetSequenceMonthly = true,
                ModuleCode = "warehouse",
                Description = "Numero documento movimento (es. MOV202511-00001)",
                SortOrder = 40
            },
            new()
            {
                EntityCode = "inventory_count",
                EntityName = "Inventario",
                Prefix = "INV",
                Pattern = "{PREFIX}{YYYY}-{SEQ:4}",
                ResetSequenceYearly = true,
                ModuleCode = "warehouse",
                Description = "Codice inventario (es. INV2025-0001)",
                SortOrder = 50
            },
            new()
            {
                EntityCode = "article_batch",
                EntityName = "Lotto Articolo",
                Prefix = "LOT",
                Pattern = "{PREFIX}{YYYY}{MM}{DD}-{SEQ:4}",
                ResetSequenceMonthly = true,
                ModuleCode = "warehouse",
                Description = "Numero lotto (es. LOT20251129-0001)",
                SortOrder = 60
            },

            // Future: Purchases module
            new()
            {
                EntityCode = "purchase_order",
                EntityName = "Ordine Acquisto",
                Prefix = "ODA",
                Pattern = "{PREFIX}{YYYY}-{SEQ:5}",
                ResetSequenceYearly = true,
                ModuleCode = "purchases",
                Description = "Numero ordine acquisto (es. ODA2025-00001)",
                SortOrder = 10,
                IsEnabled = false // Disabilitato finché il modulo non è implementato
            },
            new()
            {
                EntityCode = "supplier",
                EntityName = "Fornitore",
                Prefix = "FOR",
                Pattern = "{PREFIX}-{SEQ:5}",
                ModuleCode = "purchases",
                Description = "Codice fornitore (es. FOR-00001)",
                SortOrder = 20,
                IsEnabled = false
            },

            // Future: Sales module
            new()
            {
                EntityCode = "sales_order",
                EntityName = "Ordine Vendita",
                Prefix = "ODV",
                Pattern = "{PREFIX}{YYYY}-{SEQ:5}",
                ResetSequenceYearly = true,
                ModuleCode = "sales",
                Description = "Numero ordine vendita (es. ODV2025-00001)",
                SortOrder = 10,
                IsEnabled = false
            },
            new()
            {
                EntityCode = "invoice",
                EntityName = "Fattura",
                Prefix = "FT",
                Pattern = "{PREFIX}{YYYY}/{SEQ:5}",
                ResetSequenceYearly = true,
                ModuleCode = "sales",
                Description = "Numero fattura (es. FT2025/00001)",
                SortOrder = 20,
                IsEnabled = false
            },
        };

        foreach (var config in defaults)
        {
            var exists = await _db.AutoCodes.AnyAsync(c => c.EntityCode == config.EntityCode);
            if (!exists)
            {
                config.CreatedAt = DateTime.Now;
                _db.AutoCodes.Add(config);
                _logger.LogInformation("Configurazione AutoCode creata per {EntityCode}", config.EntityCode);
            }
        }

        await _db.SaveChangesAsync();
    }

    #region Private Methods

    private bool ShouldResetSequence(AutoCode config, DateTime now)
    {
        if (config.ResetSequenceMonthly)
        {
            return config.LastResetYear != now.Year || config.LastResetMonth != now.Month;
        }

        if (config.ResetSequenceYearly)
        {
            return config.LastResetYear != now.Year;
        }

        return false;
    }

    private static string GenerateCodeFromPattern(string pattern, string? prefix, long sequence, DateTime date)
    {
        var code = pattern
            .Replace("{PREFIX}", prefix ?? "")
            .Replace("{YEAR}", date.Year.ToString())
            .Replace("{YYYY}", date.Year.ToString())
            .Replace("{YY}", date.Year.ToString().Substring(2))
            .Replace("{MONTH}", date.Month.ToString("D2"))
            .Replace("{MM}", date.Month.ToString("D2"))
            .Replace("{DAY}", date.Day.ToString("D2"))
            .Replace("{DD}", date.Day.ToString("D2"));

        // Gestisci {SEQ:n}
        code = SequencePattern.Replace(code, match =>
        {
            var digits = int.Parse(match.Groups[1].Value);
            return sequence.ToString($"D{digits}");
        });

        return code;
    }

    private static void ValidatePattern(string pattern)
    {
        // Verifica che ci sia almeno un placeholder sequenza
        if (!SequencePattern.IsMatch(pattern))
        {
            throw new ArgumentException(
                "Il pattern deve contenere almeno un placeholder {SEQ:n} per la sequenza numerica");
        }

        // Verifica che i placeholder siano validi
        var validPlaceholders = new[]
        {
            "{PREFIX}", "{YEAR}", "{YYYY}", "{YY}",
            "{MONTH}", "{MM}", "{DAY}", "{DD}"
        };

        var placeholderRegex = new Regex(@"\{[^}]+\}");
        var matches = placeholderRegex.Matches(pattern);

        foreach (Match match in matches)
        {
            var placeholder = match.Value;
            if (!SequencePattern.IsMatch(placeholder) && !validPlaceholders.Contains(placeholder))
            {
                throw new ArgumentException($"Placeholder non valido: {placeholder}");
            }
        }
    }

    #endregion
}

/// <summary>
/// DTO per l'aggiornamento di una configurazione AutoCode.
/// </summary>
public class AutoCodeUpdateDto
{
    public string? Prefix { get; set; }
    public string? Pattern { get; set; }
    public bool? ResetSequenceYearly { get; set; }
    public bool? ResetSequenceMonthly { get; set; }
    public bool? IsEnabled { get; set; }
    public bool? IsReadOnly { get; set; }
    public string? Description { get; set; }
}
