using AyCode.Core.Interfaces;
using AyCode.Entities;
using AyCode.Services.Nav.Ekaer;
using AyCode.Services.Nav.Ekaer.Models;
using FruitBank.Common.Dtos;
using FruitBank.Common.Entities;
using Nop.Core.Domain.Customers;
using TradeReasonType = AyCode.Services.Nav.Ekaer.Models.Common.TradeReasonType;
using TradeType = AyCode.Services.Nav.Ekaer.Models.Common.TradeType;

namespace FruitBank.Common.Services.Ekaer;

/// <inheritdoc cref="IShippingToEkaerMapper"/>
/// <remarks>
/// Tiszta (állapotmentes) leképező. A bejövő (<c>ShippingDocument</c>-csoport) és a kimenő (<c>OrderDto</c>) forrás
/// előbb egy irány-független <see cref="EkaerConsignment"/>-re képződik (<see cref="ToConsignment(IReadOnlyCollection{ShippingDocument}, EkaerCompanyInfo)"/>
/// / <see cref="ToConsignment(OrderDto, EkaerCompanyInfo)"/>), majd EBBŐL épül a NAV tradeCard (<see cref="BuildTradeCard"/>).
/// Így az irányfüggő tudás a két adapterre szorul, a NAV-build közös. A fel-/lerakodási hely és a jármű már a NAV-típus
/// (a saját telephely kész <see cref="LocationType"/>, mezővesztés nélkül átengedve); a feladó/címzett normalizálása a
/// build-ben történik. A <c>TradeType</c>/<c>TradeReasonType</c> enumokat aliasszal hozzuk be a névütközés miatt.
/// </remarks>
public sealed class ShippingToEkaerMapper : IShippingToEkaerMapper
{
    /// <summary>A NAV EKÁER magyar rendszer — a „belföld" alapértéke HU (a vevő-ország feloldásáig).</summary>
    private const string HomeCountry = "HU";

    /// <summary>Kimenő pénznem — jelenleg minden HUF (a vevő/rendelés devizája az OrderDto-ban még nincs leképezve).</summary>
    private const string OutboundCurrency = "HUF";

    private readonly IEkaerSettings _settings;

    public ShippingToEkaerMapper(IEkaerSettings settings)
        => _settings = settings ?? throw new ArgumentNullException(nameof(settings));

    // ── Belépési pontok (vékony wrapperek a köztes modell + a közös build köré) ───────────────────

    public IReadOnlyList<TradeCardOperationType> MapShipping(Shipping shipping, EkaerCompanyInfo company, OperationType operation = OperationType.Create)
    {
        ArgumentNullException.ThrowIfNull(shipping);
        ArgumentNullException.ThrowIfNull(company);

        var operations = new List<TradeCardOperationType>();
        var index = 0;

        // Granularitás: egy ShippingDocument → egy tradeCard (lásd EKAER_TODO #5).
        foreach (var document in shipping.ShippingDocuments ?? [])
        {
            document.Shipping ??= shipping; // a vontató/fuvarozó a Shippingről jön
            index++;
            operations.Add(new TradeCardOperationType
            {
                Index = index,
                Operation = operation,
                TradeCard = BuildTradeCard(ToConsignment([document], company)),
            });
        }

        return operations;
    }

    public TradeCardType MapDocument(ShippingDocument document, EkaerCompanyInfo company)
    {
        ArgumentNullException.ThrowIfNull(document);
        ArgumentNullException.ThrowIfNull(company);
        return BuildTradeCard(ToConsignment([document], company));
    }

    public TradeCardType MapOrder(OrderDto order, EkaerCompanyInfo company)
    {
        ArgumentNullException.ThrowIfNull(order);
        ArgumentNullException.ThrowIfNull(company);
        return BuildTradeCard(ToConsignment(order, company));
    }

    // ── Adapterek: forrás → normalizált EkaerConsignment (az EGYETLEN irányfüggő rész) ────────────

