using Microsoft.EntityFrameworkCore;
using Microsoft.Data.SqlClient;
using Domain.Dtos.Stock;     // StockItemScanResultDto
using Domain.Generics;       // PagedResult<T>
using Models.Helpers;        // ToPagedResultAsync
using Models.Interfaces;     // IPhLSMStockItemRepository
using Models.Models;
using System.Data;         // PhronCareOperationsHubContext

namespace Models.Repositories.Stock
{
    public class PhLSMStockItemRepository(PhronCareOperationsHubContext context) : IPhLSMStockItemRepository
    {
        private readonly PhronCareOperationsHubContext _context = context;

        public async Task<PagedResult<StockItemScanResultDto>> SearchStockItemsAsync(
            string? codeOrText,
            string? batch,
            int? locationId,
            int? productType,
            int? traceabilityType,
            bool? plusProcess,
            int page,
            int take)
        {
            // Base: stock disponible (>0) + joins necesarios
            var baseQuery =
                from si in _context.PhLsmStockItems.AsNoTracking()
                join p in _context.PhLsmProducts.AsNoTracking() on si.ProductId equals p.Id
                join l in _context.PhLsmStockLocations.AsNoTracking() on si.LocationId equals l.Id into _loc
                from loc in _loc.DefaultIfEmpty()
                where si.Quantity > 0
                select new { si, p, loc };

            // ---- Filtros (case-insensitive donde aplica) ----
            if (!string.IsNullOrWhiteSpace(codeOrText))
            {
                var t = codeOrText.Trim().ToLower();
                baseQuery = baseQuery.Where(x =>
                    (!string.IsNullOrEmpty(x.p.FactoryCode) && x.p.FactoryCode.ToLower().Contains(t)) ||
                    (!string.IsNullOrEmpty(x.p.ExternalCode) && x.p.ExternalCode.ToLower().Contains(t)) ||
                    (!string.IsNullOrEmpty(x.p.Name) && x.p.Name.ToLower().Contains(t)) ||
                    (!string.IsNullOrEmpty(x.p.Descripcion) && x.p.Descripcion.ToLower().Contains(t)) ||
                    (!string.IsNullOrEmpty(x.si.Batch) && x.si.Batch.ToLower().Contains(t))
                );
            }

            if (!string.IsNullOrWhiteSpace(batch))
            {
                var b = batch.Trim().ToLower();
                baseQuery = baseQuery.Where(x => x.si.Batch != null && x.si.Batch.ToLower().Contains(b));
            }

            if (locationId.HasValue)
                baseQuery = baseQuery.Where(x => x.si.LocationId == locationId.Value);

            if (productType.HasValue)
                baseQuery = baseQuery.Where(x => x.p.ProductType == productType.Value);

            if (traceabilityType.HasValue)
                baseQuery = baseQuery.Where(x => x.p.TraceabilityType == traceabilityType.Value);

            if (plusProcess.HasValue)
                baseQuery = baseQuery.Where(x => x.p.PlusProcess == plusProcess.Value);

            // Orden lógico
            baseQuery = baseQuery
                .OrderBy(x => x.si.Expiration)
                .ThenBy(x => x.p.Name);

            // Proyección final a DTO (IQueryable<StockItemScanResultDto>)
            var dtoQuery = baseQuery.Select(x => new StockItemScanResultDto
            {
                StockItemId = x.si.Id,
                ProductId = x.p.Id,
                FactoryCode = x.p.FactoryCode ?? string.Empty,
                ExternalCode = x.p.ExternalCode,
                ProductName = x.p.Name ?? string.Empty,
                Description = x.p.Descripcion,
                LocationId = x.si.LocationId,
                LocationName = x.loc != null ? x.loc.Descripcion : null,
                Batch = x.si.Batch,
                Expiration = x.si.Expiration,
                TraceabilityType = x.p.TraceabilityType,
                AvailableQty = x.si.Quantity,
                PlusProcess = x.p.PlusProcess
            });

            page = page <= 0 ? 1 : page;
            take = take <= 0 ? 20 : take;

            // Paginado con tu helper (manteniendo el mismo patrón que ProductRepository)
            var paged = await dtoQuery.ToPagedResultAsync(page, take);

            return new PagedResult<StockItemScanResultDto>
            {
                Items = paged.Items,
                TotalItems = paged.TotalItems,
                Page = paged.Page,
                PageSize = paged.PageSize
            };
        }

