using Microsoft.Extensions.Logging.Abstractions;
using NSubstitute;
using ScadaLink.Commons.Entities.InboundApi;
using ScadaLink.Commons.Interfaces.Services;
using ScadaLink.Communication;

namespace ScadaLink.InboundAPI.Tests;

/// <summary>
/// WP-3: Tests for script execution on central — timeout, handler dispatch, error handling.
/// WP-5: Safe error messages.
/// </summary>
public class InboundScriptExecutorTests
{
    private readonly InboundScriptExecutor _executor;
    private readonly RouteHelper _route;

    public InboundScriptExecutorTests()
    {
        _executor = new InboundScriptExecutor(NullLogger<InboundScriptExecutor>.Instance, Substitute.For<IServiceProvider>());
        var locator = Substitute.For<IInstanceLocator>();
        var commService = Substitute.For<CommunicationService>(
            Microsoft.Extensions.Options.Options.Create(new CommunicationOptions()),
            NullLogger<CommunicationService>.Instance);
        _route = new RouteHelper(locator, commService);
    }

    [Fact]
    public async Task RegisteredHandler_ExecutesSuccessfully()
    {
        var method = new ApiMethod("test", "return 42;") { Id = 1, TimeoutSeconds = 10 };
        _executor.RegisterHandler("test", async ctx =>
        {
            await Task.CompletedTask;
            return new { result = 42 };
        });

        var result = await _executor.ExecuteAsync(
            method,
            new Dictionary<string, object?>(),
            _route,
            TimeSpan.FromSeconds(10));

        Assert.True(result.Success);
        Assert.NotNull(result.ResultJson);
        Assert.Contains("42", result.ResultJson);
    }

    [Fact]
    public async Task UnregisteredHandler_InvalidScript_ReturnsCompilationFailure()
    {
        // Use an invalid script that cannot be compiled by Roslyn
        var method = new ApiMethod("unknown", "%%% invalid C# %%%") { Id = 1, TimeoutSeconds = 10 };

        var result = await _executor.ExecuteAsync(
            method,
            new Dictionary<string, object?>(),
            _route,
            TimeSpan.FromSeconds(10));

        Assert.False(result.Success);
        Assert.Contains("Script compilation failed", result.ErrorMessage);
    }

    [Fact]
    public async Task UnregisteredHandler_ValidScript_LazyCompiles()
    {
        // Valid script that is not pre-registered triggers lazy compilation
        var method = new ApiMethod("lazy", "return 1;") { Id = 1, TimeoutSeconds = 10 };

        var result = await _executor.ExecuteAsync(
            method,
            new Dictionary<string, object?>(),
            _route,
            TimeSpan.FromSeconds(10));

        Assert.True(result.Success);
    }

    [Fact]
    public async Task HandlerThrows_ReturnsSafeErrorMessage()
    {
        var method = new ApiMethod("failing", "throw new Exception();") { Id = 1, TimeoutSeconds = 10 };
        _executor.RegisterHandler("failing", _ => throw new InvalidOperationException("internal detail leak"));

        var result = await _executor.ExecuteAsync(
            method,
            new Dictionary<string, object?>(),
            _route,
            TimeSpan.FromSeconds(10));

        Assert.False(result.Success);
        // WP-5: Safe error message — should NOT contain "internal detail leak"
        Assert.Equal("Internal script error", result.ErrorMessage);
    }

    [Fact]
    public async Task HandlerTimesOut_ReturnsTimeoutError()
    {
        var method = new ApiMethod("slow", "Thread.Sleep(60000);") { Id = 1, TimeoutSeconds = 1 };
        _executor.RegisterHandler("slow", async ctx =>
        {
            await Task.Delay(TimeSpan.FromSeconds(60), ctx.CancellationToken);
            return "never";
        });

        var result = await _executor.ExecuteAsync(
            method,
            new Dictionary<string, object?>(),
            _route,
            TimeSpan.FromMilliseconds(100));

        Assert.False(result.Success);
        Assert.Contains("timed out", result.ErrorMessage);
    }

    [Fact]
    public async Task HandlerAccessesParameters()
    {
        var method = new ApiMethod("echo", "return params;") { Id = 1, TimeoutSeconds = 10 };
        _executor.RegisterHandler("echo", async ctx =>
        {
            await Task.CompletedTask;
            return ctx.Parameters["name"];
        });

        var parameters = new Dictionary<string, object?> { { "name", "ScadaLink" } };

        var result = await _executor.ExecuteAsync(
            method, parameters, _route, TimeSpan.FromSeconds(10));

        Assert.True(result.Success);
        Assert.Contains("ScadaLink", result.ResultJson!);
    }
}