    /// <summary>Bejövő: egy vagy több <c>ShippingDocument</c> (a hívó (Shipping, Partner, PartnerDepot)-ra csoportosít) → egy
    /// szállítmány a csoport ÖSSZES tételével. Egy dokumentummal a generáláshoz, a csoporttal a kötelezettség-summához.</summary>
    public EkaerConsignment ToConsignment(IReadOnlyCollection<ShippingDocument> documents, EkaerCompanyInfo company)
    {
        ArgumentNullException.ThrowIfNull(documents);
        ArgumentNullException.ThrowIfNull(company);
        if (documents.Count == 0) throw new ArgumentException("Legalább egy ShippingDocument szükséges.", nameof(documents));

        var first = documents.First();
        var partner = first.Partner;   // a csoport azonos partneré (a hívó (Shipping, Partner, PartnerDepot)-ra csoportosít)
        var shipping = first.Shipping; // azonos Shipping; a kapunál lehet null (a jármű ott nem kell)
        var rateToHuf = SafeRateToHuf(partner?.Currency);
        // A depot TaxId-ja Partner-passthrough (a nested Partner-t nem töltjük külön query-vel) — ugyanaz a minta,
        // mint a MapShipping `document.Shipping ??=` visszakötése.
        if (first.PartnerDepot is { } depot) depot.Partner ??= partner;

        var lines = documents
            .SelectMany(d => d.ShippingItems ?? [])
            .Select(item => new EkaerLine
            {
                ExternalId = item.Id.ToString(),
                TradeReason = TradeReasonType.S, // S = értékesítés ÉS beszerzés (egy kód); az A kivezetett, D-nél csak S adható
                Vtsz = NormalizeVtsz(item.ProductDto?.Gtin),
                Name = item.ProductName,
                // A szállítólevélen szereplő bruttó — a bejelentés a kísérő dokumentum adatairól megy (mint a value),
                // nem a (későbbi) mérlegelésről; méretlen tétel így nem 0 kg-mal jelenik meg.
                WeightKg = item.GrossWeightOnDocument,
                ValueHuf = EkaerValueCalculator.ItemValueHuf(item, rateToHuf),
            })
            .ToList();

        return new EkaerConsignment
        {
            ForeignKey = first.Id,
            IsOutgoing = false,
            Currency = partner?.Currency,
            Seller = PartnerEndpoint(partner),
            Buyer = CompanyEndpoint(company),
            // A felrakodási hely a DEPOT címe (T-D8R4) — a generate-út betölti (LoadWith) és guardolja (TryDepotError).
            // A kapu-útvonalon a depot nincs betöltve → partner-központi cím, ami ott rendben van: a kötelezettség-döntés címet nem használ.
            LoadLocation = BuildLocation((ICompanyInfoBase?)first.PartnerDepot ?? partner),
            UnloadLocation = company.Site,           // a saját telephely (kész LocationType — mezővesztés nélkül átengedve)
            Lines = lines,
            Vehicle = BuildVehicle(shipping?.CargoTruck),
            Trailer = BuildVehicle(shipping?.CargoTrailer),
            CarrierName = shipping?.CargoPartner?.Name,
        };
    }

