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.Compression;
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 MixedWorkloadBenchmarks
{
    [Params(false, true)]
    public bool PeriodicCompaction { get; set; }

    [Params(800)]
    public int Operations { 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 readonly List<ObjectId> _activeIds = [];
    private int _nextValueSeed;

    [IterationSetup]
    public void Setup()
    {
        var id = Guid.NewGuid().ToString("N");
        _dbPath = Path.Combine(AppContext.BaseDirectory, $"bench_mixed_{id}.db");
        _walPath = Path.ChangeExtension(_dbPath, ".wal");

        var options = new CompressionOptions
        {
            EnableCompression = true,
            MinSizeBytes = 256,
            MinSavingsPercent = 0,
            Codec = CompressionCodec.Brotli
        };

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

        _activeIds.Clear();
        _nextValueSeed = 0;

        for (var i = 0; i < 300; i++)
        {
            var idValue = _collection.Insert(CreatePerson(_nextValueSeed++));
            _activeIds.Add(idValue);
        }

        _transactionHolder.CommitAndReset();
    }

    [IterationCleanup]
    public void Cleanup()
    {
        _transactionHolder?.Dispose();
        _storage?.Dispose();

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

    [Benchmark(Baseline = true)]
    [BenchmarkCategory("MixedWorkload")]
    public int InsertUpdateDeleteMix()
    {
        var random = new Random(12345);

        for (var i = 1; i <= Operations; i++)
        {
            var mode = i % 5;
            if (mode is 0 or 1)
            {
                var id = _collection.Insert(CreatePerson(_nextValueSeed++));
                _activeIds.Add(id);
            }
            else if (mode is 2 or 3)
            {
                if (_activeIds.Count > 0)
                {
                    var idx = random.Next(_activeIds.Count);
                    var id = _activeIds[idx];
                    var current = _collection.FindById(id);
                    if (current != null)
                    {
                        current.Age += 1;
                        current.Bio = BuildPayload(_nextValueSeed++);
                        _collection.Update(current);
                    }
                }
            }
            else
            {
                if (_activeIds.Count > 100)
                {
                    var idx = random.Next(_activeIds.Count);
                    var id = _activeIds[idx];
                    _collection.Delete(id);
                    _activeIds.RemoveAt(idx);
                }
            }

            if (i % 50 == 0)
            {
                _transactionHolder.CommitAndReset();
            }

            if (PeriodicCompaction && i % 200 == 0)
            {
                _storage.RunOnlineCompactionPass(new CompactionOptions
                {
                    OnlineMode = true,
                    OnlineBatchPageLimit = 8,
                    OnlineBatchDelay = TimeSpan.FromMilliseconds(1),
                    MaxOnlineDuration = TimeSpan.FromMilliseconds(120),
                    EnableTailTruncation = true
                });
            }
        }

        _transactionHolder.CommitAndReset();
        return _collection.Count();
    }

    private static Person CreatePerson(int seed)
    {
        return new Person
        {
            Id = ObjectId.NewObjectId(),
            FirstName = $"First_{seed}",
            LastName = $"Last_{seed}",
            Age = 18 + (seed % 60),
            Bio = BuildPayload(seed),
            CreatedAt = DateTime.UnixEpoch.AddSeconds(seed),
            Balance = seed,
            HomeAddress = new Address
            {
                Street = $"{seed} Mixed Ave",
                City = "Workload City",
                ZipCode = "10101"
            }
        };
    }

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

        return builder.ToString();
    }
}
