//using Domain.Dtos.Stock;

using Transversal.Models;

namespace Transversal.Services
{
    public class Gs1CodeParser
    {
        // GS1 FNC1 (ASCII 29)
        private const char FNC1 = (char)29;

        /// <summary>
        /// Parsea un string GS1 (GS1-128/EAN-128/DataMatrix) con AIs en cualquier orden.
        /// Soporta separadores FNC1 (ASCII 29), '$' y espacios. AIs: (01),(10),(11),(17),(21),(22).
        /// </summary>
        public static Gs1ScanResult Parse(string raw)
        {
            var result = new Gs1ScanResult
            {
                Raw = raw?.Trim() ?? string.Empty
            };

            if (string.IsNullOrWhiteSpace(raw))
                return result;

            // 1) Normalizar: $ y espacios -> FNC1; quitar saltos
            var s = raw.Trim()
                       .Replace("\r", "")
                       .Replace("\n", "")
                       .Replace("$", FNC1.ToString());

            // algunos escáneres meten espacios sueltos entre AIs/valores
            while (s.Contains("  ")) s = s.Replace("  ", " ");
            s = s.Replace(" ", string.Empty);

            int i = 0;
            while (i < s.Length)
            {
                if (s[i] == FNC1) { i++; continue; }
                if (i + 2 > s.Length) break;

                // Intentar leer AI de dos dígitos (los que usamos acá)
                var ai = s.Substring(i, 2);
                i += 2;

                switch (ai)
                {
                    case "01": // GTIN (14 fijos)
                        if (TryTakeFixed(s, ref i, 14, out var gtin))
                            result.Gtin = gtin;
                        break;

                    case "17": // Expiración YYMMDD (6 fijos)
                        if (TryTakeFixed(s, ref i, 6, out var yymmdd)
                            && TryParseYyMmDdToDateTime(yymmdd, out var expDt))
                        {
                            result.ExpirationDate = expDt;
                        }
                        break;

                    case "11": // Fabricación YYMMDD (6 fijos) -> opcional guardar
                        if (TryTakeFixed(s, ref i, 6, out var mfgYymmdd)
                            && TryParseYyMmDdToDateTime(mfgYymmdd, out var mfgDt))
                        {
                            // Si querés guardar: agregá ManufacturingDate en tu DTO
                            // result.ManufacturingDate = mfgDt;
                        }
                        break;

                    case "10": // Lote (var-length)
                        result.Lot = TakeVariable(s, ref i);
                        break;

                    case "21": // Serie (var-length)
                        result.Serial = TakeVariable(s, ref i);
                        break;

                    case "22": // Variante (var-length) 
                        result.Variant = TakeVariable(s, ref i);
                        break;

                    default:
                        // Heurística: si el char previo a este AI no era FNC1, podría ser que no era AI real.
                        // Retrocede 1 y avanza de a 1 hasta próximo FNC1 o final.
                        i -= 1;
                        SkipUnknownUntilFnc1(s, ref i);
                        break;
                }
            }

            // Heurística: 17 seguido de 10 sin FNC1 (p.ej. ...17YYMMDD10LOTE)
            if (result.ExpirationDate.HasValue && string.IsNullOrEmpty(result.Lot))
            {
                var idx17 = s.IndexOf("17", StringComparison.Ordinal);
                if (idx17 >= 0 && idx17 + 8 < s.Length) // "17" + 6 chars de fecha = +8
                {
                    var after17 = s.Substring(idx17 + 8);
                    var idx10 = after17.IndexOf("10", StringComparison.Ordinal);
                    if (idx10 >= 0)
                    {
                        var start = idx17 + 8 + idx10 + 2;
                        var lot = ReadUntilFnc1OrEnd(s, start);
                        if (!string.IsNullOrEmpty(lot))
                            result.Lot = lot;
                    }
                }
            }
            return result;
        }

        // === Helpers ===
        private static bool TryTakeFixed(string s, ref int i, int length, out string value)
        {
            value = string.Empty;
            if (i + length > s.Length) return false;
            value = s.Substring(i, length);
            i += length;
            return true;
        }

        /// <summary>
        /// Extrae un valor de longitud variable (por ejemplo, Batch o Serial) del código GS1.
        /// Cortamos únicamente cuando encontramos un separador FNC1.
        /// No se intenta detectar un posible AI dentro del valor, ya que hay casos donde el valor
        /// legítimamente contiene secuencias como "22" o "17" que podrían confundirse con un AI.
        /// Ejemplo: un lote "52360227" no debe cortarse en "52360" al detectar "22".
        /// </summary>
        private static string TakeVariable(string s, ref int i)
        {
            int start = i;
            while (i < s.Length && s[i] != FNC1) i++;
            return s.Substring(start, i - start);
        }

        //private static bool LooksLikeNextAi(string s, int index)
        //{
        //    if (index + 1 >= s.Length) return false;
        //    return char.IsDigit(s[index]) && char.IsDigit(s[index + 1]) &&
        //           (s[index] == '0' || s[index] == '1' || s[index] == '2') &&
        //           (s.Substring(index, 2) is "01" or "10" or "11" or "17" or "21" or "22");
        //}

        private static void SkipUnknownUntilFnc1(string s, ref int i)
        {
            while (i < s.Length && s[i] != FNC1) i++;
            if (i < s.Length && s[i] == FNC1) i++; // consumir FNC1 si lo hay
        }

        private static bool TryParseYyMmDdToDateTime(string yymmdd, out DateTime dt)
        {
            dt = default;
            if (yymmdd?.Length != 6) return false;

            int yy = int.Parse(yymmdd.Substring(0, 2));
            int mm = int.Parse(yymmdd.Substring(2, 2));
            int dd = int.Parse(yymmdd.Substring(4, 2));

            int year = 2000 + yy;

            try
            {
                dt = new DateTime(year, mm, dd);
                return true;
            }
            catch
            {
                return false;
            }
        }

        private static string ReadUntilFnc1OrEnd(string s, int start)
        {
            int i = start;
            while (i < s.Length && s[i] != FNC1) i++;
            return s.Substring(start, i - start);
        }
    }
    //public class Gs1ScanResult
    //{
    //    /// <summary>
    //    /// GTIN (AI 01). Identificador global del producto.
    //    /// Puede provenir del código de fábrica, del GTIN oficial o de un código alternativo.
    //    /// </summary>
    //    public string? Gtin { get; set; }

    //    /// <summary>
    //    /// Lote / Batch (AI 10). Identifica el lote de producción del producto.
    //    /// </summary>
    //    public string? Lot { get; set; }

    //    /// <summary>
    //    /// Fecha de vencimiento (AI 17).
    //    /// Se expresa en formato YYYY-MM-DD. Puede ser null si el producto no aplica trazabilidad por fecha.
    //    /// </summary>
    //    public DateTime? ExpirationDate { get; set; }

    //    /// <summary>
    //    /// Número de serie (AI 21).
    //    /// Aplica para productos con trazabilidad por unidad.
    //    /// </summary>
    //    public string? Serial { get; set; }

    //    /// <summary>
    //    /// Código original leído (raw input).
    //    /// Puede ser un código GS1 completo o un valor tipeado manualmente.
    //    /// </summary>
    //    public string? Raw { get; set; }

    //    /// <summary>
    //    /// Variante o presentación (AI 22).
    //    /// Identifica una sub-versión del GTIN en caso de existir.
    //    /// </summary>
    //    public string? Variant { get; set; }
    //}

}
