using System.ComponentModel.DataAnnotations;
using AyCode.Services.Nav.Ekaer.Models;

namespace AyCode.Services.Nav.Ekaer;

/// <inheritdoc cref="IEkaerTradeCardValidator"/>
/// <remarks>
/// Két forrásból gyűjt hibát:
/// <list type="number">
///   <item>a generált modellek <see cref="ValidationAttribute"/>-jai (Required / RegularExpression / Min-MaxLength),
///   rekurzívan a tradeCard + tételek + jármű + helyszínek fölött — ezt a <see cref="Validator"/> végzi;</item>
///   <item>az XSD-ben NEM jelölt, de a NAV által megkövetelt üzleti szabályok (vonó jármű, legalább egy tétel,
///   tétel-tömeg/-érték &gt; 0, feladó/címzett alapadatok) — ezeket kézzel ellenőrizzük.</item>
/// </list>
/// </remarks>
public sealed class EkaerTradeCardValidator : IEkaerTradeCardValidator
{
    public IReadOnlyList<ValidationResult> Validate(TradeCardOperationType operation)
    {
        ArgumentNullException.ThrowIfNull(operation);
        var errors = new List<ValidationResult>();

        ValidateOperation(operation, "tradeCardOperation", errors);
        return errors;
    }

    public IReadOnlyList<ValidationResult> Validate(IEnumerable<TradeCardOperationType> operations)
    {
        ArgumentNullException.ThrowIfNull(operations);
        var list = operations.ToList();
        var errors = new List<ValidationResult>();

        if (list.Count == 0)
        {
            errors.Add(Error("tradeCardOperations", "legalább egy tradeCard művelet szükséges"));
            return errors;
        }

        for (var i = 0; i < list.Count; i++)
            ValidateOperation(list[i], $"tradeCardOperations[{i}]", errors);

        return errors;
    }

    private static void ValidateOperation(TradeCardOperationType operation, string path, List<ValidationResult> errors)
    {
        // A művelet saját attribútumai (index, operation [Required]).
        ValidateAnnotations(operation, path, errors);

        var tradeCard = operation.TradeCard;
        if (tradeCard is null)
        {
            // delete művelethez elég a Tcn; minden más művelethez a tradeCard kötelező.
            if (operation.Operation != OperationType.Delete)
                errors.Add(Error($"{path}.tradeCard", "a tradeCard kötelező (kivéve delete művelet, ahol a Tcn elég)"));
            return;
        }

        ValidateTradeCard(tradeCard, $"{path}.tradeCard", errors);
    }

