using Domain.Dtos.Sales;
using Domain.Entities;
using Domain.Generics;
using Models.Interfaces;
using System.Reflection;
using System.Text.Json;
using Transversal.Services;

namespace Core.Services
{
    public class DeliveryNoteService(IPhSDeliveryNoteRepository deliveryNoteRepository) : IDeliveryNoteDom
    {
        private readonly IPhSDeliveryNoteRepository _deliveryNoteRepository = deliveryNoteRepository;

        public Task<PagedResult<DeliveryNoteSummaryDto>> SearchAsync(
            int? customerId,
            string? customerText,
            string? deliveryNoteNumber,
            int? quoteId,
            string? quoteNumber,
            DateTime? issueDateFrom,
            DateTime? issueDateTo,
            string? status,
            int page = 1,
            int pageSize = 50)
        {
            page = page <= 0 ? 1 : page;
            pageSize = pageSize <= 0 ? 50 : pageSize;

            return _deliveryNoteRepository.SearchAsync(
                customerId,
                string.IsNullOrWhiteSpace(customerText) ? null : customerText.Trim(),
                string.IsNullOrWhiteSpace(deliveryNoteNumber) ? null : deliveryNoteNumber.Trim(),
                quoteId,
                string.IsNullOrWhiteSpace(quoteNumber) ? null : quoteNumber.Trim(),
                issueDateFrom,
                issueDateTo,
                string.IsNullOrWhiteSpace(status) ? null : status.Trim(),
                page,
                pageSize);
        }

        public Task<DeliveryNoteDto?> GetDtoByIdAsync(int id)
        {
            if (id <= 0)
                throw new ArgumentOutOfRangeException(nameof(id), "El identificador del remito es inválido.");

            return _deliveryNoteRepository.GetDtoByIdAsync(id);
        }

        public async Task<byte[]> ExportFilteredToExcelAsync(DeliveryNoteSearchParams searchParams)
        {
            ArgumentNullException.ThrowIfNull(searchParams);

            try
            {
                var searchResult = await _deliveryNoteRepository.SearchAsync(
                    searchParams.CustomerId,
                    string.IsNullOrWhiteSpace(searchParams.CustomerText) ? null : searchParams.CustomerText.Trim(),
                    string.IsNullOrWhiteSpace(searchParams.DeliveryNoteNumber) ? null : searchParams.DeliveryNoteNumber.Trim(),
                    searchParams.QuoteId,
                    string.IsNullOrWhiteSpace(searchParams.QuoteNumber) ? null : searchParams.QuoteNumber.Trim(),
                    searchParams.IssueDateFrom,
                    searchParams.IssueDateTo,
                    string.IsNullOrWhiteSpace(searchParams.Status) ? null : searchParams.Status.Trim(),
                    searchParams.Page <= 0 ? 1 : searchParams.Page,
                    searchParams.PageSize <= 0 ? 50 : searchParams.PageSize);

                if (searchResult?.Items is null || !searchResult.Items.Any())
                    throw new Exception("No se encontraron remitos para exportar.");

                var items = searchResult.Items.ToList();
                var exportRows = new List<DeliveryNoteExcelRow>(items.Count);

                foreach (var deliveryNote in items)
                {
                    var dto = await _deliveryNoteRepository.GetDtoByIdAsync(deliveryNote.Id);
                    var snapshot = DeliveryNoteSnapshotInfo.FromJson(dto?.ExtraInfoJson);

                    exportRows.Add(new DeliveryNoteExcelRow
                    {
                        DeliveryNoteNumber = deliveryNote.DeliveryNoteNumber,
                        IssueDate = deliveryNote.IssueDate.ToString("dd/MM/yyyy"),
                        QuoteNumber = deliveryNote.QuoteNumber,
                        CustomerName = deliveryNote.CustomerName,
                        Status = deliveryNote.Status,
                        ProfessionalName = snapshot.ProfessionalName,
                        InstitutionName = snapshot.InstitutionName,
                        PatientName = snapshot.PatientName,
                        SurgeryDate = snapshot.SurgeryDate,
                        Observations = deliveryNote.Observations,
                        PrintCount = deliveryNote.PrintCount,
                        CreatedAt = deliveryNote.CreatedAt.ToString("dd/MM/yyyy HH:mm")
                    });
                }

                var stream = new XLSXExportBase();
                return stream.ExportExcel(exportRows);
            }
            catch (Exception ex)
            {
                var methodName = MethodBase.GetCurrentMethod()?.Name ?? "UnknownMethod";
                throw new Exception($"{methodName} Message: {ex.Message}", ex);
            }
        }

        public Task<DeliveryNoteDto?> GetDtoByDeliveryNoteNumberAsync(string deliveryNoteNumber)
        {
            if (string.IsNullOrWhiteSpace(deliveryNoteNumber))
                throw new ArgumentException("El número de remito es obligatorio.", nameof(deliveryNoteNumber));

            return _deliveryNoteRepository.GetDtoByDeliveryNoteNumberAsync(deliveryNoteNumber.Trim());
        }

        public Task<IEnumerable<DeliveryNoteDto>> GetDtosByQuoteIdAsync(int quoteId)
        {
            if (quoteId <= 0)
                throw new ArgumentOutOfRangeException(nameof(quoteId), "El identificador del presupuesto es inválido.");

            return _deliveryNoteRepository.GetDtosByQuoteIdAsync(quoteId);
        }

