using Microsoft.Extensions.Logging.Abstractions;
using ScadaLink.Commons.Types;
using ScadaLink.SiteRuntime.Scripts;

namespace ScadaLink.SiteRuntime.Tests.Scripts;

/// <summary>
/// WP-6 (Phase 8): Script sandboxing verification.
/// Adversarial tests that verify forbidden APIs are blocked at compilation time.
/// </summary>
public class SandboxTests
{
    private readonly ScriptCompilationService _service;

    public SandboxTests()
    {
        _service = new ScriptCompilationService(NullLogger<ScriptCompilationService>.Instance);
    }

    // ── System.IO forbidden ──

    [Fact]
    public void Sandbox_FileRead_Blocked()
    {
        var result = _service.Compile("evil", """System.IO.File.ReadAllText("/etc/passwd")""");
        Assert.False(result.IsSuccess);
        Assert.Contains(result.Errors, e => e.Contains("System.IO"));
    }

    [Fact]
    public void Sandbox_FileWrite_Blocked()
    {
        var result = _service.Compile("evil", """System.IO.File.WriteAllText("/tmp/hack.txt", "pwned")""");
        Assert.False(result.IsSuccess);
    }

    [Fact]
    public void Sandbox_DirectoryCreate_Blocked()
    {
        var result = _service.Compile("evil", """System.IO.Directory.CreateDirectory("/tmp/evil")""");
        Assert.False(result.IsSuccess);
    }

    [Fact]
    public void Sandbox_FileStream_Blocked()
    {
        var result = _service.Compile("evil", """new System.IO.FileStream("/tmp/x", System.IO.FileMode.Create)""");
        Assert.False(result.IsSuccess);
    }

    [Fact]
    public void Sandbox_StreamReader_Blocked()
    {
        var result = _service.Compile("evil", """new System.IO.StreamReader("/tmp/x")""");
        Assert.False(result.IsSuccess);
    }

    // ── Process forbidden ──

    [Fact]
    public void Sandbox_ProcessStart_Blocked()
    {
        var result = _service.Compile("evil", """System.Diagnostics.Process.Start("cmd.exe", "/c dir")""");
        Assert.False(result.IsSuccess);
        Assert.Contains(result.Errors, e => e.Contains("Process"));
    }

    [Fact]
    public void Sandbox_ProcessStartInfo_Blocked()
    {
        var code = """
            var psi = new System.Diagnostics.Process();
            psi.StartInfo.FileName = "bash";
            """;
        var result = _service.Compile("evil", code);
        Assert.False(result.IsSuccess);
    }

    // ── Threading forbidden (except Tasks/CancellationToken) ──

    [Fact]
    public void Sandbox_ThreadCreate_Blocked()
    {
        var result = _service.Compile("evil", """new System.Threading.Thread(() => {}).Start()""");
        Assert.False(result.IsSuccess);
        Assert.Contains(result.Errors, e => e.Contains("System.Threading"));
    }

    [Fact]
    public void Sandbox_Mutex_Blocked()
    {
        var result = _service.Compile("evil", """new System.Threading.Mutex()""");
        Assert.False(result.IsSuccess);
    }

    [Fact]
    public void Sandbox_Semaphore_Blocked()
    {
        var result = _service.Compile("evil", """new System.Threading.Semaphore(1, 1)""");
        Assert.False(result.IsSuccess);
    }

    [Fact]
    public void Sandbox_TaskDelay_Allowed()
    {
        // async/await and Tasks are explicitly allowed
        var violations = _service.ValidateTrustModel("await System.Threading.Tasks.Task.Delay(100)");
        Assert.Empty(violations);
    }

    [Fact]
    public void Sandbox_CancellationToken_Allowed()
    {
        var violations = _service.ValidateTrustModel(
            "var ct = System.Threading.CancellationToken.None;");
        Assert.Empty(violations);
    }

    [Fact]
    public void Sandbox_CancellationTokenSource_Allowed()
    {
        var violations = _service.ValidateTrustModel(
            "var cts = new System.Threading.CancellationTokenSource();");
        Assert.Empty(violations);
    }

    // ── Reflection forbidden ──

    [Fact]
    public void Sandbox_GetType_Reflection_Blocked()
    {
        var result = _service.Compile("evil",
            """typeof(string).GetMethods(System.Reflection.BindingFlags.NonPublic)""");
        Assert.False(result.IsSuccess);
    }

    [Fact]
    public void Sandbox_AssemblyLoad_Blocked()
    {
        var result = _service.Compile("evil",
            """System.Reflection.Assembly.Load("System.Runtime")""");
        Assert.False(result.IsSuccess);
    }

    [Fact]
    public void Sandbox_ActivatorCreateInstance_ViaReflection_Blocked()
    {
        var result = _service.Compile("evil",
            """System.Reflection.Assembly.GetExecutingAssembly()""");
        Assert.False(result.IsSuccess);
    }

