using FluentValidation;

namespace foodmarket.Api.Infrastructure.Validation;

// ──────────────────────────────────────────────────────────────────────────────
// FluentValidation-валидаторы для основных input-DTO. Зарегистрированы
// автоматически через AddValidatorsFromAssemblyContaining<Program>().
//
// Зачем выносим из контроллеров: единый формат ошибок (ValidationProblemDetails),
// тестируемые юнит-тестами без HTTP, переиспользуемые правила (например, BIN
// длиной 12 цифр — одинаково для Counterparty.Create и Counterparty.Update),
// читаемые правила (RuleFor + цепочка).
//
// Стиль: лаконично, на одно поле — одна цепочка. Сообщения по-русски (UI ждёт RU).
// ──────────────────────────────────────────────────────────────────────────────

public sealed class SupplyInputValidator : AbstractValidator<foodmarket.Api.Controllers.Purchases.SuppliesController.SupplyInput>
{
    public SupplyInputValidator()
    {
        RuleFor(x => x.SupplierId).NotEqual(Guid.Empty).WithMessage("Не указан поставщик.");
        RuleFor(x => x.StoreId).NotEqual(Guid.Empty).WithMessage("Не указан склад.");
        RuleFor(x => x.CurrencyId).NotEqual(Guid.Empty).WithMessage("Не указана валюта.");
        RuleFor(x => x.Date).LessThan(DateTime.UtcNow.AddDays(1)).WithMessage("Дата не может быть в будущем.");
        RuleFor(x => x.Notes).MaximumLength(1000);
        RuleFor(x => x.Lines).NotEmpty().WithMessage("Приёмка должна содержать хотя бы одну позицию.");
        RuleForEach(x => x.Lines).ChildRules(line =>
        {
            line.RuleFor(l => l.ProductId).NotEqual(Guid.Empty).WithMessage("Не указан товар в строке.");
            line.RuleFor(l => l.Quantity).GreaterThan(0).WithMessage("Количество должно быть больше 0.");
            line.RuleFor(l => l.UnitPrice).GreaterThanOrEqualTo(0).WithMessage("Цена не может быть отрицательной.");
        });
    }
}

public sealed class RetailSaleInputValidator : AbstractValidator<foodmarket.Api.Controllers.Sales.RetailSalesController.RetailSaleInput>
{
    public RetailSaleInputValidator()
    {
        RuleFor(x => x.StoreId).NotEqual(Guid.Empty).WithMessage("Не указан склад.");
        RuleFor(x => x.CurrencyId).NotEqual(Guid.Empty).WithMessage("Не указана валюта.");
        RuleFor(x => x.Date).LessThan(DateTime.UtcNow.AddDays(1)).WithMessage("Дата не может быть в будущем.");
        RuleFor(x => x.Notes).MaximumLength(1000);
        RuleFor(x => x.PaidCash).GreaterThanOrEqualTo(0);
        RuleFor(x => x.PaidCard).GreaterThanOrEqualTo(0);
        // Возврат с reference: ReferenceSaleId должен быть валидный GUID
        // (саму ссылку валидирует контроллер обращаясь в БД).
        When(x => x.IsReturn, () =>
        {
            RuleFor(x => x.ReferenceSaleId).Must(id => id is null || id != Guid.Empty)
                .WithMessage("Невалидный ReferenceSaleId.");
        });
        RuleFor(x => x.Lines).NotEmpty().WithMessage("Чек должен содержать хотя бы одну позицию.");
        RuleForEach(x => x.Lines).ChildRules(line =>
        {
            line.RuleFor(l => l.ProductId).NotEqual(Guid.Empty);
            line.RuleFor(l => l.Quantity).GreaterThan(0);
            line.RuleFor(l => l.UnitPrice).GreaterThanOrEqualTo(0);
            line.RuleFor(l => l.Discount).GreaterThanOrEqualTo(0);
            line.RuleFor(l => l.VatPercent).InclusiveBetween(0, 100);
        });
    }
}

