using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Text.Json;
using System.Text.Json.Serialization;
using Sbroenne.ExcelMcp.McpServer.Telemetry;

#pragma warning disable IL2070 // 'this' argument does not satisfy 'DynamicallyAccessedMembersAttribute' requirements

namespace Sbroenne.ExcelMcp.McpServer.Tools;

/// <summary>
/// Base class for Excel MCP tools providing common patterns and utilities.
/// All Excel tools inherit from this to ensure consistency for LLM usage.
///
/// The MCP Server forwards ALL requests to the in-process ExcelMCP Service.
/// </summary>
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods)]
public static class ExcelToolsBase
{
    private static readonly AsyncLocal<CancellationToken> CurrentCancellationToken = new();

    private sealed class CancellationTokenScope : IDisposable
    {
        private readonly CancellationToken _previousToken;
        private bool _disposed;

        public CancellationTokenScope(CancellationToken token)
        {
            _previousToken = CurrentCancellationToken.Value;
            CurrentCancellationToken.Value = token;
        }

        public void Dispose()
        {
            if (_disposed)
            {
                return;
            }

            CurrentCancellationToken.Value = _previousToken;
            _disposed = true;
        }
    }

    /// <summary>
    /// Ensures the ExcelMCP Service is running.
    /// The service is required for all MCP Server operations.
    /// </summary>
    public static async Task<bool> EnsureServiceAsync(CancellationToken cancellationToken = default)
    {
        return await ServiceBridge.ServiceBridge.EnsureServiceAsync(cancellationToken);
    }

    /// <summary>
    /// JSON serializer options optimized for LLM token efficiency.
    /// Uses compact formatting to reduce token consumption.
    /// </summary>
    /// <remarks>
    /// Token optimization settings:
    /// - WriteIndented = false: Removes whitespace (saves ~20% tokens)
    /// - DefaultIgnoreCondition = WhenWritingNull: Omits null properties
    /// - PropertyNamingPolicy = CamelCase: Consistent naming (e.g., success, errorMessage, filePath)
    /// - JsonStringEnumConverter: Human-readable enum values
    /// </remarks>
    public static readonly JsonSerializerOptions JsonOptions = new()
    {
        WriteIndented = false,
        DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
        PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
        Converters = { new JsonStringEnumConverter() }
    };

    /// <summary>
    /// Delegate wrapper for ForwardToService matching the generated code signature.
    /// Used by generated RouteAction methods.
    /// </summary>
    public static readonly Func<string, string, object?, string> ForwardToServiceFunc =
        (command, sessionId, args) => ForwardToService(command, sessionId, args);

    internal static IDisposable PushCancellationToken(CancellationToken cancellationToken) =>
        new CancellationTokenScope(cancellationToken);

    /// <summary>
    /// Forwards a command to the ExcelMCP Service and returns the JSON response.
    /// This is the primary method for MCP tools to execute commands.
    ///
    /// The command format is "category.action", e.g., "sheet.list", "range.get-values".
    /// The service handles session management and Core command execution.
    /// </summary>
    /// <param name="command">Service command in format "category.action"</param>
    /// <param name="sessionId">Session ID for the operation</param>
    /// <param name="args">Optional arguments object to serialize</param>
    /// <param name="timeoutSeconds">Optional timeout override</param>
    /// <returns>JSON response from service</returns>
    public static string ForwardToService(
        string command,
        string? sessionId,
        object? args = null,
        int? timeoutSeconds = null)
    {
        var response = ServiceBridge.ServiceBridge.SendAsync(
            command,
            sessionId,
            args,
            timeoutSeconds,
            CurrentCancellationToken.Value).GetAwaiter().GetResult();

        if (!response.Success)
        {
            var errorMessage = response.ErrorMessage ?? $"Command '{command}' failed";
            return JsonSerializer.Serialize(new
            {
                success = false,
                error = errorMessage,
                errorMessage,
                errorCategory = response.ErrorCategory,
                command = response.Command,
                sessionId = response.SessionId,
                exceptionType = response.ExceptionType,
                hresult = response.HResult,
                innerError = response.InnerError,
                isError = true
            }, JsonOptions);
        }

        return response.Result ?? JsonSerializer.Serialize(new
        {
            success = true
        }, JsonOptions);
    }

