using FluentAssertions;
using SatelliteProvider.Common.Utils;

namespace SatelliteProvider.Tests;

public class TileCsvWriterTests : IDisposable
{
    private readonly string _tempDir;

    public TileCsvWriterTests()
    {
        _tempDir = Path.Combine(Path.GetTempPath(), $"tilecsv_tests_{Guid.NewGuid():N}");
        Directory.CreateDirectory(_tempDir);
    }

    public void Dispose()
    {
        if (Directory.Exists(_tempDir))
        {
            Directory.Delete(_tempDir, recursive: true);
        }
    }

    [Fact]
    public async Task WriteAsync_WritesHeaderAndRows_AZ368_AC1()
    {
        // Arrange
        var sut = new TileCsvWriter();
        var path = Path.Combine(_tempDir, "tiles.csv");
        var rows = new[]
        {
            new TileCsvRow(47.461747, 37.647063, "/tiles/a.jpg"),
            new TileCsvRow(47.460000, 37.648000, "/tiles/b.jpg"),
        };

        // Act
        await sut.WriteAsync(path, rows);

        // Assert
        var lines = await File.ReadAllLinesAsync(path);
        lines.Should().HaveCount(3);
        lines[0].Should().Be("latitude,longitude,file_path");
        lines.Skip(1).Should().Contain("47.461747,37.647063,/tiles/a.jpg");
        lines.Skip(1).Should().Contain("47.460000,37.648000,/tiles/b.jpg");
    }

    [Fact]
    public async Task WriteAsync_OrdersByLatitudeDescThenLongitudeAsc_AZ368()
    {
        // Arrange
        var sut = new TileCsvWriter();
        var path = Path.Combine(_tempDir, "ordered.csv");
        // Intentionally unsorted; expected order is lat DESC, then lon ASC.
        var rows = new[]
        {
            new TileCsvRow(10.0, 30.0, "/c.jpg"),
            new TileCsvRow(20.0, 20.0, "/b.jpg"),
            new TileCsvRow(20.0, 10.0, "/a.jpg"),
        };

        // Act
        await sut.WriteAsync(path, rows);

        // Assert
        var lines = await File.ReadAllLinesAsync(path);
        lines[0].Should().Be("latitude,longitude,file_path");
        lines[1].Should().Be("20.000000,10.000000,/a.jpg", "lat=20 wins over lat=10; lon=10 wins over lon=20");
        lines[2].Should().Be("20.000000,20.000000,/b.jpg");
        lines[3].Should().Be("10.000000,30.000000,/c.jpg", "lat=10 comes last in desc order");
    }

    [Fact]
    public async Task WriteAsync_EmptyRows_WritesHeaderOnly_AZ368()
    {
        // Arrange
        var sut = new TileCsvWriter();
        var path = Path.Combine(_tempDir, "empty.csv");

        // Act
        await sut.WriteAsync(path, Array.Empty<TileCsvRow>());

        // Assert
        var lines = await File.ReadAllLinesAsync(path);
        lines.Should().ContainSingle();
        lines[0].Should().Be("latitude,longitude,file_path");
    }

    [Fact]
    public async Task WriteAsync_FormatsNumericValuesWithF6Precision_AZ368_AC2()
    {
        // Arrange
        var sut = new TileCsvWriter();
        var path = Path.Combine(_tempDir, "format.csv");
        var rows = new[]
        {
            new TileCsvRow(1.0, 2.0, "/x.jpg"),
            new TileCsvRow(0.1234567890, 99.0000001, "/y.jpg"),
        };

        // Act
        await sut.WriteAsync(path, rows);

        // Assert — F6 trims/pads to exactly 6 decimal places
        var lines = await File.ReadAllLinesAsync(path);
        lines.Should().Contain("1.000000,2.000000,/x.jpg");
        lines.Should().Contain("0.123457,99.000000,/y.jpg");
    }

    [Fact]
    public async Task WriteAsync_ProducesByteForByteOutputMatchingOldInlineWriter_AZ368_AC2()
    {
        // Arrange — replicate the exact pre-refactor inline writer behavior to
        // prove the extracted class doesn't drift the on-disk format.
        var rows = new[]
        {
            new TileCsvRow(47.461747, 37.647063, "/tiles/a.jpg"),
            new TileCsvRow(47.460000, 37.648000, "/tiles/b.jpg"),
            new TileCsvRow(47.470000, 37.640000, "/tiles/c.jpg"),
        };

        var newPath = Path.Combine(_tempDir, "new.csv");
        var oldPath = Path.Combine(_tempDir, "old.csv");

        // Act — new path via TileCsvWriter
        await new TileCsvWriter().WriteAsync(newPath, rows);

        // Act — old path via inlined original logic
        var ordered = rows.OrderByDescending(t => t.Latitude).ThenBy(t => t.Longitude).ToList();
        using (var writer = new StreamWriter(oldPath))
        {
            await writer.WriteLineAsync("latitude,longitude,file_path");
            foreach (var t in ordered)
            {
                await writer.WriteLineAsync($"{t.Latitude:F6},{t.Longitude:F6},{t.FilePath}");
            }
        }

        // Assert
        var newBytes = await File.ReadAllBytesAsync(newPath);
        var oldBytes = await File.ReadAllBytesAsync(oldPath);
        newBytes.Should().Equal(oldBytes, "AZ-368 AC-2 requires byte-for-byte identical CSV output");
    }

    [Fact]
    public async Task WriteAsync_NullPath_ThrowsArgumentNullException_AZ368()
    {
        // Arrange
        var sut = new TileCsvWriter();

        // Act
        Func<Task> act = () => sut.WriteAsync(null!, Array.Empty<TileCsvRow>());

        // Assert
        await act.Should().ThrowAsync<ArgumentNullException>();
    }

    [Fact]
    public async Task WriteAsync_NullRows_ThrowsArgumentNullException_AZ368()
    {
        // Arrange
        var sut = new TileCsvWriter();
        var path = Path.Combine(_tempDir, "nullrows.csv");

        // Act
        Func<Task> act = () => sut.WriteAsync(path, null!);

        // Assert
        await act.Should().ThrowAsync<ArgumentNullException>();
    }
}
