using Core.Interfaces;
using Domain.Constants;
using Domain.Dtos.Sales;
using Domain.Entities;
using Domain.Generics;
using Models.Interfaces;
using System.Text.Json;

namespace Core.Services
{
    public class SalesDocumentService(IPhSSalesDocumentRepository salesDocumentRepository) : ISalesDocumentDom
    {
        private readonly IPhSSalesDocumentRepository _salesDocumentRepository = salesDocumentRepository;

        public Task<PagedResult<SalesDocumentSummaryDto>> SearchAsync(
            int? customerId,
            string? customerText,
            int? quoteId,
            int? documentType,
            int? status,
            DateTime? issueDateFrom,
            DateTime? issueDateTo,
            int page = 1,
            int pageSize = 50)
        {
            return _salesDocumentRepository.SearchAsync(
                customerId,
                customerText,
                quoteId,
                documentType,
                status,
                issueDateFrom,
                issueDateTo,
                page,
                pageSize);
        }


        public Task<PagedResult<SalesDocumentDeliveryNoteCandidateDto>> SearchDeliveryNoteCandidatesAsync(
            int? customerId,
            string? customerText,
            string? deliveryNoteNumber,
            int? quoteId,
            DateTime? issueDateFrom,
            DateTime? issueDateTo,
            int page = 1,
            int pageSize = 50)
        {
            return _salesDocumentRepository.SearchDeliveryNoteCandidatesAsync(
                customerId,
                customerText,
                deliveryNoteNumber,
                quoteId,
                issueDateFrom,
                issueDateTo,
                page,
                pageSize);
        }

        public Task<List<SalesDocumentDeliveryNoteItemCandidateDto>> GetDeliveryNoteItemCandidatesForSalesDocumentAsync(IReadOnlyCollection<int> deliveryNoteIds)
        {
            ArgumentNullException.ThrowIfNull(deliveryNoteIds);

            var ids = deliveryNoteIds
                .Where(x => x > 0)
                .Distinct()
                .ToList();

            if (ids.Count == 0)
                throw new InvalidOperationException("Debe seleccionar al menos un remito emitido.");

            return _salesDocumentRepository.GetDeliveryNoteItemCandidatesForSalesDocumentAsync(ids);
        }

