using Extrudex.API.DTOs.Filaments;
using FluentValidation;

namespace Extrudex.API.Validators;

/// <summary>
/// Validation rules for creating a Filament (Spool) via the /filaments route.
/// Mirrors the domain rules enforced in the controller and ensures consistent
/// validation regardless of the request pipeline entry point.
/// </summary>
public class CreateFilamentRequestValidator : AbstractValidator<CreateFilamentRequest>
{
    /// <summary>
    /// Initializes validation rules for <see cref="CreateFilamentRequest"/>.
    /// </summary>
    public CreateFilamentRequestValidator()
    {
        RuleFor(x => x.MaterialBaseId)
            .NotEmpty().WithMessage("MaterialBaseId is required.");

        RuleFor(x => x.MaterialFinishId)
            .NotEmpty().WithMessage("MaterialFinishId is required.");

        RuleFor(x => x.Brand)
            .NotEmpty().WithMessage("Brand is required.")
            .MaximumLength(200).WithMessage("Brand must not exceed 200 characters.");

        RuleFor(x => x.ColorName)
            .NotEmpty().WithMessage("ColorName is required.")
            .MaximumLength(200).WithMessage("ColorName must not exceed 200 characters.");

        RuleFor(x => x.ColorHex)
            .NotEmpty().WithMessage("ColorHex is required.")
            .Matches(@"^#[0-9A-Fa-f]{6}$").WithMessage("ColorHex must be a valid hex color code (e.g., #FF0000).");

        RuleFor(x => x.WeightTotalGrams)
            .GreaterThan(0).WithMessage("Total weight must be greater than zero.");

        RuleFor(x => x.WeightRemainingGrams)
            .GreaterThanOrEqualTo(0).WithMessage("Remaining weight must be non-negative.");

        RuleFor(x => x.WeightRemainingGrams)
            .LessThanOrEqualTo(x => x.WeightTotalGrams)
            .WithMessage("WeightRemainingGrams cannot exceed WeightTotalGrams.");

        RuleFor(x => x.FilamentDiameterMm)
            .GreaterThan(0).WithMessage("Filament diameter must be greater than zero.");

        RuleFor(x => x.SpoolSerial)
            .NotEmpty().WithMessage("SpoolSerial is required.")
            .MaximumLength(200).WithMessage("SpoolSerial must not exceed 200 characters.");

        When(x => x.PurchasePrice.HasValue, () =>
        {
            RuleFor(x => x.PurchasePrice!.Value)
                .GreaterThanOrEqualTo(0).WithMessage("Purchase price must be non-negative.");
        });

        When(x => x.StorageLocation != null, () =>
        {
            RuleFor(x => x.StorageLocation!)
                .MaximumLength(200).WithMessage("StorageLocation must not exceed 200 characters.");
        });
    }
}

/// <summary>
/// Validation rules for updating a Filament (Spool) via the /filaments route.
/// Enforces the same domain rules as creation, plus ensures the updated
/// WeightRemainingGrams does not exceed the updated WeightTotalGrams.
/// </summary>
public class UpdateFilamentRequestValidator : AbstractValidator<UpdateFilamentRequest>
{
    /// <summary>
    /// Initializes validation rules for <see cref="UpdateFilamentRequest"/>.
    /// </summary>
    public UpdateFilamentRequestValidator()
    {
        RuleFor(x => x.MaterialBaseId)
            .NotEmpty().WithMessage("MaterialBaseId is required.");

        RuleFor(x => x.MaterialFinishId)
            .NotEmpty().WithMessage("MaterialFinishId is required.");

        RuleFor(x => x.Brand)
            .NotEmpty().WithMessage("Brand is required.")
            .MaximumLength(200).WithMessage("Brand must not exceed 200 characters.");

        RuleFor(x => x.ColorName)
            .NotEmpty().WithMessage("ColorName is required.")
            .MaximumLength(200).WithMessage("ColorName must not exceed 200 characters.");

        RuleFor(x => x.ColorHex)
            .NotEmpty().WithMessage("ColorHex is required.")
            .Matches(@"^#[0-9A-Fa-f]{6}$").WithMessage("ColorHex must be a valid hex color code (e.g., #FF0000).");

        RuleFor(x => x.WeightTotalGrams)
            .GreaterThan(0).WithMessage("Total weight must be greater than zero.");

        RuleFor(x => x.WeightRemainingGrams)
            .GreaterThanOrEqualTo(0).WithMessage("Remaining weight must be non-negative.");

        RuleFor(x => x.WeightRemainingGrams)
            .LessThanOrEqualTo(x => x.WeightTotalGrams)
            .WithMessage("WeightRemainingGrams cannot exceed WeightTotalGrams.");

        RuleFor(x => x.FilamentDiameterMm)
            .GreaterThan(0).WithMessage("Filament diameter must be greater than zero.");

        RuleFor(x => x.SpoolSerial)
            .NotEmpty().WithMessage("SpoolSerial is required.")
            .MaximumLength(200).WithMessage("SpoolSerial must not exceed 200 characters.");

        When(x => x.PurchasePrice.HasValue, () =>
        {
            RuleFor(x => x.PurchasePrice!.Value)
                .GreaterThanOrEqualTo(0).WithMessage("Purchase price must be non-negative.");
        });

        When(x => x.StorageLocation != null, () =>
        {
            RuleFor(x => x.StorageLocation!)
                .MaximumLength(200).WithMessage("StorageLocation must not exceed 200 characters.");
        });
    }
}