using System.Diagnostics;
using System.Diagnostics.Metrics;
using System.Security.Cryptography;
using System.Text;

namespace Tiku.Infrastructure.Observability;

public sealed record DatabaseRequestSnapshot(
    int CommandCount,
    double TotalDurationMilliseconds,
    double SlowestDurationMilliseconds,
    string? SlowestCommandFingerprint);

public static class DatabasePerformanceTelemetry
{
    public const string MeterName = "Tiku.Database";

    internal static readonly Meter Meter = new(MeterName, "1.0.0");

    internal static readonly Counter<long> CommandCounter = Meter.CreateCounter<long>(
        "tiku.database.commands",
        description: "Number of database commands executed.");

    internal static readonly Histogram<double> CommandDuration = Meter.CreateHistogram<double>(
        "tiku.database.command.duration",
        "ms",
        "Database command execution duration.");

    internal static readonly Histogram<long> RequestCommandCount = Meter.CreateHistogram<long>(
        "tiku.database.request.commands",
        description: "Database commands executed during one HTTP request.");

    internal static readonly Histogram<double> RequestCommandDuration = Meter.CreateHistogram<double>(
        "tiku.database.request.duration",
        "ms",
        "Aggregate database command duration during one HTTP request.");

    public static string Fingerprint(string commandText)
    {
        var hash = SHA256.HashData(Encoding.UTF8.GetBytes(commandText));
        return Convert.ToHexString(hash.AsSpan(0, 8)).ToLowerInvariant();
    }
}

public static class DatabaseRequestMetrics
{
    private static readonly AsyncLocal<RequestState?> CurrentState = new();

    public static IDisposable Begin()
    {
        var previous = CurrentState.Value;
        var current = new RequestState();
        CurrentState.Value = current;
        return new Scope(previous, current);
    }

    internal static void Record(TimeSpan duration, string fingerprint)
    {
        CurrentState.Value?.Record(duration, fingerprint);
    }

    private sealed class RequestState
    {
        private readonly Stopwatch stopwatch = Stopwatch.StartNew();
        private int commandCount;
        private string? slowestFingerprint;
        private long slowestTicks;
        private long totalTicks;

        public void Record(TimeSpan duration, string fingerprint)
        {
            commandCount++;
            totalTicks += duration.Ticks;
            if (duration.Ticks > slowestTicks)
            {
                slowestTicks = duration.Ticks;
                slowestFingerprint = fingerprint;
            }
        }

        public DatabaseRequestSnapshot Complete()
        {
            stopwatch.Stop();
            return new DatabaseRequestSnapshot(
                commandCount,
                TimeSpan.FromTicks(totalTicks).TotalMilliseconds,
                TimeSpan.FromTicks(slowestTicks).TotalMilliseconds,
                slowestFingerprint);
        }
    }

    private sealed class Scope(RequestState? previous, RequestState current) : IDisposable
    {
        private bool disposed;

        public void Dispose()
        {
            if (disposed) return;

            disposed = true;
            var snapshot = current.Complete();
            DatabasePerformanceTelemetry.RequestCommandCount.Record(snapshot.CommandCount);
            DatabasePerformanceTelemetry.RequestCommandDuration.Record(snapshot.TotalDurationMilliseconds);
            CurrentState.Value = previous;
        }
    }
}