        public async Task<SalesDocumentCreateResponse> CreateFromDeliveryNoteItemsAsync(SalesDocumentCreateFromDeliveryNoteItemsRequest request)
        {
            ArgumentNullException.ThrowIfNull(request);

            if (string.IsNullOrWhiteSpace(request.Currency))
                throw new ArgumentException("La moneda es obligatoria.", nameof(request.Currency));

            var selectedQuantities = request.Items
                .Where(x => x.DeliveryNoteDetailId > 0)
                .GroupBy(x => x.DeliveryNoteDetailId)
                .ToDictionary(x => x.Key, x => x.Sum(i => i.SelectedQuantity));

            if (selectedQuantities.Count == 0)
                throw new InvalidOperationException("Debe seleccionar al menos un item de remito.");

            if (selectedQuantities.Any(x => x.Value <= 0))
                throw new InvalidOperationException("Todas las cantidades seleccionadas deben ser mayores a cero.");

            var candidates = await _salesDocumentRepository.GetDeliveryNoteItemCandidatesByDetailIdsForSalesDocumentAsync(selectedQuantities.Keys.ToList());

            if (candidates.Count != selectedQuantities.Count)
                throw new InvalidOperationException("Uno o mas items seleccionados no existen o no pertenecen a remitos emitidos.");

            foreach (var candidate in candidates)
            {
                var selectedQuantity = selectedQuantities[candidate.DeliveryNoteDetailId];
                if (candidate.PendingQuantity <= 0)
                    throw new InvalidOperationException($"El item {candidate.Description} del remito {candidate.DeliveryNoteNumber} no tiene saldo pendiente.");

                if (selectedQuantity > candidate.PendingQuantity)
                    throw new InvalidOperationException($"La cantidad seleccionada para {candidate.Description} supera el saldo pendiente.");

                if (!candidate.QuoteDetailId.HasValue || candidate.QuoteDetailId.Value <= 0)
                    throw new InvalidOperationException($"El item {candidate.Description} no tiene detalle de presupuesto asociado.");

                if (candidate.ApprovedUnitPrice <= 0)
                    throw new InvalidOperationException($"El item {candidate.Description} no tiene precio aprobado.");
            }

            var fiscalCustomerIds = candidates.Select(x => x.CustomerId).Distinct().ToList();
            if (fiscalCustomerIds.Count != 1)
                throw new InvalidOperationException("No se pueden agrupar items de remitos de distintos clientes fiscales.");

            var now = DateTime.Now;
            var details = new List<ESalesDocumentDetail>();
            var lineNumber = 1;

            foreach (var candidate in candidates.OrderBy(x => x.DeliveryNoteIssueDate).ThenBy(x => x.DeliveryNoteId).ThenBy(x => x.LineNumber))
            {
                var selectedQuantity = selectedQuantities[candidate.DeliveryNoteDetailId];
                var selectedAmount = candidate.ApprovedUnitPrice * selectedQuantity;
                var billedPercentage = candidate.DeliveredQuantity <= 0
                    ? 0
                    : Math.Round(selectedQuantity / candidate.DeliveredQuantity * 100, 2);

                var originSnapshot = JsonSerializer.Serialize(new
                {
                    deliveryNoteId = candidate.DeliveryNoteId,
                    deliveryNoteNumber = candidate.DeliveryNoteNumber,
                    deliveryNoteIssueDate = candidate.DeliveryNoteIssueDate,
                    deliveryNoteDetailId = candidate.DeliveryNoteDetailId,
                    deliveryNoteLineNumber = candidate.LineNumber,
                    quoteId = candidate.QuoteId,
                    quoteNumber = candidate.QuoteNumber,
                    quoteDetailId = candidate.QuoteDetailId,
                    customerId = candidate.CustomerId,
                    customerName = candidate.CustomerName,
                    originalQuantity = candidate.DeliveredQuantity,
                    alreadyBilledQuantity = candidate.AlreadyBilledQuantity,
                    pendingQuantity = candidate.PendingQuantity,
                    selectedQuantity,
                    deliveryNoteExtraInfo = candidate.DeliveryNoteExtraInfoJson
                });

                var detail = new ESalesDocumentDetail
                {
                    LineNumber = lineNumber++,
                    OriginType = SalesDocumentOriginType.DeliveryNote.ToStorageCode(),
                    OriginId = candidate.DeliveryNoteDetailId,
                    QuoteDetailId = candidate.QuoteDetailId,
                    ProductId = candidate.ProductId,
                    Description = candidate.Description.Trim(),
                    Quantity = selectedQuantity,
                    AuthorizedUnitPrice = candidate.ApprovedUnitPrice,
                    AuthorizedAmount = candidate.ApprovedAmount,
                    BilledPercentage = billedPercentage,
                    UnitPrice = candidate.ApprovedUnitPrice,
                    NetAmount = selectedAmount,
                    TaxAmount = 0,
                    TotalAmount = selectedAmount,
                    OriginSnapshotJson = originSnapshot,
                    Createdat = now
                };

                if (candidate.QuoteId.HasValue)
                {
                    detail.PhSSalesDocumentCoverages.Add(new ESalesDocumentCoverage
                    {
                        QuoteId = candidate.QuoteId.Value,
                        QuoteDetailId = candidate.QuoteDetailId,
                        CoverageType = (int)SalesDocumentCoverageType.Direct,
                        CoveragePercentage = billedPercentage,
                        CoverageAmount = selectedAmount,
                        PeriodFrom = request.PeriodFrom,
                        PeriodTo = request.PeriodTo,
                        Notes = $"Coverage parcial desde remito {candidate.DeliveryNoteNumber} linea {candidate.LineNumber}",
                        Createdat = now
                    });
                }

                details.Add(detail);
            }

            var totalAmount = details.Sum(x => x.TotalAmount);
            if (totalAmount <= 0)
                throw new InvalidOperationException("El total del documento debe ser mayor a cero.");

            var quoteIds = candidates
                .Where(x => x.QuoteId.HasValue)
                .Select(x => x.QuoteId!.Value)
                .Distinct()
                .ToList();

            var operations = candidates
                .GroupBy(x => new
                {
                    x.DeliveryNoteId,
                    x.DeliveryNoteNumber,
                    x.QuoteId,
                    x.QuoteNumber,
                    x.CustomerId,
                    x.CustomerName,
                    x.DeliveryNoteIssueDate
                })
                .Select(x => new SalesDocumentDeliveryNoteOperationSnapshotDto
                {
                    DeliveryNoteId = x.Key.DeliveryNoteId,
                    DeliveryNoteNumber = x.Key.DeliveryNoteNumber,
                    QuoteId = x.Key.QuoteId,
                    QuoteNumber = x.Key.QuoteNumber,
                    CustomerId = x.Key.CustomerId,
                    CustomerName = x.Key.CustomerName,
                    IssueDate = x.Key.DeliveryNoteIssueDate,
                    Amount = x.Sum(i => i.ApprovedUnitPrice * selectedQuantities[i.DeliveryNoteDetailId]),
                    Coverage = x.Key.CustomerName
                })
                .ToList();

            var extraInfoJson = JsonSerializer.Serialize(new
            {
                source = "DeliveryNoteItems",
                grouped = operations.Count > 1,
                partialBilling = true,
                coverageDefinition = "Entidad financiadora / pagadora responsable",
                operations
            });

            var entity = new ESalesDocument
            {
                DocumentType = request.DocumentType,
                Status = (int)SalesDocumentStatus.Draft,
                QuoteId = quoteIds.Count == 1 ? quoteIds[0] : null,
                CustomerId = fiscalCustomerIds[0],
                BillToCustomerId = fiscalCustomerIds[0],
                IssueDate = request.IssueDate ?? now,
                Currency = request.Currency.Trim(),
                ExchangeRate = request.ExchangeRate <= 0 ? 1 : request.ExchangeRate,
                NetAmount = totalAmount,
                TaxAmount = 0,
                TotalAmount = totalAmount,
                Observations = request.Observations,
                ExtraInfoJson = extraInfoJson,
                PeriodFrom = request.PeriodFrom,
                PeriodTo = request.PeriodTo,
                Createdat = now,
                PhSSalesDocumentDetails = details
            };

            var created = await _salesDocumentRepository.CreateFromDeliveryNoteItemsAsync(entity);

            return new SalesDocumentCreateResponse
            {
                Id = created.Id,
                InternalDocumentNumber = created.InternalDocumentNumber
            };
        }

