using System.Data;
using Microsoft.EntityFrameworkCore;

namespace CMSMicroservice.Application.NetworkMembershipCQ.Queries.GetNetworkTree;

public class GetNetworkTreeQueryHandler : IRequestHandler<GetNetworkTreeQuery, NetworkTreeDto?>
{
    private readonly IApplicationDbContext _context;
    private readonly ILogger<GetNetworkTreeQueryHandler> _logger;

    public GetNetworkTreeQueryHandler(
        IApplicationDbContext context,
        ILogger<GetNetworkTreeQueryHandler> logger)
    {
        _context = context;
        _logger = logger;
    }

    public async Task<NetworkTreeDto?> Handle(GetNetworkTreeQuery request, CancellationToken cancellationToken)
    {
        // UserId must be provided - customer endpoints resolve from JWT before calling
        var userId = request.UserId;

        if (userId == 0)
        {
            throw new ArgumentException("UserId is required for network tree query");
        }

        // Create a new request with the resolved userId
        var resolvedRequest = request with { UserId = userId };

        try
        {
            // دریافت نتایج flat از Stored Procedure
            var flatNodes = await ExecuteStoredProcedureAsync(resolvedRequest, cancellationToken);
            
            if (flatNodes == null || !flatNodes.Any())
            {
                return null;
            }

            // تبدیل نتایج flat به ساختار درختی
            var tree = BuildTreeFromFlatNodes(flatNodes);
            return tree;
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Error executing GetNetworkTree for UserId: {UserId}", request.UserId);
            throw;
        }
    }

    /// <summary>
    /// اجرای Stored Procedure و دریافت نتایج
    /// </summary>
    private async Task<List<NetworkTreeNodeDto>> ExecuteStoredProcedureAsync(
        GetNetworkTreeQuery request, 
        CancellationToken cancellationToken)
    {
        var results = new List<NetworkTreeNodeDto>();
        
        var connection = _context.Database.GetDbConnection();
        
        try
        {
            if (connection.State != ConnectionState.Open)
            {
                await connection.OpenAsync(cancellationToken);
            }

            using var command = connection.CreateCommand();
            command.CommandText = "[CMS].[GetNetworkTree]";
            command.CommandType = CommandType.StoredProcedure;
            command.CommandTimeout = 120; // 2 minutes timeout for large trees

            // Parameters - use DbParameter for provider-agnostic code
            var rootUserIdParam = command.CreateParameter();
            rootUserIdParam.ParameterName = "@RootUserId";
            rootUserIdParam.DbType = DbType.Int64;
            rootUserIdParam.Value = request.UserId;
            command.Parameters.Add(rootUserIdParam);

            var maxDepthParam = command.CreateParameter();
            maxDepthParam.ParameterName = "@MaxDepth";
            maxDepthParam.DbType = DbType.Int32;
            maxDepthParam.Value = request.MaxDepth > 0 ? request.MaxDepth : 100;
            command.Parameters.Add(maxDepthParam);

            var isClubActiveParam = command.CreateParameter();
            isClubActiveParam.ParameterName = "@IsClubActive";
            isClubActiveParam.DbType = DbType.Boolean;
            isClubActiveParam.Value = request.IsClubActive.HasValue ? request.IsClubActive.Value : DBNull.Value;
            command.Parameters.Add(isClubActiveParam);

            var weekParam = command.CreateParameter();
            weekParam.ParameterName = "@ActivationWeekDefinitionId";
            weekParam.DbType = DbType.Int64;
            weekParam.Value = request.ActivationWeekDefinitionId.HasValue ? request.ActivationWeekDefinitionId.Value : DBNull.Value;
            command.Parameters.Add(weekParam);

            using var reader = await command.ExecuteReaderAsync(cancellationToken);
            
            while (await reader.ReadAsync(cancellationToken))
            {
                var node = new NetworkTreeNodeDto
                {
                    UserId = reader.GetInt64(reader.GetOrdinal("UserId")),
                    Mobile = reader.IsDBNull(reader.GetOrdinal("Mobile")) ? null : reader.GetString(reader.GetOrdinal("Mobile")),
                    FirstName = reader.IsDBNull(reader.GetOrdinal("FirstName")) ? null : reader.GetString(reader.GetOrdinal("FirstName")),
                    LastName = reader.IsDBNull(reader.GetOrdinal("LastName")) ? null : reader.GetString(reader.GetOrdinal("LastName")),
                    ReferralCode = reader.IsDBNull(reader.GetOrdinal("ReferralCode")) ? null : reader.GetString(reader.GetOrdinal("ReferralCode")),
                    LegPosition = reader.IsDBNull(reader.GetOrdinal("LegPosition")) ? null : reader.GetInt32(reader.GetOrdinal("LegPosition")),
                    ParentId = reader.IsDBNull(reader.GetOrdinal("ParentId")) ? null : reader.GetInt64(reader.GetOrdinal("ParentId")),
                    NetworkLevel = reader.GetInt32(reader.GetOrdinal("NetworkLevel")),
                    ClubActivatedAt = reader.IsDBNull(reader.GetOrdinal("ClubActivatedAt")) ? null : reader.GetDateTime(reader.GetOrdinal("ClubActivatedAt")),
                    IsClubActive = Convert.ToBoolean(reader.GetValue(reader.GetOrdinal("IsClubActive"))),
                    ActivationWeekDefinitionId = reader.IsDBNull(reader.GetOrdinal("ActivationWeekDefinitionId")) ? null : reader.GetInt64(reader.GetOrdinal("ActivationWeekDefinitionId")),
                    ActivationWeekDisplayName = reader.IsDBNull(reader.GetOrdinal("ActivationWeekDisplayName")) ? null : reader.GetString(reader.GetOrdinal("ActivationWeekDisplayName")),
                    IsActivatedInTargetWeek = Convert.ToBoolean(reader.GetValue(reader.GetOrdinal("IsActivatedInTargetWeek"))),
                    UserCreated = new DateTimeOffset(reader.GetDateTime(reader.GetOrdinal("UserCreated"))),
                    IsNewActivation = reader.IsDBNull(reader.GetOrdinal("IsNewActivation")) ? null : (bool?)(reader.GetInt32(reader.GetOrdinal("IsNewActivation")) == 1),
                    PackageName = reader.IsDBNull(reader.GetOrdinal("PackageName")) ? null : reader.GetString(reader.GetOrdinal("PackageName")),
                    PackageId = reader.IsDBNull(reader.GetOrdinal("PackageId")) ? null : reader.GetInt64(reader.GetOrdinal("PackageId"))
                };
                
                results.Add(node);
            }
        }
        finally
        {
            // Connection is managed by DbContext, don't close it here
        }

        _logger.LogInformation("GetNetworkTree SP returned {Count} nodes for UserId: {UserId}", results.Count, request.UserId);
        return results;
    }