public sealed class ProductInputValidator : AbstractValidator<foodmarket.Application.Catalog.ProductInput>
{
    public ProductInputValidator()
    {
        RuleFor(x => x.Name).NotEmpty().WithMessage("Название обязательно.")
            .MaximumLength(200);
        RuleFor(x => x.Article).MaximumLength(50);
        RuleFor(x => x.UnitOfMeasureId).NotEqual(Guid.Empty)
            .WithMessage("Не указана единица измерения.");
        RuleFor(x => x.Vat).InclusiveBetween(0m, 100m).When(x => x.Vat.HasValue)
            .WithMessage("Ставка НДС должна быть от 0 до 100%.");
        RuleFor(x => x.ReferencePrice).GreaterThanOrEqualTo(0).When(x => x.ReferencePrice.HasValue);
        RuleFor(x => x.MinStock).GreaterThanOrEqualTo(0).When(x => x.MinStock.HasValue);
        RuleFor(x => x.MaxStock).GreaterThanOrEqualTo(0).When(x => x.MaxStock.HasValue);
        // MinStock <= MaxStock когда оба заданы.
        RuleFor(x => x).Must(x => !x.MinStock.HasValue || !x.MaxStock.HasValue || x.MinStock.Value <= x.MaxStock.Value)
            .WithMessage("MinStock не может быть больше MaxStock.");
    }
}

public sealed class CounterpartyInputValidator : AbstractValidator<foodmarket.Application.Catalog.CounterpartyInput>
{
    public CounterpartyInputValidator()
    {
        RuleFor(x => x.Name).NotEmpty().WithMessage("Название обязательно.")
            .MaximumLength(200);
        // БИН (юрлицо) — 12 цифр, ИИН (физлицо) — 12 цифр. Один из двух
        // обязателен только если тип LegalEntity (Type=1). Для Individual (2)
        // достаточно ИИН или имени. На уровне БД эти ограничения не enforced —
        // валидируем именно тут.
        When(x => !string.IsNullOrEmpty(x.Bin), () =>
        {
            RuleFor(x => x.Bin!).Matches(@"^\d{12}$")
                .WithMessage("БИН должен быть 12-значный.");
        });
        When(x => !string.IsNullOrEmpty(x.Iin), () =>
        {
            RuleFor(x => x.Iin!).Matches(@"^\d{12}$")
                .WithMessage("ИИН должен быть 12-значный.");
        });
        // Простая validation формата e-mail (без RFC-зубодробильника):
        // contains @ and a dot, with no spaces.
        When(x => !string.IsNullOrEmpty(x.Email), () =>
        {
            RuleFor(x => x.Email!).EmailAddress().WithMessage("Невалидный e-mail.");
        });
    }
}

public sealed class EmployeeInputValidator : AbstractValidator<foodmarket.Api.Controllers.Organizations.EmployeesController.EmployeeInput>
{
    public EmployeeInputValidator()
    {
        RuleFor(x => x.LastName).NotEmpty().WithMessage("Фамилия обязательна.").MaximumLength(100);
        RuleFor(x => x.FirstName).NotEmpty().WithMessage("Имя обязательно.").MaximumLength(100);
        RuleFor(x => x.RoleId).NotEqual(Guid.Empty).WithMessage("Не выбрана роль.");
        // SendInvite только при CreateAccount=true и наличии Email.
        When(x => x.SendInvite, () =>
        {
            RuleFor(x => x.CreateAccount).Equal(true)
                .WithMessage("Для отправки приглашения нужно создавать учётную запись.");
            RuleFor(x => x.Email).NotEmpty()
                .WithMessage("Для приглашения нужен email сотрудника.");
        });
        When(x => x.CreateAccount, () =>
        {
            RuleFor(x => x.Email).NotEmpty().EmailAddress()
                .WithMessage("Для создания учётки нужен валидный email.");
        });
    }
}