        public async Task<SalesDocumentCreateResponse> CreateFromDeliveryNotesAsync(SalesDocumentCreateFromDeliveryNotesRequest request)
        {
            ArgumentNullException.ThrowIfNull(request);

            var deliveryNoteIds = request.DeliveryNoteIds
                .Where(x => x > 0)
                .Distinct()
                .ToList();

            if (deliveryNoteIds.Count == 0)
                throw new InvalidOperationException("Debe seleccionar al menos un remito emitido.");

            if (string.IsNullOrWhiteSpace(request.Currency))
                throw new ArgumentException("La moneda es obligatoria.", nameof(request.Currency));

            var deliveryNotes = await _salesDocumentRepository.GetDeliveryNotesForSalesDocumentAsync(deliveryNoteIds);

            if (deliveryNotes.Count != deliveryNoteIds.Count)
                throw new InvalidOperationException("Uno o más remitos seleccionados no existen.");

            var notIssued = deliveryNotes.Where(x => !string.Equals(x.Status, "Emitido", StringComparison.OrdinalIgnoreCase)).ToList();
            if (notIssued.Count > 0)
                throw new InvalidOperationException("Solo se pueden facturar remitos en estado Emitido.");

            var alreadyAssociated = deliveryNotes.Where(x => x.SalesInvoiceId.HasValue && x.SalesInvoiceId.Value > 0).ToList();
            if (alreadyAssociated.Count > 0)
                throw new InvalidOperationException("Uno o más remitos ya están asociados a un Sales Document.");

            var fiscalCustomerIds = deliveryNotes.Select(x => x.CustomerId).Distinct().ToList();
            if (fiscalCustomerIds.Count != 1)
                throw new InvalidOperationException("No se pueden agrupar remitos de distintos clientes fiscales.");

            if (deliveryNotes.Any(x => x.Items.Count == 0))
                throw new InvalidOperationException("Todos los remitos seleccionados deben tener ítems.");

            var now = DateTime.Now;
            var details = new List<ESalesDocumentDetail>();
            var coverages = new List<ESalesDocumentCoverage>();
            var lineNumber = 1;

            foreach (var deliveryNote in deliveryNotes)
            {
                foreach (var item in deliveryNote.Items)
                {
                    var unitPrice = item.ApprovedUnitPrice ?? item.OriginalUnitPrice ?? 0;
                    var approvedAmount = item.ApprovedAmount ?? (unitPrice * item.Quantity);

                    if (approvedAmount <= 0)
                        throw new InvalidOperationException($"El remito {deliveryNote.DeliveryNoteNumber} contiene ítems sin precio aprobado.");

                    var originSnapshot = JsonSerializer.Serialize(new
                    {
                        deliveryNoteId = deliveryNote.Id,
                        deliveryNoteNumber = deliveryNote.DeliveryNoteNumber,
                        deliveryNoteIssueDate = deliveryNote.IssueDate,
                        quoteId = deliveryNote.QuoteId,
                        quoteNumber = deliveryNote.QuoteNumber,
                        customerId = deliveryNote.CustomerId,
                        customerName = deliveryNote.CustomerName,
                        deliveryNoteExtraInfo = deliveryNote.ExtraInfoJson
                    });

                    details.Add(new ESalesDocumentDetail
                    {
                        LineNumber = lineNumber++,
                        OriginType = SalesDocumentOriginType.DeliveryNote.ToStorageCode(),
                        OriginId = deliveryNote.Id,
                        QuoteDetailId = item.QuoteDetailId,
                        ProductId = item.ProductId,
                        Description = item.Description.Trim(),
                        Quantity = item.Quantity,
                        AuthorizedUnitPrice = unitPrice,
                        AuthorizedAmount = approvedAmount,
                        BilledPercentage = 100,
                        UnitPrice = unitPrice,
                        NetAmount = approvedAmount,
                        TaxAmount = 0,
                        TotalAmount = approvedAmount,
                        OriginSnapshotJson = originSnapshot,
                        Createdat = now
                    });

                    if (deliveryNote.QuoteId.HasValue)
                    {
                        coverages.Add(new ESalesDocumentCoverage
                        {
                            QuoteId = deliveryNote.QuoteId.Value,
                            QuoteDetailId = item.QuoteDetailId,
                            CoverageType = (int)SalesDocumentCoverageType.Direct,
                            CoveragePercentage = 100,
                            CoverageAmount = approvedAmount,
                            PeriodFrom = request.PeriodFrom,
                            PeriodTo = request.PeriodTo,
                            Notes = $"Coverage desde remito {deliveryNote.DeliveryNoteNumber}",
                            Createdat = now
                        });
                    }
                }
            }

            var totalAmount = details.Sum(x => x.TotalAmount);
            if (totalAmount <= 0)
                throw new InvalidOperationException("El total del documento debe ser mayor a cero.");

            var operations = deliveryNotes.Select(x => new SalesDocumentDeliveryNoteOperationSnapshotDto
            {
                DeliveryNoteId = x.Id,
                DeliveryNoteNumber = x.DeliveryNoteNumber,
                QuoteId = x.QuoteId,
                QuoteNumber = x.QuoteNumber,
                CustomerId = x.CustomerId,
                CustomerName = x.CustomerName,
                IssueDate = x.IssueDate,
                Amount = x.Items.Sum(i => i.ApprovedAmount ?? ((i.ApprovedUnitPrice ?? i.OriginalUnitPrice ?? 0) * i.Quantity)),
                Coverage = x.CustomerName
            }).ToList();

            var extraInfoJson = JsonSerializer.Serialize(new
            {
                source = "DeliveryNotes",
                grouped = deliveryNotes.Count > 1,
                coverageDefinition = "Entidad financiadora / pagadora responsable",
                operations
            });

            var entity = new ESalesDocument
            {
                DocumentType = request.DocumentType,
                Status = (int)SalesDocumentStatus.Draft,
                QuoteId = deliveryNotes.Count == 1 ? deliveryNotes[0].QuoteId : null,
                CustomerId = fiscalCustomerIds[0],
                BillToCustomerId = fiscalCustomerIds[0],
                IssueDate = request.IssueDate ?? now,
                Currency = request.Currency.Trim(),
                ExchangeRate = request.ExchangeRate <= 0 ? 1 : request.ExchangeRate,
                NetAmount = totalAmount,
                TaxAmount = 0,
                TotalAmount = totalAmount,
                Observations = request.Observations,
                ExtraInfoJson = extraInfoJson,
                PeriodFrom = request.PeriodFrom,
                PeriodTo = request.PeriodTo,
                Createdat = now,
                PhSSalesDocumentDetails = details,
                PhSSalesDocumentCoverages = coverages
            };

            var created = await _salesDocumentRepository.CreateFromDeliveryNotesAsync(entity, deliveryNoteIds);

            return new SalesDocumentCreateResponse
            {
                Id = created.Id,
                InternalDocumentNumber = created.InternalDocumentNumber
            };
        }

