using FluentValidation.TestHelper;
using SatelliteProvider.Api.Validators;
using SatelliteProvider.Common.DTO;

namespace SatelliteProvider.Tests.Validators;

// AZ-808: unit tests for RegionRequestValidator. Each RuleFor in the validator
// has at least one passing case + one failing case. Required-field detection
// (id / lat / lon / sizeMeters / zoomLevel / stitchTiles) is not unit-tested
// here because it lives at the deserializer layer (JsonRequired), not the
// validator — covered by the integration tests (RegionRequestValidationTests).
public class RegionRequestValidatorTests
{
    private readonly RegionRequestValidator _validator;

    public RegionRequestValidatorTests()
    {
        GlobalValidatorConfig.ApplyOnce();
        _validator = new RegionRequestValidator();
    }

    private static RequestRegionRequest ValidRequest() => new()
    {
        Id = Guid.NewGuid(),
        Lat = 47.461747,
        Lon = 37.647063,
        SizeMeters = 200.0,
        ZoomLevel = 18,
        StitchTiles = false,
    };

    [Fact]
    public void Validate_AllValid_Passes()
    {
        // Arrange
        var request = ValidRequest();

        // Act
        var result = _validator.TestValidate(request);

        // Assert
        result.ShouldNotHaveAnyValidationErrors();
    }

    [Fact]
    public void Validate_IdEmpty_FailsNotEmptyRule()
    {
        // Arrange
        var request = ValidRequest() with { Id = Guid.Empty };

        // Act
        var result = _validator.TestValidate(request);

        // Assert
        result.ShouldHaveValidationErrorFor("id")
            .WithErrorMessage("`id` must be a non-zero GUID (the caller's idempotency key).");
    }

    [Theory]
    [InlineData(-90.001)]
    [InlineData(90.001)]
    [InlineData(180.0)]
    [InlineData(-181.0)]
    public void Validate_LatOutOfRange_FailsRangeRule(double lat)
    {
        // Arrange
        var request = ValidRequest() with { Lat = lat };

        // Act
        var result = _validator.TestValidate(request);

        // Assert
        result.ShouldHaveValidationErrorFor("lat");
    }

    [Theory]
    [InlineData(-90.0)]
    [InlineData(0.0)]
    [InlineData(47.461747)]
    [InlineData(90.0)]
    public void Validate_LatAtOrInsideBounds_Passes(double lat)
    {
        // Arrange
        var request = ValidRequest() with { Lat = lat };

        // Act
        var result = _validator.TestValidate(request);

        // Assert
        result.ShouldNotHaveValidationErrorFor("lat");
    }

    [Theory]
    [InlineData(-180.001)]
    [InlineData(180.001)]
    [InlineData(360.0)]
    public void Validate_LonOutOfRange_FailsRangeRule(double lon)
    {
        // Arrange
        var request = ValidRequest() with { Lon = lon };

        // Act
        var result = _validator.TestValidate(request);

        // Assert
        result.ShouldHaveValidationErrorFor("lon");
    }

    [Theory]
    [InlineData(-180.0)]
    [InlineData(0.0)]
    [InlineData(37.647063)]
    [InlineData(180.0)]
    public void Validate_LonAtOrInsideBounds_Passes(double lon)
    {
        // Arrange
        var request = ValidRequest() with { Lon = lon };

        // Act
        var result = _validator.TestValidate(request);

        // Assert
        result.ShouldNotHaveValidationErrorFor("lon");
    }

    [Theory]
    [InlineData(99.999)]
    [InlineData(0.0)]
    [InlineData(10000.001)]
    [InlineData(100000.0)]
    [InlineData(-1.0)]
    public void Validate_SizeMetersOutOfRange_FailsRangeRule(double sizeMeters)
    {
        // Arrange
        var request = ValidRequest() with { SizeMeters = sizeMeters };

        // Act
        var result = _validator.TestValidate(request);

        // Assert
        result.ShouldHaveValidationErrorFor("sizeMeters");
    }

    [Theory]
    [InlineData(100.0)]
    [InlineData(200.0)]
    [InlineData(5000.0)]
    [InlineData(10000.0)]
    public void Validate_SizeMetersAtOrInsideBounds_Passes(double sizeMeters)
    {
        // Arrange
        var request = ValidRequest() with { SizeMeters = sizeMeters };

        // Act
        var result = _validator.TestValidate(request);

        // Assert
        result.ShouldNotHaveValidationErrorFor("sizeMeters");
    }

    [Theory]
    [InlineData(-1)]
    [InlineData(23)]
    [InlineData(100)]
    public void Validate_ZoomLevelOutOfRange_FailsRangeRule(int zoomLevel)
    {
        // Arrange
        var request = ValidRequest() with { ZoomLevel = zoomLevel };

        // Act
        var result = _validator.TestValidate(request);

        // Assert
        result.ShouldHaveValidationErrorFor("zoomLevel");
    }

    [Theory]
    [InlineData(0)]
    [InlineData(18)]
    [InlineData(22)]
    public void Validate_ZoomLevelAtOrInsideBounds_Passes(int zoomLevel)
    {
        // Arrange
        var request = ValidRequest() with { ZoomLevel = zoomLevel };

        // Act
        var result = _validator.TestValidate(request);

        // Assert
        result.ShouldNotHaveValidationErrorFor("zoomLevel");
    }
}