    /// <summary>
    /// تبدیل لیست flat به ساختار درختی
    /// </summary>
    private NetworkTreeDto? BuildTreeFromFlatNodes(List<NetworkTreeNodeDto> flatNodes)
    {
        if (!flatNodes.Any()) return null;

        // Dictionary برای دسترسی سریع به نودها
        var nodeDict = new Dictionary<long, NetworkTreeDto>();

        // ایجاد همه نودها
        foreach (var flatNode in flatNodes)
        {
            nodeDict[flatNode.UserId] = new NetworkTreeDto
            {
                UserId = flatNode.UserId,
                Mobile = flatNode.Mobile,
                FirstName = flatNode.FirstName,
                LastName = flatNode.LastName,
                ReferralCode = flatNode.ReferralCode,
                LegPosition = flatNode.LegPosition.HasValue ? (NetworkLeg)flatNode.LegPosition.Value : null,
                CurrentDepth = flatNode.NetworkLevel,
                ClubActivatedAt = flatNode.ClubActivatedAt,
                IsClubActive = flatNode.IsClubActive,
                ActivationWeekDefinitionId = flatNode.ActivationWeekDefinitionId,
                ActivationWeekDisplayName = flatNode.ActivationWeekDisplayName,
                IsActivatedInTargetWeek = flatNode.IsActivatedInTargetWeek,
                UserCreated = flatNode.UserCreated,
                IsNewActivation = flatNode.IsNewActivation,
                PackageName = flatNode.PackageName,
                PackageId = flatNode.PackageId
            };
        }

        // برقراری ارتباط Parent-Child
        foreach (var flatNode in flatNodes)
        {
            if (flatNode.ParentId.HasValue && nodeDict.ContainsKey(flatNode.ParentId.Value))
            {
                var parent = nodeDict[flatNode.ParentId.Value];
                var child = nodeDict[flatNode.UserId];

                // تعیین موقعیت چپ یا راست
                if (flatNode.LegPosition == (int)NetworkLeg.Left)
                {
                    parent.LeftChild = child;
                }
                else if (flatNode.LegPosition == (int)NetworkLeg.Right)
                {
                    parent.RightChild = child;
                }
            }
        }

        // پیدا کردن ریشه (اولین نود با Level=0)
        var rootNode = flatNodes.FirstOrDefault(n => n.NetworkLevel == 0);
        return rootNode != null ? nodeDict[rootNode.UserId] : null;
    }
}