    // ── Raw network forbidden ──

    [Fact]
    public void Sandbox_TcpClient_Blocked()
    {
        var result = _service.Compile("evil", """new System.Net.Sockets.TcpClient("evil.com", 80)""");
        Assert.False(result.IsSuccess);
        Assert.Contains(result.Errors, e => e.Contains("System.Net.Sockets"));
    }

    [Fact]
    public void Sandbox_UdpClient_Blocked()
    {
        var result = _service.Compile("evil", """new System.Net.Sockets.UdpClient(1234)""");
        Assert.False(result.IsSuccess);
    }

    [Fact]
    public void Sandbox_HttpClient_Blocked()
    {
        var result = _service.Compile("evil", """new System.Net.Http.HttpClient()""");
        Assert.False(result.IsSuccess);
        Assert.Contains(result.Errors, e => e.Contains("System.Net.Http"));
    }

    [Fact]
    public void Sandbox_HttpRequestMessage_Blocked()
    {
        var result = _service.Compile("evil",
            """new System.Net.Http.HttpRequestMessage(System.Net.Http.HttpMethod.Get, "https://evil.com")""");
        Assert.False(result.IsSuccess);
    }

    // ── Allowed operations ──

    [Fact]
    public void Sandbox_BasicMath_Allowed()
    {
        var result = _service.Compile("safe", "Math.Max(1, 2)");
        Assert.True(result.IsSuccess);
    }

    [Fact]
    public void Sandbox_LinqOperations_Allowed()
    {
        var result = _service.Compile("safe",
            "new List<int> { 1, 2, 3 }.Where(x => x > 1).Sum()");
        Assert.True(result.IsSuccess);
    }

    [Fact]
    public void Sandbox_StringOperations_Allowed()
    {
        var result = _service.Compile("safe",
            """string.Join(", ", new[] { "a", "b", "c" })""");
        Assert.True(result.IsSuccess);
    }

    [Fact]
    public void Sandbox_DateTimeOperations_Allowed()
    {
        var result = _service.Compile("safe",
            "DateTime.UtcNow.AddHours(1).ToString(\"o\")");
        Assert.True(result.IsSuccess);
    }

    // ── Execution timeout ──

    [Fact]
    public async Task Sandbox_InfiniteLoop_CancelledByToken()
    {
        // Compile a script that loops forever
        var code = """
            while (true) {
                CancellationToken.ThrowIfCancellationRequested();
            }
            return null;
            """;

        var result = _service.Compile("infinite", code);
        Assert.True(result.IsSuccess, "Infinite loop compiles but should be cancelled at runtime");

        // Execute with a short timeout
        using var cts = new CancellationTokenSource(TimeSpan.FromMilliseconds(500));
        var globals = new ScriptGlobals
        {
            Instance = null!,
            Parameters = new ScriptParameters(),
            CancellationToken = cts.Token
        };

        await Assert.ThrowsAnyAsync<OperationCanceledException>(async () =>
        {
            await result.CompiledScript!.RunAsync(globals, cts.Token);
        });
    }

    [Fact]
    public async Task Sandbox_LongRunningScript_TimesOut()
    {
        // A script that does heavy computation with cancellation checks
        var code = """
            var sum = 0;
            for (var i = 0; i < 100_000_000; i++) {
                sum += i;
                if (i % 10000 == 0) CancellationToken.ThrowIfCancellationRequested();
            }
            return sum;
            """;

        var result = _service.Compile("heavy", code);
        Assert.True(result.IsSuccess);

        using var cts = new CancellationTokenSource(TimeSpan.FromMilliseconds(100));
        var globals = new ScriptGlobals
        {
            Instance = null!,
            Parameters = new ScriptParameters(),
            CancellationToken = cts.Token
        };

        await Assert.ThrowsAnyAsync<OperationCanceledException>(async () =>
        {
            await result.CompiledScript!.RunAsync(globals, cts.Token);
        });
    }

    // ── Combined adversarial attempts ──

    [Fact]
    public void Sandbox_MultipleViolationsInOneScript_AllDetected()
    {
        var code = """
            System.IO.File.ReadAllText("/etc/passwd");
            System.Diagnostics.Process.Start("cmd");
            new System.Net.Sockets.TcpClient();
            new System.Net.Http.HttpClient();
            """;

        var violations = _service.ValidateTrustModel(code);
        Assert.True(violations.Count >= 4,
            $"Expected at least 4 violations but got {violations.Count}: {string.Join("; ", violations)}");
    }

    [Fact]
    public void Sandbox_UsingDirective_StillDetected()
    {
        var code = """
            // Even with using aliases, the namespace string is still detected
            var x = System.IO.Path.GetTempPath();
            """;

        var violations = _service.ValidateTrustModel(code);
        Assert.NotEmpty(violations);
    }
}
