using Core.Interfaces.Stock;
using Domain.Constants;
using Domain.Dtos.Stock;
using Domain.Entities;
using Domain.Generics;
using Models.Interfaces;
using System.Reflection;
using Transversal.Services;

namespace Core.Services.Stock
{
    public class ExpeditionService : IExpeditionDom
    {
        #region Declaraciones
        private readonly IExpeditionRepository _repo;
        private readonly IPhLSMStockItemRepository _stockItemRepository;
        public ExpeditionService(
            IExpeditionRepository repo,
            IPhLSMStockItemRepository stockItemRepository)
        {
            _repo = repo;
            _stockItemRepository = stockItemRepository;
        }
        #endregion

        #region Guardado completo de expedicion (encabezado + detalles)
        public async Task<(int Id, string ExpeditionNumber)> CreateAndIssueAsync(
            ELSExpeditionHeader header,
            IEnumerable<ELSExpeditionDetail> details,
            int formSeriesId)
        {
            if (header is null)
                throw new ArgumentNullException(nameof(header));

            if (details is null || !details.Any())
                throw new InvalidOperationException("Debe incluir al menos un ítem.");

            if (formSeriesId <= 0)
                throw new ArgumentOutOfRangeException(nameof(formSeriesId), "Serie inválida.");

            var detailList = details.ToList();

            ValidateNoDuplicateStockItems(detailList);
            await ValidateSerializedConflictsAsync(detailList);
            await ValidateStockAvailabilityAsync(detailList);

            header.PhLsmExpeditionDetails = detailList;

            return await _repo.CreateFullExpeditionAsync(header, formSeriesId);
        }
        private static void ValidateNoDuplicateStockItems(List<ELSExpeditionDetail> detailList)
        {
            var duplicateIds = detailList
                .Where(d => d.StockitemId > 0)
                .GroupBy(d => d.StockitemId)
                .Where(g => g.Count() > 1)
                .Select(g => g.Key)
                .OrderBy(x => x)
                .ToList();

            if (duplicateIds.Count == 0)
                return;

            var msg = "No se puede emitir la expedición. " +
                      "El mismo StockItem fue seleccionado más de una vez: " +
                      string.Join(", ", duplicateIds);

            throw new InvalidOperationException(msg);
        }
        private async Task ValidateSerializedConflictsAsync(List<ELSExpeditionDetail> detailList)
        {
            var requestedStockItemIds = detailList
                .Where(d => d.StockitemId > 0)
                .Select(d => d.StockitemId)
                .Distinct()
                .ToList();

            if (requestedStockItemIds.Count == 0)
                return;

            var conflicts = await _repo.CheckStockItemConflictsAsync(
                requestedStockItemIds,
                ignoreExpeditionId: null);

            if (conflicts.Count == 0)
                return;

            var lines = new List<string>
    {
        $"No se puede emitir la expedición: se detectaron {conflicts.Count} stock items serializados ya asignados a expediciones activas."
    };

            foreach (var conflict in conflicts
                .OrderBy(x => x.StockitemId)
                .ThenBy(x => x.Expeditionnumber))
            {
                var statusLabel = ((ExpeditionStatus)conflict.Status).ToLabel();
                lines.Add($"• StockItem {conflict.StockitemId} → {conflict.Expeditionnumber} ({statusLabel})");
            }

            throw new InvalidOperationException(string.Join(Environment.NewLine, lines));
        }
        private async Task ValidateStockAvailabilityAsync(List<ELSExpeditionDetail> detailList)
        {
            var requestedByStockItem = detailList
                .Where(d => d.StockitemId > 0 && d.Quantity > 0)
                .GroupBy(d => d.StockitemId)
                .Select(g => new
                {
                    StockitemId = g.Key,
                    RequestedQuantity = g.Sum(x => x.Quantity)
                })
                .ToList();

            if (requestedByStockItem.Count == 0)
                return;

            var availability = await _stockItemRepository.GetAvailabilityByStockItemIdsAsync(
                requestedByStockItem.Select(x => x.StockitemId));

            var availabilityMap = availability.ToDictionary(x => x.StockitemId);

            var errors = new List<string>();

            foreach (var request in requestedByStockItem.OrderBy(x => x.StockitemId))
            {
                if (!availabilityMap.TryGetValue(request.StockitemId, out var stock))
                {
                    errors.Add($"• StockItem {request.StockitemId} → no fue encontrado en stock.");
                    continue;
                }

                var hasSerial = !string.IsNullOrWhiteSpace(stock.Serial);

                // Los serializados ya se validan por exclusividad en ValidateSerializedConflictsAsync
                if (hasSerial)
                    continue;

                if (request.RequestedQuantity > stock.AvailableQuantity)
                {
                    errors.Add(
                        $"• StockItem {request.StockitemId} → solicitado: {request.RequestedQuantity}, disponible: {stock.AvailableQuantity}.");
                }
            }

            if (errors.Count == 0)
                return;

            var lines = new List<string>
            {
                "No se puede emitir la expedición: algunos stock items no serializados no tienen cantidad disponible suficiente."
            };

            lines.AddRange(errors);

            throw new InvalidOperationException(string.Join(Environment.NewLine, lines));
        }
        #endregion