        public async Task<SalesDocumentCreateResponse> CreateAsync(SalesDocumentCreateRequest request)
        {
            ArgumentNullException.ThrowIfNull(request);

            if (request.CustomerId <= 0)
                throw new ArgumentException("Debe seleccionar un cliente.", nameof(request.CustomerId));

            if (request.BillToCustomerId <= 0)
                throw new ArgumentException("Debe seleccionar un cliente de facturación.", nameof(request.BillToCustomerId));

            if (string.IsNullOrWhiteSpace(request.Currency))
                throw new ArgumentException("La moneda es obligatoria.", nameof(request.Currency));

            if (request.Details is null || request.Details.Count == 0)
                throw new InvalidOperationException("Debe incluir al menos un detail.");

            if (request.Coverage is null || request.Coverage.Count == 0)
                throw new InvalidOperationException("Debe incluir coverage.");

            foreach (var detail in request.Details)
                ValidateDetail(detail);

            var netAmount = request.Details.Sum(x => x.NetAmount);
            var taxAmount = request.Details.Sum(x => x.TaxAmount);
            var totalAmount = request.Details.Sum(x => x.TotalAmount);

            if (totalAmount <= 0)
                throw new InvalidOperationException("El total del documento debe ser mayor a cero.");

            var now = DateTime.Now;

            var entity = new ESalesDocument
            {
                FormseriesId = request.FormseriesId,
                DocumentType = request.DocumentType,
                FiscalVoucherType = request.FiscalVoucherType,
                FiscalVoucherLetter = request.FiscalVoucherLetter,
                Status = (int)SalesDocumentStatus.Draft,
                QuoteId = request.QuoteId,
                CustomerId = request.CustomerId,
                BillToCustomerId = request.BillToCustomerId,
                IssueDate = request.IssueDate ?? now,
                Currency = request.Currency.Trim(),
                ExchangeRate = request.ExchangeRate <= 0 ? 1 : request.ExchangeRate,
                NetAmount = netAmount,
                TaxAmount = taxAmount,
                TotalAmount = totalAmount,
                AssociatedDocumentType = request.AssociatedDocumentType,
                AssociatedDocumentNumber = request.AssociatedDocumentNumber,
                AssociatedDocumentDate = request.AssociatedDocumentDate,
                Observations = request.Observations,
                ExtraInfoJson = request.ExtraInfoJson,
                PeriodFrom = request.PeriodFrom,
                PeriodTo = request.PeriodTo,
                Createdat = now,
                PhSSalesDocumentDetails = request.Details.Select(x => new ESalesDocumentDetail
                {
                    LineNumber = x.LineNumber,
                    OriginType = x.OriginType.ToStorageCode(),
                    OriginId = ResolveOriginId(x),
                    QuoteDetailId = ResolveQuoteDetailId(x),
                    ProductId = x.ProductId,
                    Description = x.Description.Trim(),
                    Quantity = x.Quantity,
                    AuthorizedUnitPrice = x.AuthorizedUnitPrice,
                    AuthorizedAmount = x.AuthorizedAmount,
                    BilledPercentage = x.BilledPercentage,
                    UnitPrice = x.UnitPrice,
                    NetAmount = x.NetAmount,
                    TaxAmount = x.TaxAmount,
                    TotalAmount = x.TotalAmount,
                    OriginSnapshotJson = x.OriginSnapshotJson,
                    Createdat = now
                }).ToList(),
                PhSSalesDocumentCoverages = request.Coverage.Select(x => new ESalesDocumentCoverage
                {
                    QuoteId = x.QuoteId,
                    QuoteDetailId = x.QuoteDetailId,
                    CoverageType = x.CoverageType,
                    CoveragePercentage = x.CoveragePercentage,
                    CoverageAmount = x.CoverageAmount,
                    PeriodFrom = x.PeriodFrom,
                    PeriodTo = x.PeriodTo,
                    Notes = x.Notes,
                    Createdat = now
                }).ToList()
            };

            var created = await _salesDocumentRepository.CreateAsync(entity);

            return new SalesDocumentCreateResponse
            {
                Id = created.Id,
                InternalDocumentNumber = created.InternalDocumentNumber
            };
        }

