using System.Collections.Concurrent;
using System.Diagnostics;
using Application.Abstractions.Messaging;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;

namespace Application.Messaging;

/// <summary>
/// MediatR ISender/IMediator를 대체하는 사내 디스패처입니다.
/// 요청의 런타임 타입으로 Handler를 해석(리플렉션 + 캐싱)하여 호출하며,
/// 기존 LoggingBehavior의 처리 시간/느린 요청 로깅을 그대로 수행합니다.
/// </summary>
internal sealed class Sender(IServiceProvider serviceProvider, ILogger<Sender> logger) : IMediator
{
    private const long SlowThresholdMs = 500;

    private static readonly ConcurrentDictionary<Type, ResponseHandlerWrapper> ResponseWrappers = new();
    private static readonly ConcurrentDictionary<Type, VoidHandlerWrapper> VoidWrappers = new();

    public async Task<TResponse> Send<TResponse>(IRequest<TResponse> request, CancellationToken cancellationToken = default)
    {
        ArgumentNullException.ThrowIfNull(request);

        var requestType = request.GetType();
        var wrapper = (ResponseHandlerWrapper<TResponse>)ResponseWrappers.GetOrAdd(
            requestType,
            static (type, responseType) => ResponseHandlerWrapper.Create(type, responseType),
            typeof(TResponse));

        var requestName = requestType.Name;
        var sw = Stopwatch.StartNew();

        try
        {
            var result = await wrapper.Handle(serviceProvider, request, cancellationToken);
            sw.Stop();
            LogCompleted(requestName, sw.ElapsedMilliseconds);

            return result;
        }
        catch (Exception ex)
        {
            sw.Stop();
            logger.LogError(ex, "[Failed] {Request} after {Elapsed}ms", requestName, sw.ElapsedMilliseconds);
            throw;
        }
    }

    public async Task Send(IRequest request, CancellationToken cancellationToken = default)
    {
        ArgumentNullException.ThrowIfNull(request);

        var requestType = request.GetType();
        var wrapper = VoidWrappers.GetOrAdd(requestType, static type => VoidHandlerWrapper.Create(type));

        var requestName = requestType.Name;
        var sw = Stopwatch.StartNew();

        try
        {
            await wrapper.Handle(serviceProvider, request, cancellationToken);
            sw.Stop();
            LogCompleted(requestName, sw.ElapsedMilliseconds);
        }
        catch (Exception ex)
        {
            sw.Stop();
            logger.LogError(ex, "[Failed] {Request} after {Elapsed}ms", requestName, sw.ElapsedMilliseconds);
            throw;
        }
    }

    private void LogCompleted(string requestName, long elapsedMs)
    {
        if (elapsedMs > SlowThresholdMs)
        {
            logger.LogWarning("[Slow] {Request} took {Elapsed}ms", requestName, elapsedMs);
        }
        else if (logger.IsEnabled(LogLevel.Debug))
        {
            logger.LogDebug("[Done] {Request} {Elapsed}ms", requestName, elapsedMs);
        }
    }

    // 반환값이 있는 Command/Query 핸들러 호출 래퍼 (런타임 타입별 1회 생성 후 캐시)
    private abstract class ResponseHandlerWrapper
    {
        public static ResponseHandlerWrapper Create(Type requestType, Type responseType)
        {
            var commandInterface = typeof(ICommand<>).MakeGenericType(responseType);
            var queryInterface = typeof(IQuery<>).MakeGenericType(responseType);

            var isCommand = commandInterface.IsAssignableFrom(requestType);
            var isQuery = queryInterface.IsAssignableFrom(requestType);

            // Handler 해석은 클래스 이름이 아니라 ICommand/IQuery 상속으로 결정됩니다.
            // 요청은 ICommand<T> 또는 IQuery<T> 중 정확히 하나만 구현해야 하며,
            // 그렇지 않으면 첫 디스패치 시 모호한 예외 대신 명확히 실패시킵니다.
            if (isCommand == isQuery)
            {
                throw new InvalidOperationException(
                    $"요청 타입 '{requestType.Name}'은(는) ICommand<{responseType.Name}> 또는 IQuery<{responseType.Name}> 중 정확히 하나만 구현해야 합니다.");
            }

            var openWrapper = isCommand
                ? typeof(CommandResponseWrapper<,>)
                : typeof(QueryResponseWrapper<,>);

            var closedWrapper = openWrapper.MakeGenericType(requestType, responseType);

            return (ResponseHandlerWrapper)Activator.CreateInstance(closedWrapper)!;
        }
    }

    private abstract class ResponseHandlerWrapper<TResponse> : ResponseHandlerWrapper
    {
        public abstract Task<TResponse> Handle(IServiceProvider provider, object request, CancellationToken cancellationToken);
    }

    private sealed class CommandResponseWrapper<TCommand, TResponse> : ResponseHandlerWrapper<TResponse>
        where TCommand : ICommand<TResponse>
    {
        public override Task<TResponse> Handle(IServiceProvider provider, object request, CancellationToken cancellationToken)
        {
            var handler = provider.GetRequiredService<ICommandHandler<TCommand, TResponse>>();

            return handler.Handle((TCommand)request, cancellationToken);
        }
    }

    private sealed class QueryResponseWrapper<TQuery, TResponse> : ResponseHandlerWrapper<TResponse>
        where TQuery : IQuery<TResponse>
    {
        public override Task<TResponse> Handle(IServiceProvider provider, object request, CancellationToken cancellationToken)
        {
            var handler = provider.GetRequiredService<IQueryHandler<TQuery, TResponse>>();

            return handler.Handle((TQuery)request, cancellationToken);
        }
    }

    // 반환값이 없는 Command 핸들러 호출 래퍼
    private abstract class VoidHandlerWrapper
    {
        public abstract Task Handle(IServiceProvider provider, object request, CancellationToken cancellationToken);

        public static VoidHandlerWrapper Create(Type requestType)
        {
            var closedWrapper = typeof(VoidCommandWrapper<>).MakeGenericType(requestType);

            return (VoidHandlerWrapper)Activator.CreateInstance(closedWrapper)!;
        }
    }

    private sealed class VoidCommandWrapper<TCommand> : VoidHandlerWrapper
        where TCommand : ICommand
    {
        public override Task Handle(IServiceProvider provider, object request, CancellationToken cancellationToken)
        {
            var handler = provider.GetRequiredService<ICommandHandler<TCommand>>();

            return handler.Handle((TCommand)request, cancellationToken);
        }
    }
}