    /// <summary>
    /// Forwards a command to the ExcelMCP Service without a session.
    /// Used for commands that don't require an active session (e.g., service.status).
    /// </summary>
    public static string ForwardToServiceNoSession(
        string command,
        object? args = null,
        int? timeoutSeconds = null)
    {
        var response = ServiceBridge.ServiceBridge.SendAsync(
            command,
            null,
            args,
            timeoutSeconds,
            CurrentCancellationToken.Value).GetAwaiter().GetResult();

        if (!response.Success)
        {
            var errorMessage = response.ErrorMessage ?? $"Command '{command}' failed";
            return JsonSerializer.Serialize(new
            {
                success = false,
                error = errorMessage,
                errorMessage,
                errorCategory = response.ErrorCategory,
                command = response.Command,
                sessionId = response.SessionId,
                exceptionType = response.ExceptionType,
                hresult = response.HResult,
                innerError = response.InnerError,
                isError = true
            }, JsonOptions);
        }

        return response.Result ?? JsonSerializer.Serialize(new
        {
            success = true
        }, JsonOptions);
    }

    /// <summary>
    /// Executes a tool operation and serializes any exception using shared error formatting.
    /// Tracks tool usage telemetry (if enabled).
    /// </summary>
    /// <param name="toolName">Tool name for telemetry (e.g., "range").</param>
    /// <param name="actionName">Action string (kebab-case) included in error context.</param>
    /// <param name="operation">Synchronous operation to execute.</param>
    /// <param name="customHandler">Optional handler that can override default error serialization. Return null/empty to fall back to default.</param>
    /// <returns>Serialized JSON response.</returns>
    public static string ExecuteToolAction(
        string toolName,
        string actionName,
        Func<string> operation,
        Func<Exception, string?>? customHandler = null) =>
        ExecuteToolAction(toolName, actionName, null, operation, customHandler);

    /// <summary>
    /// Executes a tool operation and serializes any exception using shared error formatting.
    /// Tracks tool usage telemetry (if enabled).
    /// </summary>
    /// <param name="toolName">Tool name for telemetry (e.g., "range").</param>
    /// <param name="actionName">Action string (kebab-case) included in error context.</param>
    /// <param name="path">Optional Excel path for context in error messages.</param>
    /// <param name="operation">Synchronous operation to execute.</param>
    /// <param name="customHandler">Optional handler that can override default error serialization. Return null/empty to fall back to default.</param>
    /// <returns>Serialized JSON response.</returns>
    public static string ExecuteToolAction(
        string toolName,
        string actionName,
        string? path,
        Func<string> operation,
        Func<Exception, string?>? customHandler = null)
    {
        var stopwatch = Stopwatch.StartNew();
        var success = false;

        try
        {
            var result = operation();
            success = true;
            return result;
        }
        catch (Exception ex)
        {
            // Log COM exceptions to stderr for diagnostic capture
            if (ex is System.Runtime.InteropServices.COMException comEx)
            {
                Console.Error.WriteLine($"[ExcelMcp] COM Exception in {toolName}/{actionName}: HResult=0x{comEx.HResult:X8}, Message={comEx.Message}");
                if (ex.StackTrace != null)
                {
                    Console.Error.WriteLine($"[ExcelMcp] StackTrace: {ex.StackTrace[..Math.Min(500, ex.StackTrace.Length)]}");
                }
            }
            else if (ex.InnerException is System.Runtime.InteropServices.COMException innerComEx)
            {
                Console.Error.WriteLine($"[ExcelMcp] Inner COM Exception in {toolName}/{actionName}: HResult=0x{innerComEx.HResult:X8}, Message={innerComEx.Message}");
            }

            if (customHandler != null)
            {
                var custom = customHandler(ex);
                if (!string.IsNullOrWhiteSpace(custom))
                {
                    return custom!;
                }
            }

            return SerializeToolError(actionName, path, ex);
        }
        finally
        {
            stopwatch.Stop();
            ExcelMcpTelemetry.TrackToolInvocation(toolName, actionName, stopwatch.ElapsedMilliseconds, success, path);
        }
    }