        public Task<SalesDocumentDto?> GetDtoByIdAsync(int id)
        {
            if (id <= 0)
                throw new ArgumentOutOfRangeException(nameof(id));

            return _salesDocumentRepository.GetDtoByIdAsync(id);
        }

        public async Task<SalesDocumentDraftPreviewDto?> GetDraftPreviewAsync(int id)
        {
            if (id <= 0)
                throw new ArgumentOutOfRangeException(nameof(id));

            var document = await _salesDocumentRepository.GetDtoByIdAsync(id);
            return document == null ? null : BuildDraftPreview(document);
        }

        public async Task<SalesDocumentDraftPreviewDto?> UpdateDraftReviewAsync(int id, SalesDocumentDraftReviewDto review)
        {
            if (id <= 0)
                throw new ArgumentOutOfRangeException(nameof(id));

            ArgumentNullException.ThrowIfNull(review);

            var current = await _salesDocumentRepository.GetDtoByIdAsync(id);
            if (current == null)
                return null;

            if (current.Status != (int)SalesDocumentStatus.Draft)
                throw new InvalidOperationException("Solo se pueden revisar Sales Documents en estado Draft.");

            var updated = await _salesDocumentRepository.UpdateDraftReviewAsync(id, review);
            return updated == null ? null : BuildDraftPreview(updated);
        }

