using Microsoft.Extensions.Options;
using MongoDB.Bson;
using MongoDB.Driver;
using Postall.Domain.Contracts.Repositories;
using Postall.Domain.Dtos;
using Postall.Domain.Entities;
using Postall.Infra.MongoDB.Settings;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Channels;
using System.Threading.Tasks;
using static System.Runtime.InteropServices.JavaScript.JSType;

namespace Postall.Infra.MongoDB.Repositories
{
    public class ChannelRepository : IChannelRepository
    {
        private readonly IMongoCollection<ChannelData> _channelsCollection;

        public ChannelRepository(IOptions<MongoDbSettings> mongoDbSettings)
        {
            var client = new MongoClient(mongoDbSettings.Value.ConnectionString);
            var database = client.GetDatabase(mongoDbSettings.Value.DatabaseName);
            _channelsCollection = database.GetCollection<ChannelData>(mongoDbSettings.Value.ChannelsCollectionName);

            var indexKeysDefinition = Builders<ChannelData>.IndexKeys.Ascending(c => c.ChannelId).Ascending(c => c.UserId);
            _channelsCollection.Indexes.CreateOne(new CreateIndexModel<ChannelData>(indexKeysDefinition, new CreateIndexOptions { Unique = true }));

            var textIndexDefinition = Builders<ChannelData>.IndexKeys
                .Text(c => c.Title)
                .Text(c => c.Description);
            _channelsCollection.Indexes.CreateOne(new CreateIndexModel<ChannelData>(textIndexDefinition));
        }

        /// <summary>
        /// Obtém todos os canais
        /// </summary>
        public async Task<IEnumerable<ChannelData>> GetAllAsync()
        {
            return await _channelsCollection.Find(c => true).ToListAsync();
        }

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

            return await _channelsCollection.Find(c => c.Id == id).FirstOrDefaultAsync();
        }

        public async Task<IList<ChannelData>> GetByUserIdAsync(string userId)
        {
            return await _channelsCollection.Find(c => c.UserId == userId).ToListAsync();
        }

        /// <summary>
        /// Obtém os canais pelo ID do usuario e o channelId
        /// </summary>
        public async Task<ChannelData> GetByUserIdAndChannelIdAsync(string userId, string channelId)
        {
            return await _channelsCollection.Find(c => c.UserId == userId && c.ChannelId == channelId).FirstOrDefaultAsync();
        }

        /// <summary>
        /// Obtém um canal pelo ID do YouTube
        /// </summary>
        public async Task<ChannelData> GetByYoutubeIdAsync(string youtubeId)
        {
            return await _channelsCollection.Find(c => c.YoutubeId == youtubeId).FirstOrDefaultAsync();
        }

        /// <summary>
        /// Adiciona um novo canal
        /// </summary>
        public async Task<ChannelData> AddAsync(ChannelData ChannelData)
        {
            var existingChannel = await GetByUserIdAndChannelIdAsync(ChannelData.UserId, ChannelData.ChannelId);
            if (existingChannel != null)
                return null;

            if (string.IsNullOrEmpty(ChannelData.Id) || !ObjectId.TryParse(ChannelData.Id, out _))
            {
                ChannelData.Id = ObjectId.GenerateNewId().ToString();
            }

            try 
            { 
                await _channelsCollection.InsertOneAsync(ChannelData); 
            }
            catch (MongoWriteException ex)
            {
                if (ex.WriteError.Category == ServerErrorCategory.DuplicateKey)
                {
                    return await GetByYoutubeIdAsync(ChannelData.YoutubeId);
                }
                else
                {
                    throw;
                }
            }
            return ChannelData;
        }

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

            var channelsList = channels.ToList();

            // Gera novos IDs para o MongoDB se necessário
            foreach (var ChannelData in channelsList)
            {
                if (string.IsNullOrEmpty(ChannelData.Id) || !ObjectId.TryParse(ChannelData.Id, out _))
                {
                    ChannelData.Id = ObjectId.GenerateNewId().ToString();
                }
            }