    /// <summary>Kimenő: egy rendelés → egy szállítmány. Nincs Shipping; a kötelezettséget a rendelésre nézzük.</summary>
    public EkaerConsignment ToConsignment(OrderDto order, EkaerCompanyInfo company)
    {
        ArgumentNullException.ThrowIfNull(order);
        ArgumentNullException.ThrowIfNull(company);

        var customer = order.Customer;
        var rateToHuf = SafeRateToHuf(OutboundCurrency); // jelenleg minden HUF

        var lines = (order.OrderItemDtos ?? [])
            .Select(item => new EkaerLine
            {
                ExternalId = item.Id.ToString(),
                TradeReason = TradeReasonType.S, // kimenő áru = értékesítés
                Vtsz = NormalizeVtsz(item.ProductDto?.Gtin),
                Name = item.ProductDto?.Name,
                WeightKg = item.GrossWeight,
                ValueHuf = EkaerValueCalculator.ItemValueHuf(item, rateToHuf),
            })
            .ToList();

        return new EkaerConsignment
        {
            ForeignKey = order.Id,
            IsOutgoing = true,
            Currency = OutboundCurrency,
            Seller = CompanyEndpoint(company),
            Buyer = CustomerEndpoint(customer),
            LoadLocation = company.Site,
            // A lerakodási hely az orderre választott telephely (snapshot); ha nincs beállítva,
            // fallback a vevő fő címére (a korábbi viselkedés).
            UnloadLocation = BuildSiteLocation(order.Site) ?? BuildCustomerLocation(customer),
            Lines = lines,
            // Kimenőnél nincs külön szállítmányozó: a vevő maga viszi el → carrierText üres (a NAV nem követeli).
            CarrierName = null,
            // A vonó jármű rendszáma az orderre mentett LicensePlate-ből (a customer adja meg, ill. az auto-hozzárendelés).
            Vehicle = BuildVehicleFromPlate(order.LicensePlate),
        };
    }

    // ── Közös build: normalizált szállítmány → NAV tradeCard (a korábbi két ág helyett egy) ───────

    public TradeCardType BuildTradeCard(EkaerConsignment consignment)
    {
        ArgumentNullException.ThrowIfNull(consignment);

        var tradeCard = new TradeCardType
        {
            TradeType = ResolveTradeType(consignment),
            ModByCarrierEnabled = false, // mi jelentünk; a fuvarozó alapból nem módosíthat

            SellerName = consignment.Seller.Name,
            SellerVatNumber = NormalizeVatNumber(consignment.Seller.VatNumber),
            SellerCountry = NormalizeCountryCode(consignment.Seller.CountryCode, 2),
            SellerAddress = Truncate(consignment.Seller.FullAddress, 200),

            DestinationName = consignment.Buyer.Name,
            DestinationVatNumber = NormalizeVatNumber(consignment.Buyer.VatNumber),
            DestinationCountry = NormalizeCountryCode(consignment.Buyer.CountryCode, 2),
            DestinationAddress = Truncate(consignment.Buyer.FullAddress, 200),

            CarrierText = consignment.CarrierName,

            // A helyek kész NAV LocationType-ok (a saját telephely érintetlenül) — átengedve.
            LoadLocation = consignment.LoadLocation,
            UnloadLocation = consignment.UnloadLocation,
        };

        if (consignment.Vehicle != null) tradeCard.Vehicle = consignment.Vehicle;
        if (consignment.Trailer != null) tradeCard.Vehicle2 = consignment.Trailer;

        foreach (var line in consignment.Lines) tradeCard.Items.Add(BuildItem(line));
        return tradeCard;
    }

    /// <summary>Belföld (a feladó és a címzett országkódja megegyezik) → <c>D</c>; eltérő országok → kimenőnél
    /// export (<c>E</c>), bejövőnél import (<c>I</c>).</summary>
    private static TradeType ResolveTradeType(EkaerConsignment c)
    {
        if (string.Equals(c.Seller.CountryCode?.Trim(), c.Buyer.CountryCode?.Trim(), StringComparison.OrdinalIgnoreCase))
            return TradeType.D;
        return c.IsOutgoing ? TradeType.E : TradeType.I;
    }

    private static TradeCardItemType BuildItem(EkaerLine line) => new()
    {
        ItemExternalId = line.ExternalId,
        TradeReason = line.TradeReason,
        ProductVtsz = line.Vtsz,        // már normalizált (az adapterben)
        ProductName = line.Name,
        Weight = (decimal)line.WeightKg,
        Value = line.ValueHuf,
    };

    // ── Forrás → normalizálatlan végpont (feladó/címzett: név/adószám/ország/egysoros cím) ─────────