        public async Task<SalesDocumentDraftPreviewDto?> ValidateDraftAsync(int id)
        {
            if (id <= 0)
                throw new ArgumentOutOfRangeException(nameof(id));

            var current = await _salesDocumentRepository.GetDtoByIdAsync(id);
            if (current == null)
                return null;

            var validation = BuildDraftValidation(current);
            if (!validation.IsValid)
                throw new InvalidOperationException(string.Join(" ", validation.Errors));

            var validated = await _salesDocumentRepository.ValidateDraftAsync(id);
            return validated == null ? null : BuildDraftPreview(validated);
        }

        private static SalesDocumentDraftPreviewDto BuildDraftPreview(SalesDocumentDto document)
        {
            return new SalesDocumentDraftPreviewDto
            {
                Document = document,
                OriginDeliveryNotes = BuildOriginDeliveryNotes(document),
                Validation = BuildDraftValidation(document)
            };
        }

        private static SalesDocumentDraftValidationDto BuildDraftValidation(SalesDocumentDto document)
        {
            var validation = new SalesDocumentDraftValidationDto
            {
                HasDetails = document.Details.Any(),
                HasValidAmounts = document.TotalAmount > 0 && document.NetAmount >= 0 && document.TaxAmount >= 0,
                HasCustomer = document.CustomerId > 0,
                IsDraft = document.Status == (int)SalesDocumentStatus.Draft
            };

            if (!validation.HasDetails)
                validation.Errors.Add("El documento debe tener detalles.");

            if (!validation.HasValidAmounts)
                validation.Errors.Add("El documento debe tener importes validos.");

            if (!validation.HasCustomer)
                validation.Errors.Add("El documento debe tener cliente asignado.");

            if (!validation.IsDraft)
                validation.Errors.Add("El documento debe permanecer en estado Draft.");

            return validation;
        }

        private static List<DeliveryNoteSummaryDto> BuildOriginDeliveryNotes(SalesDocumentDto document)
        {
            return document.Details
                .Where(x => string.Equals(x.OriginType, SalesDocumentOriginType.DeliveryNote.ToStorageCode(), StringComparison.OrdinalIgnoreCase))
                .Select(TryBuildDeliveryNoteSummary)
                .Where(x => x is not null)
                .Select(x => x!)
                .GroupBy(x => x.Id)
                .Select(x => x.First())
                .OrderBy(x => x.IssueDate)
                .ThenBy(x => x.Id)
                .ToList();
        }

