using FluentAssertions;
using SatelliteProvider.Common.Utils;

namespace SatelliteProvider.Tests;

public class DownloaderRefactorTests
{
    [Fact]
    public void GoogleMapsDownloaderV2_UsesHashSetForExistingTileLookup_AZ375_AC1()
    {
        // Arrange
        var path = LocateRepoFile(Path.Combine(
            "SatelliteProvider.Services.TileDownloader", "GoogleMapsDownloaderV2.cs"));
        path.Should().NotBeNull("GoogleMapsDownloaderV2.cs must be present in the workspace for this assertion");
        var content = File.ReadAllText(path!);

        // Assert
        content.Should().NotContain("existingTiles.FirstOrDefault",
            "AZ-375 replaces the per-tile FirstOrDefault scan with a HashSet membership check");
        content.Should().NotContain("_processingConfig.LatLonTolerance",
            "AZ-375 removes the float-tolerance comparison at the existing-tile lookup site");
        content.Should().Contain("HashSet<(int X, int Y, int Z)>",
            "AZ-375 introduces a typed HashSet keyed by tile (X, Y, Z) for O(1) lookup");
        content.Should().Contain("existingTileKeys.Contains((x, y, zoomLevel))",
            "the inner loop must consult the precomputed HashSet for skip decisions");
    }

    [Fact]
    public void GoogleMapsDownloaderV2_UsesGeoUtilsCircumferenceConstant_AZ377_AC1()
    {
        // Arrange
        var path = LocateRepoFile(Path.Combine(
            "SatelliteProvider.Services.TileDownloader", "GoogleMapsDownloaderV2.cs"));
        path.Should().NotBeNull();
        var content = File.ReadAllText(path!);

        // Assert
        content.Should().NotContain("EARTH_CIRCUMFERENCE_METERS",
            "AZ-377 removes the duplicated local Earth circumference literal");
        content.Should().NotContain("40075016.686",
            "AZ-377 forbids duplicate Earth circumference literals outside GeoUtils");
        content.Should().Contain("GeoUtils.EarthEquatorialCircumferenceMeters",
            "AZ-377 routes the downloader through the canonical GeoUtils constant");
    }

    [Fact]
    public void TileRepository_UsesGeoUtilsAndMapConfigConstants_AZ377_AC1()
    {
        // Arrange
        var path = LocateRepoFile(Path.Combine(
            "SatelliteProvider.DataAccess", "Repositories", "TileRepository.cs"));
        path.Should().NotBeNull();
        var content = File.ReadAllText(path!);

        // Assert
        content.Should().NotContain("EARTH_CIRCUMFERENCE_METERS");
        content.Should().NotContain("TILE_SIZE_PIXELS = 256");
        content.Should().NotContain("40075016.686",
            "AZ-377 forbids duplicate Earth circumference literals outside GeoUtils");
        content.Should().NotContain("111000.0",
            "AZ-377 forbids duplicate per-degree-latitude literals outside GeoUtils");
        content.Should().Contain("GeoUtils.EarthEquatorialCircumferenceMeters");
        content.Should().Contain("GeoUtils.MetersPerDegreeLatitude");
        content.Should().Contain("MapConfig.DefaultTileSizePixels");
    }

    [Fact]
    public void GeoUtils_IsTheSoleHolderOfRawEarthLiterals_AZ377_AC1()
    {
        // Arrange
        var geoUtilsPath = LocateRepoFile(Path.Combine(
            "SatelliteProvider.Common", "Utils", "GeoUtils.cs"));
        geoUtilsPath.Should().NotBeNull();
        var content = File.ReadAllText(geoUtilsPath!);

        // Assert
        content.Should().Contain("public const double EarthRadiusMeters = 6378137");
        content.Should().Contain("public const double EarthEquatorialCircumferenceMeters = 40075016.686");
        content.Should().Contain("public const double MetersPerDegreeLatitude = 111000");
    }

    [Fact]
    public void HaversineDistance_WithTinyDelta_UsesEarthRadiusConstantConsistently_AZ377_AC2()
    {
        // Arrange
        var p1 = new SatelliteProvider.Common.DTO.GeoPoint(50.4501, 30.5234);
        var p2 = new SatelliteProvider.Common.DTO.GeoPoint(50.4501, 30.6234);

        // Act
        var distance = GeoUtils.CalculateDistance(p1, p2);

        // Assert
        var expectedManual = 2d * Math.Asin(Math.Sqrt(
            Math.Cos(50.4501 * Math.PI / 180.0) * Math.Cos(50.4501 * Math.PI / 180.0)
            * Math.Pow(Math.Sin(0.05 * Math.PI / 180.0), 2)))
            * GeoUtils.EarthRadiusMeters;
        distance.Should().BeApproximately(expectedManual, 0.0001,
            "AZ-377 must not change the numeric result of CalculateDistance");
    }

    private static string? LocateRepoFile(string relativePath)
    {
        var dir = new DirectoryInfo(Directory.GetCurrentDirectory());
        while (dir is not null)
        {
            var candidate = Path.Combine(dir.FullName, relativePath);
            if (File.Exists(candidate))
            {
                return candidate;
            }
            dir = dir.Parent;
        }
        return null;
    }
}