    private static EkaerEndpoint PartnerEndpoint(ICompanyInfoBase? partner) => new()
    {
        Name = partner?.Name,
        VatNumber = partner?.TaxId,
        CountryCode = partner?.CountryCode,
        FullAddress = partner?.FullAddress,
    };

    private static EkaerEndpoint CompanyEndpoint(EkaerCompanyInfo company) => new()
    {
        Name = company.Name,
        VatNumber = company.TaxId,
        CountryCode = company.CountryCode,
        FullAddress = company.FullAddress,
    };

    /// <summary>A vevő mint fél. Ország jelenleg HU — a Customer.CountryId→ISO feloldás (export E) külön feladat.</summary>
    private static EkaerEndpoint CustomerEndpoint(Customer? customer) => new()
    {
        Name = customer?.Company,
        VatNumber = customer?.VatNumber,
        CountryCode = HomeCountry,
        FullAddress = ComposeCustomerAddress(customer),
    };

    // ── Forrás → NAV LocationType / jármű (a saját telephelyet az adapter közvetlenül adja) ────────

    /// <summary>Felrakodási hely a beszállító adataiból (bejövő). A NAV magyar feladónál a Phone/Email-t is kéri, ami
    /// az entitásban nincs — lásd EKAER_TODO #6.</summary>
    private static LocationType? BuildLocation(ICompanyInfoBase? seller)
    {
        if (seller is null) return null;
        return new LocationType
        {
            Name = seller.Name,
            VatNumber = NormalizeVatNumber(seller.TaxId),
            Country = NormalizeCountryCode(seller.CountryCode, 2),
            ZipCode = NormalizeZipCode(seller.PostalCode),
            City = seller.City,
            Street = seller.Street,
        };
    }

    /// <summary>Lerakodási hely a vevő adataiból (kimenő). Ország jelenleg HU (belföld).</summary>
    private static LocationType? BuildCustomerLocation(Customer? customer)
    {
        if (customer is null) return null;
        return new LocationType
        {
            Name = customer.Company,
            VatNumber = NormalizeVatNumber(customer.VatNumber),
            Country = HomeCountry,
            ZipCode = NormalizeZipCode(customer.ZipPostalCode),
            City = customer.City,
            Street = customer.StreetAddress,
        };
    }

    private static BasicVehicleDetailType? BuildVehicle(CargoTruck? truck)
    {
        if (truck is null) return null;
        return new BasicVehicleDetailType
        {
            PlateNumber = NormalizePlateNumber(truck.LicencePlate),
            Country = NormalizeCountryCode(truck.CountryCode, 3),
        };
    }


    /// <summary>Lerakodási hely az orderre választott telephely-snapshotból (kimenő). A snapshot már a rendelés
    /// pillanatában befagyott LocationType-mezőket hordoz (név, adószám, irsz., város, utca, ország).</summary>
    private static LocationType? BuildSiteLocation(OrderSiteSnapshot? site)
    {
        if (site is null) return null;
        return new LocationType
        {
            Name = site.Name,
            VatNumber = NormalizeVatNumber(site.VatNumber),
            Country = NormalizeCountryCode(site.CountryCode, 2),
            ZipCode = NormalizeZipCode(site.ZipCode),
            City = site.City,
            Street = site.Street,
        };
    }

    /// <summary>A vonó jármű az orderre mentett rendszámból (kimenő). Ország jelenleg belföld (HU); a rendszámot
    /// a meglévő NAV-pattern szerint normalizáljuk. Üres/érvénytelen rendszám → nincs jármű (a validátor jelzi).</summary>
    private static BasicVehicleDetailType? BuildVehicleFromPlate(string? plateNumber)
    {
        var plate = NormalizePlateNumber(plateNumber);
        if (plate is null) return null;
        return new BasicVehicleDetailType
        {
            PlateNumber = plate,
            Country = NormalizeCountryCode(HomeCountry, 3),
        };
    }


