using Microsoft.EntityFrameworkCore;
using SocialService.Domain.AggregatesModel.RelationshipAggregate;
using SocialService.Domain.Services;

namespace SocialService.Infrastructure.Services;

/// <summary>
/// EN: Graph query service implementation using PostgreSQL Recursive CTEs.
/// VI: Implementation graph query service sử dụng PostgreSQL Recursive CTEs.
/// </summary>
public class GraphQueryService : IGraphQueryService
{
    private readonly SocialServiceContext _context;

    public GraphQueryService(SocialServiceContext context)
    {
        _context = context ?? throw new ArgumentNullException(nameof(context));
    }

    /// <summary>
    /// EN: Get mutual friends between two users.
    /// VI: Lấy danh sách bạn chung giữa hai users.
    /// </summary>
    public async Task<IEnumerable<Guid>> GetMutualFriendsAsync(Guid userId1, Guid userId2)
    {
        // EN: Find users who are friends with both userId1 and userId2
        // VI: Tìm users là bạn của cả userId1 và userId2
        var sql = @"
            WITH user1_friends AS (
                SELECT CASE 
                    WHEN requester_id = {0} THEN addressee_id 
                    ELSE requester_id 
                END AS friend_id
                FROM relationships
                WHERE type_id = {2}
                AND status_id = {3}
                AND (requester_id = {0} OR addressee_id = {0})
            ),
            user2_friends AS (
                SELECT CASE 
                    WHEN requester_id = {1} THEN addressee_id 
                    ELSE requester_id 
                END AS friend_id
                FROM relationships
                WHERE type_id = {2}
                AND status_id = {3}
                AND (requester_id = {1} OR addressee_id = {1})
            )
            SELECT u1.friend_id
            FROM user1_friends u1
            INNER JOIN user2_friends u2 ON u1.friend_id = u2.friend_id";

        var result = await _context.Database
            .SqlQueryRaw<Guid>(sql, 
                userId1, 
                userId2, 
                RelationshipType.Friendship.Id, 
                RelationshipStatus.Accepted.Id)
            .ToListAsync();

        return result;
    }

    /// <summary>
    /// EN: Get friend suggestions based on mutual friends (friends of friends).
    /// VI: Gợi ý kết bạn dựa trên bạn chung (bạn của bạn).
    /// </summary>
    public async Task<IEnumerable<FriendSuggestion>> GetFriendSuggestionsAsync(Guid userId, int limit = 10)
    {
        // EN: Find friends of friends who are not already friends with the user
        // VI: Tìm bạn của bạn mà chưa là bạn với user
        var sql = @"
            WITH my_friends AS (
                SELECT CASE 
                    WHEN requester_id = {0} THEN addressee_id 
                    ELSE requester_id 
                END AS friend_id
                FROM relationships
                WHERE type_id = {1}
                AND status_id = {2}
                AND (requester_id = {0} OR addressee_id = {0})
            ),
            friends_of_friends AS (
                SELECT CASE 
                    WHEN r.requester_id = mf.friend_id THEN r.addressee_id 
                    ELSE r.requester_id 
                END AS suggested_id,
                mf.friend_id AS via_friend_id
                FROM relationships r
                INNER JOIN my_friends mf ON (r.requester_id = mf.friend_id OR r.addressee_id = mf.friend_id)
                WHERE r.type_id = {1}
                AND r.status_id = {2}
                AND CASE 
                    WHEN r.requester_id = mf.friend_id THEN r.addressee_id 
                    ELSE r.requester_id 
                END != {0}
            ),
            suggestions AS (
                SELECT 
                    suggested_id,
                    COUNT(*) AS mutual_count
                FROM friends_of_friends fof
                WHERE suggested_id NOT IN (SELECT friend_id FROM my_friends)
                AND NOT EXISTS (
                    SELECT 1 FROM user_blocks ub 
                    WHERE (ub.blocker_id = {0} AND ub.blocked_id = fof.suggested_id)
                    OR (ub.blocker_id = fof.suggested_id AND ub.blocked_id = {0})
                )
                GROUP BY suggested_id
            )
            SELECT suggested_id, mutual_count
            FROM suggestions
            ORDER BY mutual_count DESC
            LIMIT {3}";

        var result = await _context.Database
            .SqlQueryRaw<FriendSuggestionResult>(sql, 
                userId, 
                RelationshipType.Friendship.Id, 
                RelationshipStatus.Accepted.Id,
                limit)
            .ToListAsync();

        return result.Select(r => new FriendSuggestion(r.suggested_id, r.mutual_count));
    }

    /// <summary>
    /// EN: Get the shortest connection path between two users (degrees of separation).
    /// VI: Lấy đường dẫn kết nối ngắn nhất giữa hai users (bậc tách biệt).
    /// </summary>
    public async Task<int> GetConnectionDegreeAsync(Guid userId1, Guid userId2)
    {
        const int maxDepth = 6;
        
        // EN: Use recursive CTE with limited depth (BFS)
        // VI: Sử dụng recursive CTE với độ sâu giới hạn (BFS)
        var sql = @"
            WITH RECURSIVE connections AS (
                -- Base case: direct friends of userId1
                SELECT 
                    CASE WHEN requester_id = {0} THEN addressee_id ELSE requester_id END AS user_id,
                    1 AS degree,
                    ARRAY[{0}] AS path
                FROM relationships
                WHERE type_id = {2}
                AND status_id = {3}
                AND (requester_id = {0} OR addressee_id = {0})
                
                UNION ALL
                
                -- Recursive case: friends of friends
                SELECT 
                    CASE WHEN r.requester_id = c.user_id THEN r.addressee_id ELSE r.requester_id END AS user_id,
                    c.degree + 1 AS degree,
                    c.path || c.user_id
                FROM relationships r
                INNER JOIN connections c ON (r.requester_id = c.user_id OR r.addressee_id = c.user_id)
                WHERE r.type_id = {2}
                AND r.status_id = {3}
                AND c.degree < {4}
                AND NOT (c.path @> ARRAY[CASE WHEN r.requester_id = c.user_id THEN r.addressee_id ELSE r.requester_id END])
            )
            SELECT COALESCE(MIN(degree), -1) AS min_degree
            FROM connections
            WHERE user_id = {1}";

        var result = await _context.Database
            .SqlQueryRaw<int>(sql, 
                userId1, 
                userId2, 
                RelationshipType.Friendship.Id, 
                RelationshipStatus.Accepted.Id,
                maxDepth)
            .FirstOrDefaultAsync();

        return result;
    }

    /// <summary>
    /// EN: Get mutual friends count between two users.
    /// VI: Lấy số lượng bạn chung giữa hai users.
    /// </summary>
    public async Task<int> GetMutualFriendsCountAsync(Guid userId1, Guid userId2)
    {
        var mutualFriends = await GetMutualFriendsAsync(userId1, userId2);
        return mutualFriends.Count();
    }

    // EN: Internal class for mapping SQL result
    // VI: Class nội bộ để map kết quả SQL
    private class FriendSuggestionResult
    {
        public Guid suggested_id { get; set; }
        public int mutual_count { get; set; }
    }
}
