using Knot.Shared.Kernel;
using Knot.Contracts.Profiles.Domain;
using Knot.Contracts.Profiles.Application.DTOs;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using MediatR;
using System;
using Knot.Contracts.Relations.Application.Contacts;
using Knot.Contracts.Auth.Application.Abstractions;

namespace Knot.Modules.Profiles.Application.Profiles.Search;

public sealed record SearchProfilesQuery(string Query, Guid UserId) : IQuery<List<UserProfileDto>>;

internal sealed class SearchProfilesQueryHandler : IQueryHandler<SearchProfilesQuery, List<UserProfileDto>>
{
    private readonly IProfileRepository _repository;
    private readonly ISender _sender;

    public SearchProfilesQueryHandler(IProfileRepository repository, ISender sender)
    {
        _repository = repository;
        _sender = sender;
    }

    public async Task<Result<List<UserProfileDto>>> Handle(SearchProfilesQuery request, CancellationToken cancellationToken)
    {
        // 1. Fetch candidates from MongoDB (which might have orphans)
        var profiles = await _repository.SearchAsync(request.Query, 20, cancellationToken);
        
        var candidates = profiles.Where(p => p.UserId != request.UserId).ToList();
        if (!candidates.Any()) return Result.Success(new List<UserProfileDto>());

        var candidateIds = candidates.Select(p => p.UserId).ToList();

        // 2. Validate existence in Auth module (to filter out orphans from deleted accounts)
        var existingIds = new List<Guid>();
        try {
            var existenceResult = await _sender.Send(new GetUsersExistenceQuery(candidateIds), cancellationToken);
            if (existenceResult.IsSuccess) existingIds = existenceResult.Value;
        } catch { 
            // If Auth module is not available, we assume all exist to avoid empty results
            existingIds = candidateIds;
        }

        // 3. Remove orphans (and physically delete them from Mongo if they don't exist in Auth)
        var validCandidates = candidates.Where(p => existingIds.Contains(p.UserId)).ToList();
        var orphanIds = candidateIds.Except(existingIds).ToList();
        foreach (var orphanId in orphanIds)
        {
            // Background cleanup (fire and forget or just do it since it's only a few)
            _ = _repository.DeleteAsync(orphanId, CancellationToken.None);
        }

        if (!validCandidates.Any()) return Result.Success(new List<UserProfileDto>());

        // 4. Check for blocked users among valid candidates
        var blockedIds = new List<Guid>();
        try {
            var blockedResult = await _sender.Send(new CheckBlockedStatusQuery(request.UserId, validCandidates.Select(v => v.UserId).ToList()), cancellationToken);
            if (blockedResult.IsSuccess) blockedIds = blockedResult.Value;
        } catch { }

        // final filter
        var filtered = validCandidates
            .Where(p => !blockedIds.Contains(p.UserId))
            .ToList();

        return Result.Success(filtered);
    }
}
