using ZB.MOM.WW.CBDD.Bson;
using ZB.MOM.WW.CBDD.Core.CDC;
using ZB.MOM.WW.CBDD.Core.Storage;

namespace ZB.MOM.WW.CBDD.Core.Transactions;

/// <summary>
///     Represents a transaction with ACID properties.
///     Uses MVCC (Multi-Version Concurrency Control) for isolation.
/// </summary>
public sealed class Transaction : ITransaction
{
    private readonly List<InternalChangeEvent> _pendingChanges = new();
    private readonly StorageEngine _storage;
    private bool _disposed;

    /// <summary>
    ///     Initializes a new transaction.
    /// </summary>
    /// <param name="transactionId">The unique transaction identifier.</param>
    /// <param name="storage">The storage engine used by this transaction.</param>
    /// <param name="isolationLevel">The transaction isolation level.</param>
    public Transaction(ulong transactionId,
        StorageEngine storage,
        IsolationLevel isolationLevel = IsolationLevel.ReadCommitted)
    {
        TransactionId = transactionId;
        _storage = storage ?? throw new ArgumentNullException(nameof(storage));
        IsolationLevel = isolationLevel;
        StartTime = DateTime.UtcNow;
        State = TransactionState.Active;
    }

    /// <summary>
    ///     Gets the configured transaction isolation level.
    /// </summary>
    public IsolationLevel IsolationLevel { get; }

    /// <summary>
    ///     Gets the UTC start time of the transaction.
    /// </summary>
    public DateTime StartTime { get; }

    /// <summary>
    ///     Gets the unique transaction identifier.
    /// </summary>
    public ulong TransactionId { get; }

    /// <summary>
    ///     Gets the current transaction state.
    /// </summary>
    public TransactionState State { get; private set; }

    /// <summary>
    ///     Adds a write operation to the transaction's write set.
    ///     NOTE: Makes a defensive copy of the data to ensure memory safety.
    ///     This allocation is necessary because the caller may return the buffer to a pool.
    /// </summary>
    /// <param name="operation">The write operation to add.</param>
    public void AddWrite(WriteOperation operation)
    {
        if (State != TransactionState.Active)
            throw new InvalidOperationException($"Cannot add writes to transaction in state {State}");

        // Defensive copy: necessary to prevent use-after-return if caller uses pooled buffers
        byte[] ownedCopy = operation.NewValue.ToArray();
        // StorageEngine gestisce tutte le scritture transazionali
        _storage.WritePage(operation.PageId, TransactionId, ownedCopy);
    }

    /// <summary>
    ///     Prepares the transaction for commit (2PC first phase)
    /// </summary>
    public bool Prepare()
    {
        if (State != TransactionState.Active)
            return false;

        State = TransactionState.Preparing;

        // StorageEngine handles WAL writes
        return _storage.PrepareTransaction(TransactionId);
    }

    /// <summary>
    ///     Commits the transaction.
    ///     Writes to WAL for durability and moves data to committed buffer.
    ///     Pages remain in memory until CheckpointManager writes them to disk.
    /// </summary>
    public void Commit()
    {
        if (State != TransactionState.Preparing && State != TransactionState.Active)
            throw new InvalidOperationException($"Cannot commit transaction in state {State}");

        // StorageEngine handles WAL writes and buffer management
        _storage.CommitTransaction(TransactionId);

        State = TransactionState.Committed;

        // Publish CDC events after successful commit
        if (_pendingChanges.Count > 0 && _storage.Cdc != null)
            foreach (var change in _pendingChanges)
                _storage.Cdc.Publish(change);
    }

    /// <summary>
    ///     Asynchronously commits the transaction.
    /// </summary>
    /// <param name="ct">A cancellation token.</param>
    public async Task CommitAsync(CancellationToken ct = default)
    {
        if (State != TransactionState.Preparing && State != TransactionState.Active)
            throw new InvalidOperationException($"Cannot commit transaction in state {State}");

        // StorageEngine handles WAL writes and buffer management
        await _storage.CommitTransactionAsync(TransactionId, ct);

        State = TransactionState.Committed;

        // Publish CDC events after successful commit
        if (_pendingChanges.Count > 0 && _storage.Cdc != null)
            foreach (var change in _pendingChanges)
                _storage.Cdc.Publish(change);
    }