    /// <summary>
    /// Validates that a path is a valid Windows absolute path.
    /// Returns null if valid, or a JSON error response if invalid.
    /// </summary>
    /// <param name="path">The path to validate</param>
    /// <returns>JSON error response if invalid, null if valid</returns>
    public static string? ValidateWindowsPath(string? path)
    {
        if (string.IsNullOrWhiteSpace(path))
        {
            return null; // Let existing null checks handle this
        }

        // Use .NET's built-in check for fully qualified Windows paths
        // Returns false for Unix paths like /home/user/file.xlsx, relative paths like ./file.xlsx
        if (!Path.IsPathFullyQualified(path))
        {
            // Extract filename from the invalid path (works for both Unix and Windows separators)
            var fileName = Path.GetFileName(path.Replace('/', Path.DirectorySeparatorChar));
            if (string.IsNullOrEmpty(fileName))
            {
                fileName = "workbook.xlsx";
            }

            // Get user's actual Documents folder to provide a valid suggestion
            var documentsFolder = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments);
            var suggestedPath = Path.Combine(documentsFolder, fileName);

            var errorMessage = path.StartsWith('/')
                ? $"Invalid path format: '{path}' appears to be a Unix/Linux path. This server runs on Windows. Use: '{suggestedPath}'"
                : $"Invalid path format: '{path}' is not an absolute Windows path. Use: '{suggestedPath}'";

            return JsonSerializer.Serialize(new
            {
                success = false,
                error = errorMessage,
                errorMessage,
                filePath = path,
                suggestedPath,
                documentsFolder,
                isError = true
            }, JsonOptions);
        }

        return null;
    }

    /// <summary>
    /// Serializes a tool error response with consistent structure.
    /// Uses camelCase property names matching JsonNamingPolicy: success, errorMessage, isError.
    /// Includes detailed COM exception info for diagnostics.
    /// </summary>
    /// <param name="actionName">Action string (kebab-case) included in message.</param>
    /// <param name="path">Optional Excel path context.</param>
    /// <param name="ex">Exception to serialize.</param>
    /// <returns>Serialized JSON error payload.</returns>
    public static string SerializeToolError(string actionName, string? path, Exception ex)
    {
        var errorMessage = path != null
            ? $"{actionName} failed for '{path}': {ex.Message}"
            : $"{actionName} failed: {ex.Message}";

        // Add detailed COM exception info for diagnostics
        string? exceptionType = ex.GetType().Name;
        string? hresult = null;
        string? innerError = null;

        if (ex is System.Runtime.InteropServices.COMException comEx)
        {
            hresult = $"0x{comEx.HResult:X8}";
            errorMessage += $" [COM Error: {hresult}]";
        }

        if (ex.InnerException != null)
        {
            innerError = ex.InnerException.Message;
            if (ex.InnerException is System.Runtime.InteropServices.COMException innerComEx)
            {
                innerError += $" [COM: 0x{innerComEx.HResult:X8}]";
            }
        }

        var payload = new
        {
            success = false,
            error = errorMessage,
            errorMessage,
            isError = true,
            exceptionType,
            hresult,
            innerError
        };

        return JsonSerializer.Serialize(payload, JsonOptions);
    }

    /// <summary>
    /// Returns a JSON error response when the required 'action' parameter is missing.
    /// Used by generated tool methods to handle null action gracefully instead of
    /// throwing an unhandled exception at the framework level.
    /// </summary>
    /// <param name="toolName">Tool name for error context.</param>
    /// <returns>JSON error payload with isError=true.</returns>
    public static string MissingActionError(string toolName)
    {
        return JsonSerializer.Serialize(new
        {
            success = false,
            error = $"The 'action' parameter is required for the '{toolName}' tool. Provide a valid action value.",
            errorMessage = $"The 'action' parameter is required for the '{toolName}' tool. Provide a valid action value.",
            isError = true
        }, JsonOptions);
    }
}




