using System.Diagnostics;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
using MongoDB.Bson;
using MongoDB.Driver;
using MongoDB.Driver.Core.Events;

namespace ExpenseTracker.Persistence.MongoDb;

public class MongoDbContext : IDisposable
{
    private IMongoDatabase Database { get; set; }
    private MongoClient Client { get; set; }
    public IClientSessionHandle Session { get; private set; }

    private readonly List<Func<Task>> _commands;

    public MongoDbContext(IConfiguration configuration, ILogger<MongoDbContext> logger)
    {
        var settings = new MongoClientSettings();

        settings.ClusterConfigurator = cb =>
        {
            cb.Subscribe<CommandStartedEvent>(e =>
                {
                    logger.LogDebug(
                        "{@DateUtc} {@TimeUtc} {@TraceId} {@SpanId} Starting performing MongoDB command {@CommandName}.\nQuery: {@Query}.",
                        DateTime.UtcNow.ToString("yyyy-MM-dd"),
                        DateTime.UtcNow.ToString("HH:mm:ss.FFF"),
                        Activity.Current?.TraceId.ToString(),
                        Activity.Current?.SpanId.ToString(),
                        e.CommandName,
                        e.Command.ToString());
                });
            cb.Subscribe<CommandFailedEvent>(e =>
                {
                    logger.LogCritical(
                        "{@DateUtc} {@TimeUtc} {@TraceId} {@SpanId} MongoDB command {@CommandName} failed with exception {@Exception}.",
                        DateTime.UtcNow.ToString("yyyy-MM-dd"),
                        DateTime.UtcNow.ToString("HH:mm:ss.FFF"),
                        Activity.Current?.TraceId.ToString(),
                        Activity.Current?.SpanId.ToString(),
                        e.CommandName,
                        e.Failure);
                });
            cb.Subscribe<CommandSucceededEvent>(e =>
                {
                    logger.LogDebug(
                        "{@DateUtc} {@TimeUtc} {@TraceId} {@SpanId} Finished performing MongoDB command {@CommandName} in {@DurationInSeconds} seconds.",
                        DateTime.UtcNow.ToString("yyyy-MM-dd"),
                        DateTime.UtcNow.ToString("HH:mm:ss.FFF"),
                        Activity.Current?.TraceId.ToString(),
                        Activity.Current?.SpanId.ToString(),
                        e.CommandName,
                        e.Duration.TotalSeconds);
                });
        };

        settings.Server = new MongoServerAddress(configuration["Database:ConnectionString"].Split(':')[0]);

        Client = new MongoClient(settings);
        Database = Client.GetDatabase(configuration["Database:DomainPartitionName"]);

        _commands = new List<Func<Task>>();
    }

    public async Task<int> SaveAsync(CancellationToken cancellationToken)
    {
        // Console.WriteLine($"\n\n\n{_commands.Count}\n\n\n");
        // if (_commands.Count == 1)
        // {
        //     var task = _commands.First();
        //     await task();
        //
        //     return 1;
        // }

        using (Session = await Client.StartSessionAsync())
        {
            Session.StartTransaction();

            try
            {
                var commandTasks = _commands.Select(c => c());

                await Task.WhenAll(commandTasks);
            }
            catch (Exception)
            {
                await Session.AbortTransactionAsync(cancellationToken);
                throw;
            }

            await Session.CommitTransactionAsync(cancellationToken);
        }

        return _commands.Count;
    }

    public IMongoCollection<T> GetCollection<T>(string name)
    {
        return Database.GetCollection<T>(name);
    }

    public void AddCommand(Func<Task> command)
    {
        _commands.Add(command);
    }

    public void Dispose()
    {
        Session?.Dispose();
        GC.SuppressFinalize(this);
    }
}