        // -------- Búsqueda PARSEADA (GS1/DataMatrix) ----------
        public async Task<PagedResult<StockItemScanResultDto>> SearchStockItemsParsedAsync(
            string? gtin,
            string? batch,
            DateOnly? expiration,
            string? serial,
            int? locationId,
            int page,
            int take)
        {
            // 0) Sin ningún dato parseado -> vacío (evitar traer todo)
            if (string.IsNullOrWhiteSpace(gtin) &&
                string.IsNullOrWhiteSpace(batch) &&
                !expiration.HasValue &&
                string.IsNullOrWhiteSpace(serial))
            {
                return new PagedResult<StockItemScanResultDto>
                { Items = [], TotalItems = 0, Page = page <= 0 ? 1 : page, PageSize = take <= 0 ? 20 : take };
            }

            // 1) Resolver productos por GTIN/Factory/Regulatory si vino GTIN
            var productIds = new List<int>();
            if (!string.IsNullOrWhiteSpace(gtin))
            {
                var g = gtin.Trim();
                productIds = await _context.PhLsmProducts.AsNoTracking()
                    .Where(p => p.ExternalCode == g || p.FactoryCode == g || p.RegulatoryCode == g)
                    .Select(p => p.Id).ToListAsync();

                if (productIds.Count == 0)
                {
                    return new PagedResult<StockItemScanResultDto>
                    { Items = [], TotalItems = 0, Page = page <= 0 ? 1 : page, PageSize = take <= 0 ? 20 : take };
                }
            }

            // 2) Base query (stock disponible)
            var baseQuery =
                from si in _context.PhLsmStockItems.AsNoTracking()
                join p in _context.PhLsmProducts.AsNoTracking() on si.ProductId equals p.Id
                join l in _context.PhLsmStockLocations.AsNoTracking() on si.LocationId equals l.Id into _loc
                from loc in _loc.DefaultIfEmpty()
                where si.Quantity > 0
                select new { si, p, loc };

            if (productIds.Count > 0)
                baseQuery = baseQuery.Where(x => productIds.Contains(x.p.Id));

            if (locationId.HasValue)
                baseQuery = baseQuery.Where(x => x.si.LocationId == locationId.Value);

            // 3) Reglas por tipo de trazabilidad — PRIORIDAD: tipo REAL del producto si hay productIds
            if (productIds.Count > 0)
            {
                var traces = await _context.PhLsmProducts.AsNoTracking()
                    .Where(p => productIds.Contains(p.Id))
                    .Select(p => p.TraceabilityType)
                    .Distinct()
                    .ToListAsync();

                // Nota: normalmente habrá un solo tipo; si hubiera mezcla, estas ramas las contemplan.

                if (traces.Contains(4) || traces.Contains(5))
                {
                    // T4/T5: requieren serial. T5 además requiere expiration exacta.
                    if (string.IsNullOrWhiteSpace(serial))
                        return new PagedResult<StockItemScanResultDto>
                        { Items = [], TotalItems = 0, Page = page <= 0 ? 1 : page, PageSize = take <= 0 ? 20 : take };

                    var s = serial.Trim();

                    if (traces.Contains(5) && expiration.HasValue)
                    {
                        var e = expiration.Value;
                        baseQuery = baseQuery.Where(x =>
                            (x.p.TraceabilityType == 5 &&
                             x.si.Serial != null && x.si.Serial == s &&
                             x.si.Expiration.HasValue && x.si.Expiration.Value == e)
                            ||
                            // si el conjunto tuviera también productos T4, permitimos T4 por serial
                            (x.p.TraceabilityType == 4 &&
                             x.si.Serial != null && x.si.Serial == s)
                        );
                    }
                    else
                    {
                        baseQuery = baseQuery.Where(x =>
                            x.p.TraceabilityType == 4 &&
                            x.si.Serial != null && x.si.Serial == s
                        );
                    }
                }
                else if (traces.Contains(3))
                {
                    // T3: exige batch + expiration exactos
                    if (string.IsNullOrWhiteSpace(batch) || !expiration.HasValue)
                        return new PagedResult<StockItemScanResultDto>
                        { Items = [], TotalItems = 0, Page = page <= 0 ? 1 : page, PageSize = take <= 0 ? 20 : take };

                    var b = batch.Trim();
                    var e = expiration.Value;

                    baseQuery = baseQuery.Where(x =>
                        x.p.TraceabilityType == 3 &&
                        x.si.Batch == b &&
                        x.si.Expiration.HasValue && x.si.Expiration.Value == e
                    );
                }
                else if (traces.Contains(2))
                {
                    // T2: exige batch exacto (ignorar serial/expiration si vinieran)
                    if (string.IsNullOrWhiteSpace(batch))
                        return new PagedResult<StockItemScanResultDto>
                        { Items = [], TotalItems = 0, Page = page <= 0 ? 1 : page, PageSize = take <= 0 ? 20 : take };

                    var b = batch.Trim();

                    baseQuery = baseQuery.Where(x =>
                        x.p.TraceabilityType == 2 &&
                        x.si.Batch == b
                    );
                }
                else if (traces.Contains(1))
                {
                    // T1: sin traza (solo producto + ubicación)
                    baseQuery = baseQuery.Where(x => x.p.TraceabilityType == 1);
                }
                else
                {
                    return new PagedResult<StockItemScanResultDto>
                    { Items = [], TotalItems = 0, Page = page <= 0 ? 1 : page, PageSize = take <= 0 ? 20 : take };
                }
            }
            else
            {
                // Fallback: sin productIds -> decidir por presencia de datos (tu heurística original)
                if (!string.IsNullOrWhiteSpace(serial))
                {
                    var s = serial.Trim();

                    if (expiration.HasValue)
                    {
                        var e = expiration.Value;
                        baseQuery = baseQuery.Where(x =>
                            (x.p.TraceabilityType == 5 &&
                             x.si.Serial != null && x.si.Serial == s &&
                             x.si.Expiration.HasValue && x.si.Expiration.Value == e)
                            ||
                            (x.p.TraceabilityType == 4 &&
                             x.si.Serial != null && x.si.Serial == s)
                        );
                    }
                    else
                    {
                        baseQuery = baseQuery.Where(x =>
                            x.p.TraceabilityType == 4 &&
                            x.si.Serial != null && x.si.Serial == s
                        );
                    }
                }
                else if (!string.IsNullOrWhiteSpace(batch))
                {
                    var b = batch.Trim();

                    if (expiration.HasValue)
                    {
                        var e = expiration.Value;
                        baseQuery = baseQuery.Where(x =>
                            (x.p.TraceabilityType == 3 &&
                             x.si.Batch == b &&
                             x.si.Expiration.HasValue && x.si.Expiration.Value == e)
                            ||
                            (x.p.TraceabilityType == 2 &&
                             x.si.Batch == b)
                        );
                    }
                    else
                    {
                        baseQuery = baseQuery.Where(x =>
                            x.p.TraceabilityType == 2 &&
                            x.si.Batch == b
                        );
                    }
                }
                else
                {
                    // Solo aceptar T1 cuando no hay otra evidencia
                    baseQuery = baseQuery.Where(x => x.p.TraceabilityType == 1);
                }
            }

            // 4) Orden y proyección
            baseQuery = baseQuery.OrderBy(x => x.si.Expiration).ThenBy(x => x.p.Name);

            var dtoQuery = baseQuery.Select(x => new StockItemScanResultDto
            {
                StockItemId = x.si.Id,
                ProductId = x.p.Id,
                FactoryCode = x.p.FactoryCode ?? string.Empty,
                ExternalCode = x.p.ExternalCode,
                ProductName = x.p.Name ?? string.Empty,
                Description = x.p.Descripcion,
                LocationId = x.si.LocationId,
                LocationName = x.loc != null ? x.loc.Descripcion : null,
                Batch = x.si.Batch,
                Expiration = x.si.Expiration,
                Serial = x.si.Serial,
                TraceabilityType = x.p.TraceabilityType,
                AvailableQty = x.si.Quantity,
                PlusProcess = x.p.PlusProcess
            });

            page = page <= 0 ? 1 : page;
            take = take <= 0 ? 20 : take;

            var paged = await dtoQuery.ToPagedResultAsync(page, take);

            return new PagedResult<StockItemScanResultDto>
            {
                Items = paged.Items,
                TotalItems = paged.TotalItems,
                Page = paged.Page,
                PageSize = paged.PageSize
            };
        }

