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<VideoData> _videosCollection;

        public VideoRepository(IOptions<MongoDbSettings> mongoDbSettings)
        {
            var client = new MongoClient(mongoDbSettings.Value.ConnectionString);
            var database = client.GetDatabase(mongoDbSettings.Value.DatabaseName);
            _videosCollection = database.GetCollection<VideoData>(mongoDbSettings.Value.VideosCollectionName);

            // Cria índice composto para videoId e userId
            var indexKeysDefinition = Builders<VideoData>.IndexKeys
                .Ascending(v => v.VideoId)
                .Ascending(v => v.UserId);
            _videosCollection.Indexes.CreateOne(new CreateIndexModel<VideoData>(indexKeysDefinition, new CreateIndexOptions { Unique = true }));

            // Cria índice para buscas por texto
            var textIndexDefinition = Builders<VideoData>.IndexKeys
                .Text(v => v.Title)
                .Text(v => v.Description);
            _videosCollection.Indexes.CreateOne(new CreateIndexModel<VideoData>(textIndexDefinition));

            // Cria índice para buscas por channelId
            var channelIndexDefinition = Builders<VideoData>.IndexKeys
                .Ascending(v => v.ChannelId);
            _videosCollection.Indexes.CreateOne(new CreateIndexModel<VideoData>(channelIndexDefinition));

            // Cria índice para userId + channelId
            var userChannelIndexDefinition = Builders<VideoData>.IndexKeys
                .Ascending(v => v.UserId)
                .Ascending(v => v.ChannelId);
            _videosCollection.Indexes.CreateOne(new CreateIndexModel<VideoData>(userChannelIndexDefinition));
        }

        /// <summary>
        /// Obtém todos os vídeos
        /// </summary>
        public async Task<IEnumerable<VideoData>> GetAllAsync()
        {
            return await _videosCollection.Find(v => true).ToListAsync();
        }

        /// <summary>
        /// Obtém um vídeo pelo ID do MongoDB
        /// </summary>
        public async Task<VideoData> GetByIdAsync(string id)
        {
            if (!ObjectId.TryParse(id, out _))
                return null;

            return await _videosCollection.Find(v => v.Id == id).FirstOrDefaultAsync();
        }

        /// <summary>
        /// Obtém um vídeo pelo ID do YouTube
        /// </summary>
        public async Task<VideoData> GetByVideoIdAsync(string videoId)
        {
            return await _videosCollection.Find(v => v.VideoId == videoId).FirstOrDefaultAsync();
        }

        /// <summary>
        /// Obtém os vídeos pelo ID do usuário
        /// </summary>
        public async Task<IList<VideoData>> GetByUserIdAsync(string userId)
        {
            return await _videosCollection.Find(v => v.UserId == userId).ToListAsync();
        }

        /// <summary>
        /// Obtém os vídeos pelo ID do canal
        /// </summary>
        public async Task<IList<VideoData>> GetByChannelIdAsync(string channelId)
        {
            return await _videosCollection.Find(v => v.ChannelId == channelId).ToListAsync();
        }

        /// <summary>
        /// Obtém os vídeos pelo ID do usuário e ID do canal
        /// </summary>
        public async Task<IList<VideoData>> GetByUserIdAndChannelIdAsync(string userId, string channelId)
        {
            return await _videosCollection.Find(v => v.UserId == userId && v.ChannelId == channelId).ToListAsync();
        }

        /// <summary>
        /// Obtém um vídeo pelo ID do usuário e ID do vídeo
        /// </summary>
        public async Task<VideoData> GetByUserIdAndVideoIdAsync(string userId, string videoId)
        {
            return await _videosCollection.Find(v => v.UserId == userId && v.VideoId == videoId).FirstOrDefaultAsync();
        }

        /// <summary>
        /// Adiciona um novo vídeo
        /// </summary>
        public async Task<VideoData> 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;
        }

        /// <summary>
        /// Adiciona vários vídeos de uma vez
        /// </summary>
        public async Task<IEnumerable<VideoData>> AddManyAsync(IEnumerable<VideoData> videos)
        {
            if (videos == null || !videos.Any())
                return new List<VideoData>();

            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<VideoData>.Filter.And(
                Builders<VideoData>.Filter.In(v => v.UserId, userIds),
                Builders<VideoData>.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;
        }

        /// <summary>
        /// Atualiza um vídeo existente
        /// </summary>
        public async Task<bool> 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;
        }

        /// <summary>
        /// Remove um vídeo pelo ID do MongoDB
        /// </summary>
        public async Task<bool> 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;
        }

        /// <summary>
        /// Remove um vídeo pelo ID do YouTube
        /// </summary>
        public async Task<bool> DeleteByVideoIdAsync(string videoId)
        {
            var result = await _videosCollection.DeleteOneAsync(v => v.VideoId == videoId);
            return result.IsAcknowledged && result.DeletedCount > 0;
        }

        /// <summary>
        /// Busca vídeos com base em um termo de pesquisa no título ou descrição
        /// </summary>
        public async Task<IEnumerable<VideoData>> SearchAsync(string searchTerm)
        {
            if (string.IsNullOrWhiteSpace(searchTerm))
                return await GetAllAsync();

            var filter = Builders<VideoData>.Filter.Text(searchTerm);
            return await _videosCollection.Find(filter).ToListAsync();
        }
    }
}