    /// <summary>A számla-pénznem → HUF szorzó, NEM dobó változat (a kapu külföldinél küszöb nélkül jelent, ott az érték
    /// nem kell): HUF → 1; külföldi + érvényes árfolyam → árfolyam; külföldi + hiányzó árfolyam → 0 (a tétel-érték null lesz).
    /// Generáláskor a service config-kapuja (TryConfigError) előbb elvágja a hiányzó-árfolyamos külföldi esetet.</summary>
    private double SafeRateToHuf(string? currency)
        => EkaerValueCalculator.IsHuf(currency) ? 1d : (_settings.EurHufRate > 0 ? _settings.EurHufRate : 0d);

    /// <summary>A vevő egysoros címe (irsz + város + utca) a NAV <c>destinationAddress</c>-hez.</summary>
    private static string? ComposeCustomerAddress(Customer? customer)
    {
        if (customer is null) return null;
        var parts = new[] { customer.ZipPostalCode, customer.City, customer.StreetAddress, customer.StreetAddress2 }
            .Where(p => !string.IsNullOrWhiteSpace(p));
        return EmptyToNull(string.Join(" ", parts).Trim());
    }

    // ── Normalizálók (NAV pattern-ek) ─────────────────────────────────────────────────────────────

    /// <summary>Adószám normalizálása. Pattern: <c>[0-9A-Z-]{1,15}</c>.</summary>
    private static string? NormalizeVatNumber(string? value)
    {
        if (string.IsNullOrWhiteSpace(value)) return null;
        var cleaned = new string([.. value.ToUpperInvariant().Where(c => char.IsAsciiLetterOrDigit(c) || c == '-')]);
        return Truncate(EmptyToNull(cleaned), 15);
    }

    /// <summary>Országkód normalizálása. Pattern: <c>[A-Z]{1,maxLen}</c> (seller/location: 2, jármű: 3).</summary>
    private static string? NormalizeCountryCode(string? value, int maxLen)
    {
        if (string.IsNullOrWhiteSpace(value)) return null;
        var cleaned = new string([.. value.ToUpperInvariant().Where(char.IsAsciiLetter)]);
        return Truncate(EmptyToNull(cleaned), maxLen);
    }

    /// <summary>Rendszám normalizálása. Pattern: <c>[A-Z0-9ÖŐÜŰ]{4,15}</c> — kötőjel/szóköz NEM engedett.</summary>
    private static string? NormalizePlateNumber(string? value)
    {
        if (string.IsNullOrWhiteSpace(value)) return null;
        var cleaned = new string([.. value.ToUpperInvariant().Where(c => char.IsAsciiLetterOrDigit(c) || c is 'Ö' or 'Ő' or 'Ü' or 'Ű')]);
        return Truncate(EmptyToNull(cleaned), 15);
    }

    /// <summary>Irányítószám normalizálása. Pattern: <c>[A-Z0-9 -]{2,10}</c> vagy üres.</summary>
    private static string? NormalizeZipCode(string? value)
    {
        if (string.IsNullOrWhiteSpace(value)) return null;
        var cleaned = new string([.. value.ToUpperInvariant().Where(c => char.IsAsciiLetterOrDigit(c) || c is ' ' or '-')]);
        return Truncate(EmptyToNull(cleaned), 10);
    }

    /// <summary>VTSZ normalizálása. Pattern: <c>[0-9]{4,8}</c> — a forrásban tagolt formátum is lehet
    /// („0805 10 00", „0805.10.00"), ezért csak a számjegyek maradnak; a 4-nél rövidebbet a validátor jelzi.</summary>
    private static string? NormalizeVtsz(string? value)
    {
        if (string.IsNullOrWhiteSpace(value)) return null;
        var cleaned = new string([.. value.Where(char.IsAsciiDigit)]);
        return Truncate(EmptyToNull(cleaned), 8);
    }

    private static string? EmptyToNull(string? value) => string.IsNullOrEmpty(value) ? null : value;

    private static string? Truncate(string? value, int maxLen)
        => value is null ? null : value.Length <= maxLen ? value : value[..maxLen];
}