        private static DeliveryNoteSummaryDto? TryBuildDeliveryNoteSummary(SalesDocumentDetailDto detail)
        {
            if (string.IsNullOrWhiteSpace(detail.OriginSnapshotJson))
            {
                return detail.OriginId.HasValue
                    ? new DeliveryNoteSummaryDto
                    {
                        Id = detail.OriginId.Value,
                        DeliveryNoteNumber = $"Remito #{detail.OriginId.Value}"
                    }
                    : null;
            }

            try
            {
                using var jsonDocument = JsonDocument.Parse(detail.OriginSnapshotJson);
                var root = jsonDocument.RootElement;

                var deliveryNoteId = root.TryGetProperty("deliveryNoteId", out var idProperty) && idProperty.TryGetInt32(out var parsedId)
                    ? parsedId
                    : detail.OriginId;

                if (!deliveryNoteId.HasValue)
                    return null;

                var issueDate = DateTime.MinValue;
                if (root.TryGetProperty("deliveryNoteIssueDate", out var dateProperty) &&
                    dateProperty.ValueKind == JsonValueKind.String &&
                    DateTime.TryParse(dateProperty.GetString(), out var parsedDate))
                {
                    issueDate = parsedDate;
                }

                return new DeliveryNoteSummaryDto
                {
                    Id = deliveryNoteId.Value,
                    DeliveryNoteNumber = ReadString(root, "deliveryNoteNumber") ?? $"Remito #{deliveryNoteId.Value}",
                    QuoteId = ReadInt(root, "quoteId"),
                    QuoteNumber = ReadString(root, "quoteNumber"),
                    IssueDate = issueDate,
                    CustomerId = ReadInt(root, "customerId") ?? 0,
                    CustomerName = ReadString(root, "customerName") ?? string.Empty,
                    Status = "Emitido"
                };
            }
            catch
            {
                return detail.OriginId.HasValue
                    ? new DeliveryNoteSummaryDto
                    {
                        Id = detail.OriginId.Value,
                        DeliveryNoteNumber = $"Remito #{detail.OriginId.Value}"
                    }
                    : null;
            }
        }

        private static string? ReadString(JsonElement root, string propertyName)
        {
            return root.TryGetProperty(propertyName, out var property) && property.ValueKind == JsonValueKind.String
                ? property.GetString()
                : null;
        }

        private static int? ReadInt(JsonElement root, string propertyName)
        {
            if (!root.TryGetProperty(propertyName, out var property))
                return null;

            if (property.ValueKind == JsonValueKind.Number && property.TryGetInt32(out var value))
                return value;

            return null;
        }
        private static void ValidateDetail(SalesDocumentCreateDetailRequest detail)
        {
            if (detail.LineNumber <= 0)
                throw new ArgumentException("El número de línea del detail debe ser mayor a cero.", nameof(detail.LineNumber));

            if (!Enum.IsDefined(typeof(SalesDocumentOriginType), detail.OriginType))
                throw new ArgumentException("El tipo de origen del detail no es válido.", nameof(detail.OriginType));

            if (string.IsNullOrWhiteSpace(detail.Description))
                throw new ArgumentException("La descripción del detail es obligatoria.", nameof(detail.Description));

            if (detail.Quantity <= 0)
                throw new ArgumentException("La cantidad del detail debe ser mayor a cero.", nameof(detail.Quantity));

            var hasOriginId = detail.OriginId.HasValue && detail.OriginId.Value > 0;
            var hasQuoteDetailId = detail.QuoteDetailId.HasValue && detail.QuoteDetailId.Value > 0;

            if (detail.OriginType != SalesDocumentOriginType.Manual && !hasOriginId && !hasQuoteDetailId)
                throw new ArgumentException("Debe informar OriginId o QuoteDetailId para trazabilidad del origen.", nameof(detail.OriginId));

            if (detail.OriginType == SalesDocumentOriginType.QuoteDetail && !hasQuoteDetailId && !hasOriginId)
                throw new ArgumentException("Debe informar QuoteDetailId u OriginId para líneas originadas en presupuesto.", nameof(detail.QuoteDetailId));
        }

        private static int? ResolveOriginId(SalesDocumentCreateDetailRequest detail)
        {
            if (detail.OriginId.HasValue && detail.OriginId.Value > 0)
                return detail.OriginId;

            return detail.OriginType == SalesDocumentOriginType.QuoteDetail
                ? detail.QuoteDetailId
                : null;
        }

        private static int? ResolveQuoteDetailId(SalesDocumentCreateDetailRequest detail)
        {
            if (detail.QuoteDetailId.HasValue && detail.QuoteDetailId.Value > 0)
                return detail.QuoteDetailId;

            return detail.OriginType == SalesDocumentOriginType.QuoteDetail
                ? detail.OriginId
                : null;
        }

    }
}