            // Verifica canais existentes pelo ID do YouTube
            var youtubeIds = channelsList.Select(c => c.YoutubeId).ToList();
            var existingChannels = await _channelsCollection
                .Find(c => youtubeIds.Contains(c.YoutubeId))
                .ToListAsync();

            var existingYoutubeIds = existingChannels.Select(c => c.YoutubeId).ToHashSet();
            var newChannels = channelsList.Where(c => !existingYoutubeIds.Contains(c.YoutubeId)).ToList();

            if (newChannels.Any())
                await _channelsCollection.InsertManyAsync(newChannels);

            return newChannels;
        }

        /// <summary>
        /// Atualiza um canal existente
        /// </summary>
        public async Task<bool> UpdateAsync(ChannelData ChannelData)
        {
            if (string.IsNullOrEmpty(ChannelData.Id) || !ObjectId.TryParse(ChannelData.Id, out _))
                return false;

            var result = await _channelsCollection.ReplaceOneAsync(
                c => c.Id == ChannelData.Id,
                ChannelData,
                new ReplaceOptions { IsUpsert = false });

            return result.IsAcknowledged && result.ModifiedCount > 0;
        }

        /// <summary>
        /// Remove um canal pelo ID do MongoDB
        /// </summary>
        public async Task<bool> DeleteAsync(string id)
        {
            if (!ObjectId.TryParse(id, out _))
                return false;

            var result = await _channelsCollection.DeleteOneAsync(c => c.Id == id);
            return result.IsAcknowledged && result.DeletedCount > 0;
        }

        /// <summary>
        /// Remove um canal pelo ID do YouTube
        /// </summary>
        public async Task<bool> DeleteByYoutubeIdAsync(string youtubeId)
        {
            var result = await _channelsCollection.DeleteOneAsync(c => c.YoutubeId == youtubeId);
            return result.IsAcknowledged && result.DeletedCount > 0;
        }

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

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

        /// <summary>
        /// Obtém canais selecionados (IsSelected = true)
        /// </summary>
        public async Task<IEnumerable<ChannelData>> GetSelectedAsync()
        {
            return await _channelsCollection.Find(c => c.IsSelected).ToListAsync();
        }

        /// <summary>
        /// Marca ou desmarca um canal como selecionado
        /// </summary>
        public async Task<bool> SetSelectedStatusAsync(string id, bool isSelected)
        {
            if (!ObjectId.TryParse(id, out _))
                return false;

            var update = Builders<ChannelData>.Update.Set(c => c.IsSelected, isSelected);
            var result = await _channelsCollection.UpdateOneAsync(c => c.Id == id, update);

            return result.IsAcknowledged && result.ModifiedCount > 0;
        }

        /// <summary>
        /// Converte ChannelResponse para ChannelData
        /// </summary>
        public ChannelData ConvertFromResponse(ChannelResponse channelResponse)
        {
            if (channelResponse == null)
                return null;

            return new ChannelData
            {
                YoutubeId = channelResponse.Id,
                Title = channelResponse.Title,
                Description = channelResponse.Description,
                ThumbnailUrl = channelResponse.ThumbnailUrl,
                PublishedAt = channelResponse.PublishedAt,
                SubscriberCount = channelResponse.SubscriberCount,
                VideoCount = channelResponse.VideoCount,
                IsSelected = channelResponse.IsSelected
            };
        }

        /// <summary>
        /// Converte ChannelData para ChannelResponse
        /// </summary>
        public ChannelResponse ConvertToResponse(ChannelData ChannelData)
        {
            if (ChannelData == null)
                return null;

            return new ChannelResponse
            {
                Id = ChannelData.YoutubeId,
                Title = ChannelData.Title,
                Description = ChannelData.Description,
                ThumbnailUrl = ChannelData.ThumbnailUrl,
                PublishedAt = ChannelData.PublishedAt,
                SubscriberCount = ChannelData.SubscriberCount,
                VideoCount = ChannelData.VideoCount,
                IsSelected = ChannelData.IsSelected
            };
        }
    }
}
