// <copyright file="BulkOperationTransaction.cs" company="PlaceholderCompany">
// Copyright (c) PlaceholderCompany. All rights reserved.
// </copyright>

namespace Jellyfin.Database.Providers.Postgres;

using System;
using System.Threading;
using System.Threading.Tasks;
using Jellyfin.Database.Implementations;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Storage;

/// <summary>
/// Helper class for managing transactions during bulk operations to reduce dead tuple generation.
/// </summary>
public sealed class BulkOperationTransaction : IAsyncDisposable, IDisposable
{
    private readonly JellyfinDbContext _context;
    private IDbContextTransaction? _transaction;
    private bool _disposed;

    /// <summary>
    /// Initializes a new instance of the <see cref="BulkOperationTransaction"/> class.
    /// </summary>
    /// <param name="context">The database context.</param>
    public BulkOperationTransaction(JellyfinDbContext context)
    {
        _context = context ?? throw new ArgumentNullException(nameof(context));
    }

    /// <summary>
    /// Gets a value indicating whether the transaction has been committed.
    /// </summary>
    public bool IsCommitted { get; private set; }

    /// <summary>
    /// Begins a new transaction with isolation level optimized for bulk operations.
    /// </summary>
    /// <param name="cancellationToken">Cancellation token.</param>
    /// <returns>A task representing the asynchronous operation.</returns>
    public async Task BeginAsync(CancellationToken cancellationToken = default)
    {
        if (_transaction is not null)
        {
            throw new InvalidOperationException("Transaction is already active. Call Dispose or RollbackAsync first.");
        }

        // Use READ COMMITTED isolation for bulk operations
        // This provides a good balance between consistency and concurrency
        _transaction = await _context.Database.BeginTransactionAsync(
            System.Data.IsolationLevel.ReadCommitted,
            cancellationToken).ConfigureAwait(false);
    }

    /// <summary>
    /// Commits the transaction.
    /// </summary>
    /// <param name="cancellationToken">Cancellation token.</param>
    /// <returns>A task representing the asynchronous operation.</returns>
    public async Task CommitAsync(CancellationToken cancellationToken = default)
    {
        if (_transaction is null)
        {
            throw new InvalidOperationException("No active transaction. Call BeginAsync first.");
        }

        try
        {
            await _transaction.CommitAsync(cancellationToken).ConfigureAwait(false);
            IsCommitted = true;
        }
        finally
        {
            _transaction.Dispose();
            _transaction = null;
        }
    }

    /// <summary>
    /// Rolls back the transaction.
    /// </summary>
    /// <param name="cancellationToken">Cancellation token.</param>
    /// <returns>A task representing the asynchronous operation.</returns>
    public async Task RollbackAsync(CancellationToken cancellationToken = default)
    {
        if (_transaction is null)
        {
            return;
        }

        try
        {
            await _transaction.RollbackAsync(cancellationToken).ConfigureAwait(false);
        }
        finally
        {
            _transaction.Dispose();
            _transaction = null;
        }
    }

    /// <summary>
    /// Disposes the transaction if not already committed.
    /// </summary>
    public void Dispose()
    {
        if (_disposed)
        {
            return;
        }

        _transaction?.Dispose();
        _transaction = null;
        _disposed = true;
    }

    /// <summary>
    /// Disposes the transaction asynchronously if not already committed.
    /// </summary>
    /// <returns>A task representing the asynchronous operation.</returns>
    public async ValueTask DisposeAsync()
    {
        if (_disposed)
        {
            return;
        }

        if (_transaction is not null)
        {
            await _transaction.DisposeAsync().ConfigureAwait(false);
        }

        _transaction = null;
        _disposed = true;
    }
}

