using System.Net;
using FluentAssertions;
using SatelliteProvider.Common.Exceptions;
using SatelliteProvider.Services.RegionProcessing;

namespace SatelliteProvider.Tests;

public class RegionFailureClassifierTests
{
    [Fact]
    public void Classify_TaskCanceled_WhenTimeoutFired_ReturnsTimeout()
    {
        // Arrange
        var timeoutCts = new CancellationTokenSource();
        timeoutCts.Cancel();
        var external = new CancellationToken();
        var ex = new TaskCanceledException("timed out");

        // Act
        var result = RegionFailureClassifier.Classify(ex, timeoutCts, external);

        // Assert
        result.Category.Should().Be(RegionFailureCategory.Timeout);
        result.ErrorMessage.Should().Be(RegionFailureClassifier.TimeoutMessage);
    }

    [Fact]
    public void Classify_TaskCanceled_WhenExternalTokenCancelled_ReturnsExternalCancellation()
    {
        // Arrange
        var timeoutCts = new CancellationTokenSource();
        var externalCts = new CancellationTokenSource();
        externalCts.Cancel();
        var ex = new TaskCanceledException("shutdown");

        // Act
        var result = RegionFailureClassifier.Classify(ex, timeoutCts, externalCts.Token);

        // Assert
        result.Category.Should().Be(RegionFailureCategory.ExternalCancellation);
        result.ErrorMessage.Should().Be(RegionFailureClassifier.ExternalCancellationMessage);
    }

    [Fact]
    public void Classify_TaskCanceled_WithNoTokenCancelled_ReturnsTaskCanceledOther()
    {
        // Arrange
        var timeoutCts = new CancellationTokenSource();
        var ex = new TaskCanceledException("inner http timeout");

        // Act
        var result = RegionFailureClassifier.Classify(ex, timeoutCts, CancellationToken.None);

        // Assert
        result.Category.Should().Be(RegionFailureCategory.TaskCanceledOther);
        result.ErrorMessage.Should().Contain("inner http timeout");
        result.ErrorMessage.Should().Contain("HttpClient timeout");
    }

    [Fact]
    public void Classify_OperationCanceled_WhenTimeoutFired_ReturnsTimeout()
    {
        // Arrange
        var timeoutCts = new CancellationTokenSource();
        timeoutCts.Cancel();
        var ex = new OperationCanceledException("op cancelled");

        // Act
        var result = RegionFailureClassifier.Classify(ex, timeoutCts, CancellationToken.None);

        // Assert
        result.Category.Should().Be(RegionFailureCategory.Timeout);
        result.ErrorMessage.Should().Be(RegionFailureClassifier.TimeoutMessage);
    }

    [Fact]
    public void Classify_OperationCanceled_WithoutTimeout_ReturnsOperationCanceledOther()
    {
        // Arrange
        var timeoutCts = new CancellationTokenSource();
        var ex = new OperationCanceledException("manual abort");

        // Act
        var result = RegionFailureClassifier.Classify(ex, timeoutCts, CancellationToken.None);

        // Assert
        result.Category.Should().Be(RegionFailureCategory.OperationCanceledOther);
        result.ErrorMessage.Should().Contain("manual abort");
    }

    [Fact]
    public void Classify_RateLimitException_ReturnsRateLimit()
    {
        // Arrange
        var timeoutCts = new CancellationTokenSource();
        var ex = new RateLimitException("retries exhausted");

        // Act
        var result = RegionFailureClassifier.Classify(ex, timeoutCts, CancellationToken.None);

        // Assert
        result.Category.Should().Be(RegionFailureCategory.RateLimit);
        result.ErrorMessage.Should().Contain("retries exhausted");
        result.ErrorMessage.Should().Contain("Rate limit exceeded");
    }

    [Fact]
    public void Classify_HttpRequestException_ReturnsNetwork_WithStatusCode()
    {
        // Arrange
        var timeoutCts = new CancellationTokenSource();
        var ex = new HttpRequestException("server down", inner: null, statusCode: HttpStatusCode.BadGateway);

        // Act
        var result = RegionFailureClassifier.Classify(ex, timeoutCts, CancellationToken.None);

        // Assert
        result.Category.Should().Be(RegionFailureCategory.Network);
        result.ErrorMessage.Should().Contain("BadGateway");
        result.ErrorMessage.Should().Contain("server down");
    }

    [Fact]
    public void Classify_UnknownException_ReturnsUnexpected()
    {
        // Arrange
        var timeoutCts = new CancellationTokenSource();
        var ex = new InvalidOperationException("boom");

        // Act
        var result = RegionFailureClassifier.Classify(ex, timeoutCts, CancellationToken.None);

        // Assert
        result.Category.Should().Be(RegionFailureCategory.Unexpected);
        result.ErrorMessage.Should().Contain("InvalidOperationException");
        result.ErrorMessage.Should().Contain("boom");
    }

    [Fact]
    public void Classify_TimeoutTakesPrecedenceOverExternalCancellation()
    {
        // Arrange
        var timeoutCts = new CancellationTokenSource();
        timeoutCts.Cancel();
        var externalCts = new CancellationTokenSource();
        externalCts.Cancel();
        var ex = new TaskCanceledException();

        // Act
        var result = RegionFailureClassifier.Classify(ex, timeoutCts, externalCts.Token);

        // Assert
        result.Category.Should().Be(RegionFailureCategory.Timeout);
    }

    [Fact]
    public void Classify_NullException_Throws()
    {
        // Arrange
        var timeoutCts = new CancellationTokenSource();

        // Act
        Action act = () => RegionFailureClassifier.Classify(null!, timeoutCts, CancellationToken.None);

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