using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Nexus.Api.Http;
using Xunit;

namespace Nexus.Api.Tests;

public sealed class ProblemDetailsContractTests
{
    [Theory]
    [InlineData(400, NexusProblemCodes.ValidationFailed)]
    [InlineData(401, NexusProblemCodes.Unauthenticated)]
    [InlineData(403, NexusProblemCodes.Forbidden)]
    [InlineData(409, NexusProblemCodes.Conflict)]
    [InlineData(429, NexusProblemCodes.RateLimited)]
    [InlineData(503, NexusProblemCodes.DependencyUnavailable)]
    [InlineData(504, NexusProblemCodes.Timeout)]
    [InlineData(500, NexusProblemCodes.InternalError)]
    public void Defaults_AddStableCodeAndTraceId(int status, string expectedCode)
    {
        var context = new DefaultHttpContext { TraceIdentifier = "trace-contract" };
        var problem = new ProblemDetails { Status = status };

        NexusProblemDetailsDefaults.Apply(problem, context);

        Assert.Equal(expectedCode, problem.Extensions["code"]);
        Assert.Equal("trace-contract", problem.Extensions["traceId"]);
        Assert.False(string.IsNullOrWhiteSpace(problem.Title));
        Assert.False(string.IsNullOrWhiteSpace(problem.Type));
    }

    [Fact]
    public void Defaults_PreserveExplicitMachineCode()
    {
        var context = new DefaultHttpContext { TraceIdentifier = "trace-contract" };
        var problem = new ProblemDetails { Status = 409 };
        problem.Extensions["code"] = "config_hash_conflict";

        NexusProblemDetailsDefaults.Apply(problem, context);

        Assert.Equal("config_hash_conflict", problem.Extensions["code"]);
    }

    [Fact]
    public void LegacyError_IsConvertedWithoutLosingTheStableContract()
    {
        var context = new DefaultHttpContext { TraceIdentifier = "trace-legacy" };

        var problem = NexusProblemDetailsDefaults.FromLegacyError(
            new { error = "Task not found." },
            StatusCodes.Status404NotFound,
            context);

        Assert.NotNull(problem);
        Assert.Equal("Task not found.", problem.Detail);
        Assert.Equal(NexusProblemCodes.NotFound, problem.Extensions["code"]);
        Assert.Equal("trace-legacy", problem.Extensions["traceId"]);
    }

    [Fact]
    public void LegacySuccessPayload_IsNotConverted()
    {
        var context = new DefaultHttpContext();

        var problem = NexusProblemDetailsDefaults.FromLegacyError(
            new { error = "informational field" },
            StatusCodes.Status200OK,
            context);

        Assert.Null(problem);
    }

    [Fact]
    public void SharedResult_UsesProblemJsonAndPreservesTypedExtensions()
    {
        var result = NexusHttpResults.Problem(
            StatusCodes.Status409Conflict,
            "The agent file changed.",
            extensions: new Dictionary<string, object?>
            {
                ["currentHash"] = "hash-current"
            });

        Assert.Equal(
            "application/problem+json",
            Assert.IsAssignableFrom<IContentTypeHttpResult>(result).ContentType);
        var problem = Assert.IsType<ProblemDetails>(
            Assert.IsAssignableFrom<IValueHttpResult>(result).Value);
        Assert.Equal(NexusProblemCodes.Conflict, problem.Extensions["code"]);
        Assert.Equal("hash-current", problem.Extensions["currentHash"]);
    }
}
