using FluentValidation.TestHelper;
using Microsoft.Extensions.Options;
using SatelliteProvider.Api.Validators;
using SatelliteProvider.Common.Configs;
using SatelliteProvider.Common.DTO;

namespace SatelliteProvider.Tests.Validators;

// AZ-810: per-item metadata validator tests. Each RuleFor in
// UavTileMetadataValidator gets at least one passing + one failing case.
// Required-field detection lives at the deserializer layer ([JsonRequired]
// on UavTileMetadata) and is exercised at the integration layer.
public class UavTileMetadataValidatorTests
{
    private readonly UavTileMetadataValidator _validator;
    private readonly DateTime _now;

    public UavTileMetadataValidatorTests()
    {
        GlobalValidatorConfig.ApplyOnce();
        var config = Options.Create(new UavQualityConfig
        {
            MaxAgeDays = 7,
            CapturedAtFutureSkewSeconds = 30,
        });
        _now = new DateTime(2026, 5, 22, 12, 0, 0, DateTimeKind.Utc);
        _validator = new UavTileMetadataValidator(config, new FixedTimeProvider(_now));
    }

    // Mirrors the existing pattern in UavTileUploadHandlerTests / UavTileQualityGateTests
    // (those tests inline the same shape). Kept private here for SRP; if a third
    // consumer appears, promote to SatelliteProvider.TestSupport.
    private sealed class FixedTimeProvider : TimeProvider
    {
        private readonly DateTime _utcNow;
        public FixedTimeProvider(DateTime utcNow) => _utcNow = utcNow;
        public override DateTimeOffset GetUtcNow() => new(_utcNow, TimeSpan.Zero);
    }

    private static UavTileMetadata ValidMetadata(DateTime capturedAt) => new()
    {
        Latitude = 50.10,
        Longitude = 36.10,
        TileZoom = 18,
        TileSizeMeters = 200.0,
        CapturedAt = capturedAt,
        FlightId = null,
    };

    [Fact]
    public void Validate_AllValid_Passes()
    {
        // Arrange
        var metadata = ValidMetadata(_now.AddMinutes(-5));

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

        // Assert
        result.ShouldNotHaveAnyValidationErrors();
    }

    [Theory]
    [InlineData(-91.0)]
    [InlineData(90.001)]
    [InlineData(180.0)]
    public void Validate_LatOutOfRange_FailsRangeRule(double lat)
    {
        // Arrange
        var metadata = ValidMetadata(_now) with { Latitude = lat };

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

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

    [Theory]
    [InlineData(-181.0)]
    [InlineData(180.001)]
    [InlineData(360.0)]
    public void Validate_LonOutOfRange_FailsRangeRule(double lon)
    {
        // Arrange
        var metadata = ValidMetadata(_now) with { Longitude = lon };

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

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

    [Theory]
    [InlineData(-1)]
    [InlineData(23)]
    [InlineData(100)]
    public void Validate_TileZoomOutOfRange_FailsRangeRule(int zoom)
    {
        // Arrange
        var metadata = ValidMetadata(_now) with { TileZoom = zoom };

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

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

    [Theory]
    [InlineData(0.0)]
    [InlineData(-1.0)]
    public void Validate_TileSizeMetersNonPositive_FailsGreaterThanRule(double size)
    {
        // Arrange
        var metadata = ValidMetadata(_now) with { TileSizeMeters = size };

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

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

    [Fact]
    public void Validate_CapturedAtFuture_FailsFreshnessRule()
    {
        // Arrange — 60s in the future (skew limit is 30s).
        var metadata = ValidMetadata(_now.AddSeconds(60));

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

        // Assert
        result.ShouldHaveValidationErrorFor("capturedAt")
            .WithErrorMessage("`capturedAt` must be within 30s of the current time (no future-dated tiles).");
    }

    [Fact]
    public void Validate_CapturedAtNearFutureWithinSkew_Passes()
    {
        // Arrange — 10s in the future (within the 30s skew window).
        var metadata = ValidMetadata(_now.AddSeconds(10));

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

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

    [Fact]
    public void Validate_CapturedAtTooOld_FailsFreshnessRule()
    {
        // Arrange — 8 days ago (cap is 7 days).
        var metadata = ValidMetadata(_now.AddDays(-8));

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

        // Assert
        result.ShouldHaveValidationErrorFor("capturedAt")
            .WithErrorMessage("`capturedAt` must be within the last 7 days.");
    }

    [Fact]
    public void Validate_FlightIdNull_Passes()
    {
        // Arrange — AZ-503 anonymous-flight semantics: null FlightId is valid.
        var metadata = ValidMetadata(_now) with { FlightId = null };

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

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

    [Fact]
    public void Validate_FlightIdSet_Passes()
    {
        // Arrange
        var metadata = ValidMetadata(_now) with { FlightId = Guid.NewGuid() };

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

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