        // Otros métodos de la clase...
        public Task<ExpeditionDto?> GetDtoByExpeditionNumberAsync(string expeditionNumber)
        {
            throw new NotImplementedException();
        }
        public Task<PagedResult<ExpeditionDto>> SearchAsync(
            string? expeditionNumber,
            string? status,
            DateTime? issueDateFrom,
            DateTime? issueDateTo,
            int? locationId,
            int page,
            int pageSize)
            => _repo.SearchAsync(expeditionNumber, status, issueDateFrom, issueDateTo, locationId, page, pageSize);
        public Task<ExpeditionDto?> GetDtoByIdAsync(int id)
            => _repo.GetDtoByIdAsync(id);
        public async Task<byte[]> ExportFilteredToExcelAsync(ExpeditionSearchParams searchParams)
        {
            try
            {
                // Realiza la búsqueda de clientes con los parámetros proporcionados
                var searchResult = await _repo.SearchAsync(
                    searchParams.Number,
                    searchParams.Status,
                    searchParams.From,
                    searchParams.To,
                    searchParams.LocationId,
                    searchParams.Page,
                    searchParams.PageSize
                );
                // Verifica que se hayan encontrado resultados
                if (searchResult?.Items is null || !searchResult.Items.Any())
                {
                    throw new Exception("No se encontraron clientes para exportar.");
                }
                // Llamamos a un método que exporta los datos a Excel
                var stream = new XLSXExportBase();
                // Convertimos los resultados de la búsqueda a un formato adecuado para el exportador
                var items = searchResult.Items.Select(c => new
                {
                    c.Expeditionnumber,
                    Issuedate = c.Issuedate.ToString("yyyy-MM-dd"),          // ← string
                    Createdat = c.Createdat.ToString("yyyy-MM-dd HH:mm"),    // ← string

                    c.Status,
                    c.LocationId,
                    c.ExternalReference,
                    c.TicketId,
                    c.ExtrainfoJson,
                    c.Observations,
                    c.TotalItems
                }).ToList();
                // Genera el archivo Excel
                var excelFile = stream.ExportExcel(items);
                // Devuelve el archivo Excel como un array de bytes
                return excelFile;
            }
            catch (Exception ex)
            {
                var methodName = MethodBase.GetCurrentMethod()?.Name ?? "UnknownMethod";
                throw new Exception($"{ex.Message}", ex);
            }
        }
        public async Task MarkInTransitAsync(int expeditionId)
        {
            if (expeditionId <= 0)
                throw new ArgumentException("El identificador de la expedición no es válido.");

            await _repo.MarkInTransitAsync(expeditionId);
        }
    }
}
