using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Configs;
using BenchmarkDotNet.Jobs;
using ZB.MOM.WW.CBDD.Bson;
using ZB.MOM.WW.CBDD.Core.Collections;
using ZB.MOM.WW.CBDD.Core.Storage;

namespace ZB.MOM.WW.CBDD.Tests.Benchmark;

[SimpleJob]
[InProcess]
[MemoryDiagnoser]
[GroupBenchmarksBy(BenchmarkLogicalGroupRule.ByCategory)]
[HtmlExporter]
[JsonExporterAttribute.Full]
public class CompactionBenchmarks
{
    /// <summary>
    /// Gets or sets the number of documents used per benchmark iteration.
    /// </summary>
    [Params(2_000)]
    public int DocumentCount { get; set; }

    private string _dbPath = string.Empty;
    private string _walPath = string.Empty;
    private StorageEngine _storage = null!;
    private BenchmarkTransactionHolder _transactionHolder = null!;
    private DocumentCollection<Person> _collection = null!;
    private List<ObjectId> _insertedIds = [];

    /// <summary>
    /// Prepares benchmark state and seed data for each iteration.
    /// </summary>
    [IterationSetup]
    public void Setup()
    {
        var id = Guid.NewGuid().ToString("N");
        _dbPath = Path.Combine(AppContext.BaseDirectory, $"bench_compaction_{id}.db");
        _walPath = Path.ChangeExtension(_dbPath, ".wal");

        _storage = new StorageEngine(_dbPath, PageFileConfig.Small);
        _transactionHolder = new BenchmarkTransactionHolder(_storage);
        _collection = new DocumentCollection<Person>(_storage, _transactionHolder, new PersonMapper());

        _insertedIds.Clear();
        for (var i = 0; i < DocumentCount; i++)
        {
            var person = CreatePerson(i);
            var insertedId = _collection.Insert(person);
            _insertedIds.Add(insertedId);
        }

        _transactionHolder.CommitAndReset();
        _storage.Checkpoint();

        for (var i = _insertedIds.Count - 1; i >= _insertedIds.Count / 3; i--)
        {
            _collection.Delete(_insertedIds[i]);
        }

        _transactionHolder.CommitAndReset();
        _storage.Checkpoint();
    }

    /// <summary>
    /// Cleans up benchmark resources and temporary files after each iteration.
    /// </summary>
    [IterationCleanup]
    public void Cleanup()
    {
        _transactionHolder?.Dispose();
        _storage?.Dispose();

        if (File.Exists(_dbPath)) File.Delete(_dbPath);
        if (File.Exists(_walPath)) File.Delete(_walPath);
    }

    /// <summary>
    /// Benchmarks reclaimed file bytes reported by offline compaction.
    /// </summary>
    /// <returns>The reclaimed file byte count.</returns>
    [Benchmark(Baseline = true)]
    [BenchmarkCategory("Compaction_Offline")]
    public long OfflineCompact_ReclaimedBytes()
    {
        var stats = _storage.Compact(new CompactionOptions
        {
            OnlineMode = false,
            EnableTailTruncation = true,
            DefragmentSlottedPages = true,
            NormalizeFreeList = true
        });

        return stats.ReclaimedFileBytes;
    }

    /// <summary>
    /// Benchmarks tail bytes truncated by offline compaction.
    /// </summary>
    /// <returns>The truncated tail byte count.</returns>
    [Benchmark]
    [BenchmarkCategory("Compaction_Offline")]
    public long OfflineCompact_TailBytesTruncated()
    {
        var stats = _storage.Compact(new CompactionOptions
        {
            OnlineMode = false,
            EnableTailTruncation = true,
            DefragmentSlottedPages = true,
            NormalizeFreeList = true
        });

        return stats.TailBytesTruncated;
    }

    private static Person CreatePerson(int i)
    {
        return new Person
        {
            Id = ObjectId.NewObjectId(),
            FirstName = $"First_{i}",
            LastName = $"Last_{i}",
            Age = i % 90,
            Bio = BuildPayload(i),
            CreatedAt = DateTime.UnixEpoch.AddSeconds(i),
            Balance = i * 1.5m,
            HomeAddress = new Address
            {
                Street = $"{i} Long Street",
                City = "Compaction City",
                ZipCode = "90000"
            }
        };
    }

    private static string BuildPayload(int seed)
    {
        var builder = new System.Text.StringBuilder(2500);
        for (var i = 0; i < 80; i++)
        {
            builder.Append("compact-");
            builder.Append(seed.ToString("D6"));
            builder.Append('-');
            builder.Append(i.ToString("D3"));
            builder.Append('|');
        }

        return builder.ToString();
    }
}
