// Copyright (c) Sbroenne. All rights reserved.
// Licensed under the MIT License.

using System.IO.Pipelines;
using System.Text.Json;
using ModelContextProtocol.Client;
using ModelContextProtocol.Protocol;
using Xunit;
using Xunit.Abstractions;

namespace Sbroenne.ExcelMcp.McpServer.Tests.Integration.Tools;

/// <summary>
/// Tests for ExcelFileTool operation tracking functionality.
/// Verifies that LIST action returns operation counts and that
/// CLOSE is blocked when operations are running.
/// </summary>
[Collection("ProgramTransport")]  // Uses Program.ConfigureTestTransport() - must run sequentially
[Trait("Category", "Integration")]
[Trait("Speed", "Medium")]
[Trait("Layer", "McpServer")]
[Trait("Feature", "SessionManager")]
[Trait("RequiresExcel", "true")]
public class ExcelFileToolOperationTrackingTests : IAsyncLifetime, IAsyncDisposable
{
    private readonly ITestOutputHelper _output;
    private readonly string _tempDir;

    // MCP transport pipes
    private readonly Pipe _clientToServerPipe = new();
    private readonly Pipe _serverToClientPipe = new();
    private readonly CancellationTokenSource _cts = new();
    private McpClient? _client;
    private Task? _serverTask;

    public ExcelFileToolOperationTrackingTests(ITestOutputHelper output)
    {
        _output = output;
        _tempDir = Path.Join(Path.GetTempPath(), $"ExcelFileToolOpTrackingTests_{Guid.NewGuid():N}");
        Directory.CreateDirectory(_tempDir);
        _output.WriteLine($"Test directory: {_tempDir}");
    }

    public async Task InitializeAsync()
    {
        (_client, _serverTask) = await ProgramTransportTestHost.StartAsync(
            _clientToServerPipe,
            _serverToClientPipe,
            _cts.Token,
            "OpTrackingTestClient");

        _output.WriteLine($"✓ Connected to server: {_client.ServerInfo?.Name} v{_client.ServerInfo?.Version}");
    }

    public async Task DisposeAsync()
    {
        await DisposeAsyncCore();
    }

    async ValueTask IAsyncDisposable.DisposeAsync()
    {
        await DisposeAsyncCore();
        GC.SuppressFinalize(this);
    }

    private async Task DisposeAsyncCore()
    {
        await ProgramTransportTestHost.StopAsync(
            _client,
            _clientToServerPipe,
            _serverToClientPipe,
            _serverTask,
            _output);

        // Delete test files
        if (Directory.Exists(_tempDir))
        {
#pragma warning disable CA1031 // Catch general exception - best effort cleanup in test disposal
            try
            {
                Directory.Delete(_tempDir, recursive: true);
            }
            catch
            {
                // Best effort cleanup - test files will be cleaned by OS temp cleanup
            }
#pragma warning restore CA1031
        }
    }

    private async Task<JsonElement> CallToolAsync(string toolName, Dictionary<string, object?> args)
    {
        var result = await _client!.CallToolAsync(toolName, args, cancellationToken: _cts.Token);

        // Parse response - use pattern from McpServerSmokeTests
        var textBlock = result.Content.OfType<TextContentBlock>().FirstOrDefault();
        if (textBlock?.Text != null)
        {
            return JsonDocument.Parse(textBlock.Text).RootElement;
        }

        throw new InvalidOperationException($"Unexpected response from {toolName}");
    }

    #region List Action with Operation Tracking