    private static void ValidateTradeCard(TradeCardType tradeCard, string path, List<ValidationResult> errors)
    {
        // 1) Séma-szint: a generált DataAnnotations a tradeCard saját + örökölt property-jein.
        ValidateAnnotations(tradeCard, path, errors);

        // 2) Séma-szint a beágyazott objektumokon (a Validator nem rekurzív, ezért kézzel járjuk be).
        if (tradeCard.Vehicle is not null) ValidateAnnotations(tradeCard.Vehicle, $"{path}.vehicle", errors);
        if (tradeCard.Vehicle2 is not null) ValidateAnnotations(tradeCard.Vehicle2, $"{path}.vehicle2", errors);
        if (tradeCard.LoadLocation is not null) ValidateAnnotations(tradeCard.LoadLocation, $"{path}.loadLocation", errors);
        if (tradeCard.UnloadLocation is not null) ValidateAnnotations(tradeCard.UnloadLocation, $"{path}.unloadLocation", errors);

        for (var i = 0; i < tradeCard.Items.Count; i++)
        {
            var itemPath = $"{path}.items[{i}]";
            var item = tradeCard.Items[i];

            ValidateAnnotations(item, itemPath, errors);

            // A productVtsz a modellen [Required(AllowEmptyStrings=true)] + pattern — az üres string ("") elcsúszna
            // a sémán (a null bukik a Required-en, az "" nem, mert a RegularExpression az üreset validnak veszi),
            // ezért üzleti szabállyal lezárjuk. A { Length: 0 } property-pattern a null-ra NEM illeszkedik → nincs dupla hiba.
            if (item.ProductVtsz is { Length: 0 }) errors.Add(Error($"{itemPath}.productVtsz", "a tétel VTSZ-száma (productVtsz) nem lehet üres"));

            // A tétel bruttó tömege kötelező és pozitív — a 0 azt jelzi, hogy a forrás-dokumentumról hiányzik a súly (adathiba).
            if (item.Weight <= 0) errors.Add(Error($"{itemPath}.weight", "a tétel bruttó tömege (weight) kötelező és nem lehet 0"));

            // A NAV 2021.01.01-től minden tételre value > 0-t követel — a hiányzó/0 érték (pl. ismeretlen ár) blokkoló adathiba.
            if (item.Value is not > 0) errors.Add(Error($"{itemPath}.value", "a tétel értéke (value) kötelező és pozitív (NAV-követelmény 2021-től)"));
        }

        // 3) Üzleti szabályok (az XSD nem jelöli [Required]-ként, de a NAV megköveteli).
        // A rendszám a NAV-nál legkésőbb a felrakodás megkezdéséig pótolható — a bejelentéskor NEM kötelező,
        // ezért hiánya WARNING (a bejelentés beküldhető), nem blokkoló error. Lásd EKAER_OPERATIONS.md (create→finalize).
        if (tradeCard.Vehicle is null) errors.Add(Warning($"{path}.vehicle", "a vonó jármű (rendszám) hiányzik — legkésőbb a felrakodás megkezdéséig pótolandó"));
        if (tradeCard.Items.Count == 0) errors.Add(Error($"{path}.items", "legalább egy tétel szükséges"));

        RequireText(tradeCard.SellerName, $"{path}.sellerName", "a feladó (eladó) neve kötelező", errors);
        RequireText(tradeCard.SellerVatNumber, $"{path}.sellerVatNumber", "a feladó adószáma kötelező", errors);
        RequireText(tradeCard.SellerCountry, $"{path}.sellerCountry", "a feladó országkódja kötelező", errors);
        RequireText(tradeCard.DestinationName, $"{path}.destinationName", "a címzett neve kötelező", errors);
        RequireText(tradeCard.DestinationVatNumber, $"{path}.destinationVatNumber", "a címzett adószáma kötelező", errors);
        RequireText(tradeCard.DestinationCountry, $"{path}.destinationCountry", "a címzett országkódja kötelező", errors);
    }

    /// <summary>
    /// A generált modell <see cref="ValidationAttribute"/>-jainak ellenőrzése (Required / pattern / hossz),
    /// property-szelektíven. A generátor a nullable érték-mezőkhöz <c>*Value</c> + <c>*Specified</c> segéd-property-ket
    /// készít, és a tényleges attribútumok ezeken ülnek; a <c>*Value</c> default értéke (pl. a 0001-01-01 DateTime a
    /// timestamp-pattern ellen) téves hibát adna, ezért kihagyjuk őket — a serializáció úgyis a <c>*Specified</c>
    /// flag alapján dönt a kiírásról, így a default <c>*Value</c> sosem kerül ki a beküldött XML-be.
    /// </summary>
    private static void ValidateAnnotations(object instance, string path, List<ValidationResult> errors)
    {
        foreach (var property in instance.GetType().GetProperties())
        {
            if (property.GetIndexParameters().Length != 0) continue; // indexer kihagyása
            if (property.Name.EndsWith("Value", StringComparison.Ordinal) ||
                property.Name.EndsWith("Specified", StringComparison.Ordinal))
                continue;

            var context = new ValidationContext(instance) { MemberName = property.Name };
            var local = new List<ValidationResult>();

            Validator.TryValidateProperty(property.GetValue(instance), context, local);

            foreach (var result in local)
                errors.Add(new EkaerValidationMessage(EkaerSeverity.Error, $"{path}.{property.Name}: {result.ErrorMessage}"));
        }
    }

    private static void RequireText(string? value, string path, string message, List<ValidationResult> errors)
    {
        if (string.IsNullOrWhiteSpace(value))
            errors.Add(Error(path, message));
    }

    private static EkaerValidationMessage Error(string path, string message) => new(EkaerSeverity.Error, $"{path}: {message}");

    private static EkaerValidationMessage Warning(string path, string message) => new(EkaerSeverity.Warning, $"{path}: {message}");
}
