// Copyright (c) Microsoft. All rights reserved.

using System.Linq;
using System.Threading.Tasks;
using Microsoft.Agents.AI.Hyperlight.Internal;
using Microsoft.Extensions.AI;
using Moq;

namespace Microsoft.Agents.AI.Hyperlight.UnitTests;

public sealed class ProvideAIContextTests
{
    private static readonly AIAgent s_mockAgent = new Mock<AIAgent>().Object;

    private static AIContextProvider.InvokingContext NewInvokingContext() => new(s_mockAgent, session: null, new AIContext());

    [Fact]
    public async Task ProvideAIContextAsync_ReturnsExecuteCodeToolAndInstructionsAsync()
    {
        // Arrange
        using var provider = new HyperlightCodeActProvider(new HyperlightCodeActProviderOptions());

        // Act
        var context = await provider.InvokingAsync(NewInvokingContext());

        // Assert
        Assert.NotNull(context);
        Assert.NotNull(context!.Tools);
        var tools = context.Tools!.ToList();
        Assert.Single(tools);
        var function = Assert.IsAssignableFrom<AIFunction>(tools[0]);
        Assert.Equal("execute_code", function.Name);
        Assert.False(string.IsNullOrWhiteSpace(context.Instructions));
    }

    [Fact]
    public async Task ProvideAIContextAsync_AlwaysRequire_WrapsInApprovalRequiredAsync()
    {
        // Arrange
        using var provider = new HyperlightCodeActProvider(new HyperlightCodeActProviderOptions
        {
            ApprovalMode = CodeActApprovalMode.AlwaysRequire,
        });

        // Act
        var context = await provider.InvokingAsync(NewInvokingContext());

        // Assert
        _ = Assert.IsType<ApprovalRequiredAIFunction>(context!.Tools!.First());
    }

    [Fact]
    public async Task ProvideAIContextAsync_NeverRequireWithApprovalTool_WrapsInApprovalRequiredAsync()
    {
        // Arrange
        var inner = AIFunctionFactory.Create(() => "ok", name: "t");
        using var provider = new HyperlightCodeActProvider(new HyperlightCodeActProviderOptions
        {
            ApprovalMode = CodeActApprovalMode.NeverRequire,
            Tools = [new ApprovalRequiredAIFunction(inner)],
        });

        // Act
        var context = await provider.InvokingAsync(NewInvokingContext());

        // Assert
        _ = Assert.IsType<ApprovalRequiredAIFunction>(context!.Tools!.First());
    }

    [Fact]
    public async Task ProvideAIContextAsync_CapturesSnapshot_MutationsAfterDoNotAffectDescriptionAsync()
    {
        // Arrange
        using var provider = new HyperlightCodeActProvider(new HyperlightCodeActProviderOptions());
        provider.AddTools(AIFunctionFactory.Create(() => "one", name: "first_tool"));

        // Act
        var context = await provider.InvokingAsync(NewInvokingContext());
        provider.AddTools(AIFunctionFactory.Create(() => "two", name: "second_tool"));

        // Assert — the returned execute_code description must reflect the first snapshot only.
        var function = Assert.IsAssignableFrom<AIFunction>(context!.Tools!.First());
        Assert.Contains("first_tool", function.Description);
        Assert.DoesNotContain("second_tool", function.Description);
    }

    [Fact]
    public async Task ProvideAIContextAsync_AddToolsSameNameReplacement_ChangesSnapshotFingerprintAsync()
    {
        // Arrange
        using var provider = new HyperlightCodeActProvider(new HyperlightCodeActProviderOptions());
        provider.AddTools(AIFunctionFactory.Create(() => "one", name: "same_tool"));

        // Act
        var firstContext = await provider.InvokingAsync(NewInvokingContext());
        provider.AddTools(AIFunctionFactory.Create(() => "two", name: "same_tool"));
        var secondContext = await provider.InvokingAsync(NewInvokingContext());

        // Assert
        var firstFunction = Assert.IsType<ExecuteCodeFunction>(firstContext!.Tools!.First());
        var secondFunction = Assert.IsType<ExecuteCodeFunction>(secondContext!.Tools!.First());
        Assert.NotEqual(firstFunction.ConfigFingerprint, secondFunction.ConfigFingerprint);
    }
}