    [Fact]
    public async Task List_ReturnsSessionsWithOperationStatus()
    {
        // Create a unique file and session for this test
        var testFile = Path.Join(_tempDir, $"ListTest_{Guid.NewGuid():N}.xlsx");
        var openResult = await CallToolAsync("file", new Dictionary<string, object?>
        {
            ["action"] = "create",
            ["path"] = testFile,
            ["show"] = false
        });

        Assert.True(openResult.GetProperty("success").GetBoolean());
        var sessionId = openResult.GetProperty("session_id").GetString();
        Assert.NotNull(sessionId);

        try
        {
            // List sessions
            var listResult = await CallToolAsync("file", new Dictionary<string, object?>
            {
                ["action"] = "list"
            });

            Assert.True(listResult.GetProperty("success").GetBoolean());

            var sessions = listResult.GetProperty("sessions");
            Assert.Equal(1, sessions.GetArrayLength());

            var session = sessions[0];
            Assert.Equal(sessionId, session.GetProperty("sessionId").GetString());
            Assert.True(session.TryGetProperty("activeOperations", out var activeOps));
            Assert.Equal(0, activeOps.GetInt32());
            Assert.True(session.TryGetProperty("canClose", out var canClose));
            Assert.True(canClose.GetBoolean());
            Assert.True(session.TryGetProperty("isExcelVisible", out var isVisible));
            Assert.False(isVisible.GetBoolean());
        }
        finally
        {
            // Cleanup
            await CallToolAsync("file", new Dictionary<string, object?>
            {
                ["action"] = "close",
                ["session_id"] = sessionId,
                ["save"] = false
            });
        }
    }

    [Fact]
    public async Task List_SessionWithShowExcelTrue_ReturnsIsExcelVisibleTrue()
    {
        // Create a unique file with show=true for this test
        var testFile = Path.Join(_tempDir, $"ShowExcelTest_{Guid.NewGuid():N}.xlsx");
        var openResult = await CallToolAsync("file", new Dictionary<string, object?>
        {
            ["action"] = "create",
            ["path"] = testFile,
            ["show"] = true
        });

        Assert.True(openResult.GetProperty("success").GetBoolean());
        var sessionId = openResult.GetProperty("session_id").GetString();

        try
        {
            // List sessions
            var listResult = await CallToolAsync("file", new Dictionary<string, object?>
            {
                ["action"] = "list"
            });

            var sessions = listResult.GetProperty("sessions");
            var session = sessions[0];
            Assert.True(session.GetProperty("isExcelVisible").GetBoolean());
        }
        finally
        {
            await CallToolAsync("file", new Dictionary<string, object?>
            {
                ["action"] = "close",
                ["session_id"] = sessionId,
                ["save"] = false
            });
        }
    }

    #endregion

    #region Close Blocking (Note: Hard to test without simulating parallel calls)

    [Fact]
    public async Task Close_NoOperationsRunning_ClosesSuccessfully()
    {
        // Create a unique file and session for this test
        var testFile = Path.Join(_tempDir, $"CloseTest_{Guid.NewGuid():N}.xlsx");
        var openResult = await CallToolAsync("file", new Dictionary<string, object?>
        {
            ["action"] = "create",
            ["path"] = testFile,
            ["show"] = false
        });

        var sessionId = openResult.GetProperty("session_id").GetString();

        // Close should succeed (no operations running)
        var closeResult = await CallToolAsync("file", new Dictionary<string, object?>
        {
            ["action"] = "close",
            ["session_id"] = sessionId,
            ["save"] = false
        });

        Assert.True(closeResult.GetProperty("success").GetBoolean());

        // Verify session is gone
        var listResult = await CallToolAsync("file", new Dictionary<string, object?>
        {
            ["action"] = "list"
        });

        Assert.Equal(0, listResult.GetProperty("count").GetInt32());
    }

    [Fact]
    public async Task Close_NonExistentSession_ReturnsError()
    {
        var closeResult = await CallToolAsync("file", new Dictionary<string, object?>
        {
            ["action"] = "close",
            ["session_id"] = "nonexistent-session-id",
            ["save"] = false
        });

        Assert.False(closeResult.GetProperty("success").GetBoolean());
        Assert.True(closeResult.TryGetProperty("errorMessage", out var errorMsg));
        Assert.Contains("not found", errorMsg.GetString());
    }

    #endregion
}






