// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

using System.Collections;
using System.Diagnostics;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
using Microsoft.Teams.Core.Diagnostics;
using Microsoft.Teams.Core.Schema;

namespace Microsoft.Teams.Core;

/// <summary>
/// Manages and executes a middleware pipeline for processing bot turns.
/// </summary>
/// <remarks>
/// This class implements a chain of responsibility pattern where each middleware component can process
/// an activity before passing control to the next middleware in the pipeline. The pipeline executes
/// sequentially, with each middleware having the opportunity to modify the activity, perform side effects,
/// or short-circuit the pipeline. Middleware is executed in the order it was registered via the Use method.
/// </remarks>
internal sealed class TurnMiddleware : ITurnMiddleware, IEnumerable<ITurnMiddleware>
{
    private readonly IList<ITurnMiddleware> _middlewares = [];
    private ILogger _logger = NullLogger.Instance;

    internal void SetLogger(ILogger logger) => _logger = logger;

    /// <summary>
    /// Adds a middleware component to the end of the pipeline.
    /// </summary>
    /// <param name="middleware">The middleware to add. Cannot be null.</param>
    /// <returns>The current TurnMiddleware instance for method chaining.</returns>
    internal TurnMiddleware Use(ITurnMiddleware middleware)
    {
        _middlewares.Add(middleware);
        _logger.MiddlewareRegistered(middleware.GetType().Name, _middlewares.Count);
        return this;
    }

    /// <summary>
    /// Processes a turn by executing the middleware pipeline.
    /// </summary>
    /// <param name="botApplication">The bot application processing the turn.</param>
    /// <param name="activity">The activity to process.</param>
    /// <param name="next">Delegate to invoke the next middleware in the outer pipeline.</param>
    /// <param name="cancellationToken">A cancellation token that can be used to cancel the operation.</param>
    /// <returns>A task that represents the asynchronous pipeline execution.</returns>
    public async Task OnTurnAsync(BotApplication botApplication, CoreActivity activity, NextTurn next, CancellationToken cancellationToken = default)
    {
        await RunPipelineAsync(botApplication, activity, null!, 0, cancellationToken).ConfigureAwait(false);
        await next(cancellationToken).ConfigureAwait(false);
    }

    /// <summary>
    /// Recursively executes the middleware pipeline starting from the specified index.
    /// </summary>
    /// <param name="botApplication">The bot application processing the turn.</param>
    /// <param name="activity">The activity to process.</param>
    /// <param name="callback">Optional callback to invoke after all middleware has executed.</param>
    /// <param name="nextMiddlewareIndex">The index of the next middleware to execute in the pipeline.</param>
    /// <param name="cancellationToken">A cancellation token that can be used to cancel the operation.</param>
    /// <returns>A task that represents the asynchronous pipeline execution.</returns>
    public async Task RunPipelineAsync(BotApplication botApplication, CoreActivity activity, Func<CoreActivity, CancellationToken, Task>? callback, int nextMiddlewareIndex, CancellationToken cancellationToken)
    {
        if (nextMiddlewareIndex == _middlewares.Count)
        {
            if (nextMiddlewareIndex > 0)
            {
                _logger.MiddlewarePipelineCompleted(nextMiddlewareIndex);
            }
            if (callback is not null)
            {
                await (callback(activity, cancellationToken) ?? Task.CompletedTask).ConfigureAwait(false);
            }
            return;
        }

        ITurnMiddleware nextMiddleware = _middlewares[nextMiddlewareIndex];
        string middlewareName = nextMiddleware.GetType().Name;
        _logger.MiddlewareExecuting(middlewareName, nextMiddlewareIndex + 1, _middlewares.Count);

        using Activity? span = Telemetry.Source.StartActivity(Telemetry.Spans.Middleware, ActivityKind.Internal);
        if (span is not null)
        {
            span.SetTag(Telemetry.Tags.MiddlewareName, middlewareName);
            span.SetTag(Telemetry.Tags.MiddlewareIndex, nextMiddlewareIndex);
        }

        KeyValuePair<string, object?> mwTag = new(Telemetry.Tags.MiddlewareName, middlewareName);
        long start = Stopwatch.GetTimestamp();
        try
        {
            await nextMiddleware.OnTurnAsync(
                botApplication,
                activity,
                (ct) => RunPipelineAsync(botApplication, activity, callback, nextMiddlewareIndex + 1, ct),
                cancellationToken).ConfigureAwait(false);
        }
        catch (Exception ex)
        {
            span.RecordException(ex);
            throw;
        }
        finally
        {
            Telemetry.MiddlewareDuration.Record(Stopwatch.GetElapsedTime(start).TotalMilliseconds, mwTag);
        }
    }

    public IEnumerator<ITurnMiddleware> GetEnumerator()
    {
        return _middlewares.GetEnumerator();
    }

    IEnumerator IEnumerable.GetEnumerator()
    {
        return GetEnumerator();
    }
}