        public async Task<DeliveryNoteCreateResponse> CreateAndIssueDeliveryNoteAsync(DeliveryNoteCreateRequest request)
        {
            ArgumentNullException.ThrowIfNull(request);

            if (string.IsNullOrWhiteSpace(request.DeliveryNoteNumber))
                throw new ArgumentException("El número de remito es obligatorio.", nameof(request.DeliveryNoteNumber));

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

            if (request.IssueDate == default)
                throw new ArgumentException("La fecha de emisión es obligatoria.", nameof(request.IssueDate));

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

            if (request.Items.Any(i => i.Quantity <= 0))
                throw new InvalidOperationException("Todas las cantidades deben ser mayores a cero.");

            if (request.Items.Any(i => string.IsNullOrWhiteSpace(i.Description)))
                throw new InvalidOperationException("Todos los ítems deben incluir descripción.");

            var deliveryNoteNumber = request.DeliveryNoteNumber.Trim();

            if (await _deliveryNoteRepository.ExistsByDeliveryNoteNumberAsync(deliveryNoteNumber))
                throw new InvalidOperationException($"Ya existe un remito con el número '{deliveryNoteNumber}'.");

            var now = DateTime.Now;

            var entity = new EDeliveryNote
            {
                Deliverynotenumber = deliveryNoteNumber,
                QuoteId = request.QuoteId,
                Issuedate = request.IssueDate,
                CustomerId = request.CustomerId,
                Status = "Emitido",
                Observations = string.IsNullOrWhiteSpace(request.Observations) ? null : request.Observations.Trim(),
                ExtrainfoJson = string.IsNullOrWhiteSpace(request.ExtraInfoJson) ? null : request.ExtraInfoJson.Trim(),
                Printcount = 0,
                Createdat = now,
                PhSDeliveryNoteDetails = request.Items
                    .Select((item, index) => new EDeliveryNoteDetail
                    {
                        LineNumber = index + 1,
                        OriginType = item.OriginType,
                        OriginId = item.OriginId,
                        QuoteDetailId = item.QuoteDetailId,
                        Description = item.Description.Trim(),
                        Quantity = item.Quantity,
                        Notes = string.IsNullOrWhiteSpace(item.Notes) ? string.Empty : item.Notes.Trim(),
                        Createdat = now
                    })
                    .ToList()
            };

            var created = await _deliveryNoteRepository.CreateAsync(entity);

            return new DeliveryNoteCreateResponse
            {
                Id = created.Id,
                DeliveryNoteNumber = created.Deliverynotenumber
            };
        }

        private sealed class DeliveryNoteExcelRow
        {
            public string DeliveryNoteNumber { get; set; } = string.Empty;
            public string IssueDate { get; set; } = string.Empty;
            public string? QuoteNumber { get; set; }
            public string? CustomerName { get; set; }
            public string? Status { get; set; }
            public string? ProfessionalName { get; set; }
            public string? InstitutionName { get; set; }
            public string? PatientName { get; set; }
            public string? SurgeryDate { get; set; }
            public string? Observations { get; set; }
            public int PrintCount { get; set; }
            public string CreatedAt { get; set; } = string.Empty;
        }

        private sealed class DeliveryNoteSnapshotInfo
        {
            public string? ProfessionalName { get; private set; }
            public string? InstitutionName { get; private set; }
            public string? PatientName { get; private set; }
            public string? SurgeryDate { get; private set; }

            public static DeliveryNoteSnapshotInfo FromJson(string? extraInfoJson)
            {
                var snapshot = new DeliveryNoteSnapshotInfo();

                if (string.IsNullOrWhiteSpace(extraInfoJson))
                    return snapshot;

                try
                {
                    using var document = JsonDocument.Parse(extraInfoJson);
                    var root = document.RootElement;

                    snapshot.ProfessionalName = ReadString(root, "professional", "professionalName", "doctor", "doctorName", "medico", "medicoNombre");
                    snapshot.InstitutionName = ReadString(root, "institution", "institutionName", "hospital", "hospitalName", "institucion", "institucionNombre");
                    snapshot.PatientName = ReadString(root, "patient", "patientName", "paciente", "pacienteNombre");
                    snapshot.SurgeryDate = ReadDate(root, "surgeryDate", "estimatedDate", "fechaCirugia", "surgery_date", "estimated_date");
                }
                catch
                {
                    return snapshot;
                }

                return snapshot;
            }

            private static string? ReadString(JsonElement root, params string[] propertyNames)
            {
                foreach (var propertyName in propertyNames)
                {
                    if (!TryGetPropertyIgnoreCase(root, propertyName, out var value))
                        continue;

                    if (value.ValueKind == JsonValueKind.String)
                        return value.GetString();

                    if (value.ValueKind != JsonValueKind.Null && value.ValueKind != JsonValueKind.Undefined)
                        return value.ToString();
                }

                return null;
            }

            private static string? ReadDate(JsonElement root, params string[] propertyNames)
            {
                foreach (var propertyName in propertyNames)
                {
                    if (!TryGetPropertyIgnoreCase(root, propertyName, out var value))
                        continue;

                    if (value.ValueKind == JsonValueKind.String)
                    {
                        var raw = value.GetString();

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

                        if (DateTime.TryParse(raw, out var parsedDate))
                            return parsedDate.ToString("dd/MM/yyyy");

                        return raw;
                    }

                    if (value.ValueKind != JsonValueKind.Null && value.ValueKind != JsonValueKind.Undefined)
                        return value.ToString();
                }

                return null;
            }

            private static bool TryGetPropertyIgnoreCase(JsonElement element, string propertyName, out JsonElement value)
            {
                foreach (var property in element.EnumerateObject())
                {
                    if (string.Equals(property.Name, propertyName, StringComparison.OrdinalIgnoreCase))
                    {
                        value = property.Value;
                        return true;
                    }
                }

                value = default;
                return false;
            }
        }
    }
}
