using Microsoft.Extensions.Options; using MongoDB.Bson; using MongoDB.Driver; using Postall.Domain; using Postall.Domain.Entities; using Postall.Infra.MongoDB.Settings; using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; namespace Postall.Infra.MongoDB.Repositories { public class VideoRepository : IVideoRepository { private readonly IMongoCollection _videosCollection; public VideoRepository(IOptions mongoDbSettings) { var client = new MongoClient(mongoDbSettings.Value.ConnectionString); var database = client.GetDatabase(mongoDbSettings.Value.DatabaseName); _videosCollection = database.GetCollection(mongoDbSettings.Value.VideosCollectionName); // Cria índice composto para videoId e userId var indexKeysDefinition = Builders.IndexKeys .Ascending(v => v.VideoId) .Ascending(v => v.UserId); _videosCollection.Indexes.CreateOne(new CreateIndexModel(indexKeysDefinition, new CreateIndexOptions { Unique = true })); // Cria índice para buscas por texto var textIndexDefinition = Builders.IndexKeys .Text(v => v.Title) .Text(v => v.Description); _videosCollection.Indexes.CreateOne(new CreateIndexModel(textIndexDefinition)); // Cria índice para buscas por channelId var channelIndexDefinition = Builders.IndexKeys .Ascending(v => v.ChannelId); _videosCollection.Indexes.CreateOne(new CreateIndexModel(channelIndexDefinition)); // Cria índice para userId + channelId var userChannelIndexDefinition = Builders.IndexKeys .Ascending(v => v.UserId) .Ascending(v => v.ChannelId); _videosCollection.Indexes.CreateOne(new CreateIndexModel(userChannelIndexDefinition)); } /// /// Obtém todos os vídeos /// public async Task> GetAllAsync() { return await _videosCollection.Find(v => true).ToListAsync(); } /// /// Obtém um vídeo pelo ID do MongoDB /// public async Task GetByIdAsync(string id) { if (!ObjectId.TryParse(id, out _)) return null; return await _videosCollection.Find(v => v.Id == id).FirstOrDefaultAsync(); } /// /// Obtém um vídeo pelo ID do YouTube /// public async Task GetByVideoIdAsync(string videoId) { return await _videosCollection.Find(v => v.VideoId == videoId).FirstOrDefaultAsync(); } /// /// Obtém os vídeos pelo ID do usuário /// public async Task> GetByUserIdAsync(string userId) { return await _videosCollection.Find(v => v.UserId == userId).ToListAsync(); } /// /// Obtém os vídeos pelo ID do canal /// public async Task> GetByChannelIdAsync(string channelId) { return await _videosCollection.Find(v => v.ChannelId == channelId).ToListAsync(); } /// /// Obtém os vídeos pelo ID do usuário e ID do canal /// public async Task> GetByUserIdAndChannelIdAsync(string userId, string channelId) { return await _videosCollection.Find(v => v.UserId == userId && v.ChannelId == channelId).ToListAsync(); } /// /// Obtém um vídeo pelo ID do usuário e ID do vídeo /// public async Task GetByUserIdAndVideoIdAsync(string userId, string videoId) { return await _videosCollection.Find(v => v.UserId == userId && v.VideoId == videoId).FirstOrDefaultAsync(); } /// /// Adiciona um novo vídeo /// public async Task AddAsync(VideoData videoData) { var existingVideo = await GetByUserIdAndVideoIdAsync(videoData.UserId, videoData.VideoId); if (existingVideo != null) return existingVideo; if (string.IsNullOrEmpty(videoData.Id) || !ObjectId.TryParse(videoData.Id, out _)) { videoData.Id = ObjectId.GenerateNewId().ToString(); } videoData.CreatedAt = DateTime.UtcNow; try { await _videosCollection.InsertOneAsync(videoData); } catch (MongoWriteException ex) { if (ex.WriteError.Category == ServerErrorCategory.DuplicateKey) { return await GetByUserIdAndVideoIdAsync(videoData.UserId, videoData.VideoId); } else { throw; } } return videoData; } /// /// Adiciona vários vídeos de uma vez /// public async Task> AddManyAsync(IEnumerable videos) { if (videos == null || !videos.Any()) return new List(); var videosList = videos.ToList(); var now = DateTime.UtcNow; // Gera novos IDs para o MongoDB se necessário foreach (var videoData in videosList) { if (string.IsNullOrEmpty(videoData.Id) || !ObjectId.TryParse(videoData.Id, out _)) { videoData.Id = ObjectId.GenerateNewId().ToString(); } videoData.CreatedAt = now; } // Verifica vídeos existentes pelo ID do YouTube e usuário var userIds = videosList.Select(v => v.UserId).Distinct().ToList(); var videoIds = videosList.Select(v => v.VideoId).ToList(); var filter = Builders.Filter.And( Builders.Filter.In(v => v.UserId, userIds), Builders.Filter.In(v => v.VideoId, videoIds) ); var existingVideos = await _videosCollection.Find(filter).ToListAsync(); // Filtra apenas os vídeos que ainda não foram adicionados var existingPairs = existingVideos .Select(v => $"{v.UserId}:{v.VideoId}") .ToHashSet(); var newVideos = videosList .Where(v => !existingPairs.Contains($"{v.UserId}:{v.VideoId}")) .ToList(); if (newVideos.Any()) { try { await _videosCollection.InsertManyAsync(newVideos); } catch (MongoBulkWriteException) { // Em caso de erro, adiciona um por um foreach (var video in newVideos) { try { await AddAsync(video); } catch { /* Ignora erros individuais */ } } } } return newVideos; } /// /// Atualiza um vídeo existente /// public async Task UpdateAsync(VideoData videoData) { if (string.IsNullOrEmpty(videoData.Id) || !ObjectId.TryParse(videoData.Id, out _)) return false; videoData.UpdatedAt = DateTime.UtcNow; var result = await _videosCollection.ReplaceOneAsync( v => v.Id == videoData.Id, videoData, new ReplaceOptions { IsUpsert = false }); return result.IsAcknowledged && result.ModifiedCount > 0; } /// /// Remove um vídeo pelo ID do MongoDB /// public async Task DeleteAsync(string id) { if (!ObjectId.TryParse(id, out _)) return false; var result = await _videosCollection.DeleteOneAsync(v => v.Id == id); return result.IsAcknowledged && result.DeletedCount > 0; } /// /// Remove um vídeo pelo ID do YouTube /// public async Task DeleteByVideoIdAsync(string videoId) { var result = await _videosCollection.DeleteOneAsync(v => v.VideoId == videoId); return result.IsAcknowledged && result.DeletedCount > 0; } /// /// Busca vídeos com base em um termo de pesquisa no título ou descrição /// public async Task> SearchAsync(string searchTerm) { if (string.IsNullOrWhiteSpace(searchTerm)) return await GetAllAsync(); var filter = Builders.Filter.Text(searchTerm); return await _videosCollection.Find(filter).ToListAsync(); } } }