        public async Task<List<StockItemAvailabilityDto>> GetAvailabilityByStockItemIdsAsync(
            IEnumerable<int> stockItemIds)
        {
            try
            {
                var ids = stockItemIds?
                    .Where(x => x > 0)
                    .Distinct()
                    .ToList() ?? new List<int>();

                if (ids.Count == 0)
                    return new List<StockItemAvailabilityDto>();

                // TVP = Table Valued Parameter
                var tvp = new DataTable();
                tvp.Columns.Add("stockitem_id", typeof(int));

                foreach (var id in ids)
                    tvp.Rows.Add(id);

                var result = new List<StockItemAvailabilityDto>();
                var conn = _context.Database.GetDbConnection();
                var cs = _context.Database.GetConnectionString();
                if (conn.State != ConnectionState.Open)
                    await _context.Database.OpenConnectionAsync();


                await using var cmd = conn.CreateCommand();
                cmd.CommandText = "dbo.PhLSM_Stock_GetAvailabilityByStockItemIds";
                cmd.CommandType = CommandType.StoredProcedure;

                var pIds = new SqlParameter("@StockItemIds", SqlDbType.Structured)
                {
                    TypeName = "dbo.PhLSM_StockItemIdList",
                    Value = tvp
                };

                cmd.Parameters.Add(pIds);

                await using var reader = await cmd.ExecuteReaderAsync();

                var ordStockItemId = reader.GetOrdinal("StockitemId");
                var ordQuantity = reader.GetOrdinal("Quantity");
                var ordReserved = reader.GetOrdinal("ReservedQuantity");
                var ordAvailable = reader.GetOrdinal("AvailableQuantity");
                var ordSerial = reader.GetOrdinal("Serial");

                while (await reader.ReadAsync())
                {
                    result.Add(new StockItemAvailabilityDto
                    {
                        StockitemId = reader.GetInt32(ordStockItemId),
                        Quantity = reader.GetDecimal(ordQuantity),
                        ReservedQuantity = reader.GetDecimal(ordReserved),
                        AvailableQuantity = reader.GetDecimal(ordAvailable),
                        Serial = reader.IsDBNull(ordSerial)
                            ? null
                            : reader.GetString(ordSerial)
                    });
                }

                return result;
            }
            catch (Exception ex)
            {
                throw new Exception(ex.ToString());
            }
        }
    }
}