    /// <summary>
    ///     Rolls back the transaction (discards all writes)
    /// </summary>
    public event Action? OnRollback;

    /// <summary>
    ///     Rolls back the transaction and discards pending writes.
    /// </summary>
    public void Rollback()
    {
        if (State == TransactionState.Committed)
            throw new InvalidOperationException("Cannot rollback committed transaction");

        _pendingChanges.Clear();
        _storage.RollbackTransaction(TransactionId);
        State = TransactionState.Aborted;

        OnRollback?.Invoke();
    }

    /// <summary>
    ///     Releases transaction resources and rolls back if still active.
    /// </summary>
    public void Dispose()
    {
        if (_disposed)
            return;

        if (State == TransactionState.Active || State == TransactionState.Preparing)
            // Auto-rollback if not committed
            Rollback();

        _disposed = true;
        GC.SuppressFinalize(this);
    }

    /// <summary>
    ///     Adds a pending CDC change to be published after commit.
    /// </summary>
    /// <param name="change">The change event to buffer.</param>
    internal void AddChange(InternalChangeEvent change)
    {
        _pendingChanges.Add(change);
    }

    /// <summary>
    ///     Marks the transaction as committed without writing to PageFile.
    ///     Used by TransactionManager with lazy checkpointing.
    /// </summary>
    internal void MarkCommitted()
    {
        if (State != TransactionState.Preparing && State != TransactionState.Active)
            throw new InvalidOperationException($"Cannot commit transaction in state {State}");

        // StorageEngine marks transaction as committed and moves to committed buffer
        _storage.MarkTransactionCommitted(TransactionId);

        State = TransactionState.Committed;
    }
}

/// <summary>
///     Represents a write operation in a transaction.
///     Optimized to avoid allocations by using ReadOnlyMemory instead of byte[].
/// </summary>
public struct WriteOperation
{
    /// <summary>
    ///     Gets or sets the identifier of the affected document.
    /// </summary>
    public ObjectId DocumentId { get; set; }

    /// <summary>
    ///     Gets or sets the new serialized value.
    /// </summary>
    public ReadOnlyMemory<byte> NewValue { get; set; }

    /// <summary>
    ///     Gets or sets the target page identifier.
    /// </summary>
    public uint PageId { get; set; }

    /// <summary>
    ///     Gets or sets the operation type.
    /// </summary>
    public OperationType Type { get; set; }

    /// <summary>
    ///     Initializes a new write operation.
    /// </summary>
    /// <param name="documentId">The identifier of the affected document.</param>
    /// <param name="newValue">The new serialized value.</param>
    /// <param name="pageId">The target page identifier.</param>
    /// <param name="type">The operation type.</param>
    public WriteOperation(ObjectId documentId, ReadOnlyMemory<byte> newValue, uint pageId, OperationType type)
    {
        DocumentId = documentId;
        NewValue = newValue;
        PageId = pageId;
        Type = type;
    }

    // Backward compatibility constructor
    /// <summary>
    ///     Initializes a new write operation from a byte array payload.
    /// </summary>
    /// <param name="documentId">The identifier of the affected document.</param>
    /// <param name="newValue">The new serialized value.</param>
    /// <param name="pageId">The target page identifier.</param>
    /// <param name="type">The operation type.</param>
    public WriteOperation(ObjectId documentId, byte[] newValue, uint pageId, OperationType type)
    {
        DocumentId = documentId;
        NewValue = newValue;
        PageId = pageId;
        Type = type;
    }
}

/// <summary>
///     Type of write operation
/// </summary>
public enum OperationType : byte
{
    Insert = 1,
    Update = 2,
    Delete = 3,
    AllocatePage = 4
}