using FluentValidation;
using SatelliteProvider.Common.DTO;

namespace SatelliteProvider.Api.Validators;

// AZ-809: per-polygon validator invoked via RuleForEach on the parent
// CreateRouteRequest (guarded by When(geofences != null) at the parent).
// Enforces both corner-point shape and the "NW is north-of and west-of SE"
// invariant.
//
// Error path: errors keys land at `geofences.polygons[i].northWest.lat` etc.
public sealed class GeofencePolygonValidator : AbstractValidator<GeofencePolygon>
{
    private const double MinLat = -90.0;
    private const double MaxLat = 90.0;
    private const double MinLon = -180.0;
    private const double MaxLon = 180.0;

    public GeofencePolygonValidator()
    {
        // Both corners must be present. Without them no useful range/cross-field
        // check can run, so short-circuit via .Cascade(CascadeMode.Stop).
        RuleFor(p => p.NorthWest)
            .Cascade(CascadeMode.Stop)
            .NotNull().WithMessage("`northWest` corner is required.")
            .SetValidator(new GeoCornerValidator("northWest")!);

        RuleFor(p => p.SouthEast)
            .Cascade(CascadeMode.Stop)
            .NotNull().WithMessage("`southEast` corner is required.")
            .SetValidator(new GeoCornerValidator("southEast")!);

        // Cross-field invariant: NW must be genuinely north-of (lat greater)
        // AND west-of (lon smaller) SE. Only runs when both corners survived
        // the NotNull check above; FluentValidation skips the rule if either
        // is null (.When(...) guard below).
        RuleFor(p => p)
            .Must(HaveNorthWestActuallyNorthOfSouthEast)
            .When(p => p.NorthWest is not null && p.SouthEast is not null)
            .WithName("northWest")
            .WithMessage("`northWest.lat` must be greater than `southEast.lat` (NW is north-of SE).");

        RuleFor(p => p)
            .Must(HaveNorthWestActuallyWestOfSouthEast)
            .When(p => p.NorthWest is not null && p.SouthEast is not null)
            .WithName("northWest")
            .WithMessage("`northWest.lon` must be less than `southEast.lon` (NW is west-of SE).");
    }

    private static bool HaveNorthWestActuallyNorthOfSouthEast(GeofencePolygon polygon)
        => polygon.NorthWest!.Lat > polygon.SouthEast!.Lat;

    private static bool HaveNorthWestActuallyWestOfSouthEast(GeofencePolygon polygon)
        => polygon.NorthWest!.Lon < polygon.SouthEast!.Lon;

    // Inner per-corner validator. Kept private to this file because the
    // polygon corners are the only consumer; if a sibling endpoint needs
    // point-shape validation, promote and rename.
    private sealed class GeoCornerValidator : AbstractValidator<GeoPoint>
    {
        public GeoCornerValidator(string cornerLabel)
        {
            RuleFor(g => g.Lat)
                .InclusiveBetween(MinLat, MaxLat)
                .WithMessage($"`{cornerLabel}.lat` must be between {MinLat} and {MaxLat}.");

            RuleFor(g => g.Lon)
                .InclusiveBetween(MinLon, MaxLon)
                .WithMessage($"`{cornerLabel}.lon` must be between {MinLon} and {MaxLon}.");
        }
    }
}
