using Microsoft.Extensions.Logging;
using StellaOps.Scanner.Core.Contracts;
using StellaOps.Scanner.Core.Utility;

namespace StellaOps.Scanner.Core.Observability;

public static class ScannerLogExtensions
{
    private sealed class NoopScope : IDisposable
    {
        public static NoopScope Instance { get; } = new();

        public void Dispose()
        {
        }
    }

    private sealed class CompositeScope : IDisposable
    {
        private readonly IDisposable first;
        private readonly IDisposable second;
        private bool disposed;

        public CompositeScope(IDisposable first, IDisposable second)
        {
            this.first = first;
            this.second = second;
        }

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

            disposed = true;
            second.Dispose();
            first.Dispose();
        }
    }

    public static IDisposable BeginScanScope(this ILogger? logger, ScanJob job, string? stage = null, string? component = null)
    {
        var correlation = ScannerCorrelationContext.Create(job.Id, stage, component);
        var logScope = logger is null
            ? NoopScope.Instance
            : logger.BeginScope(CreateScopeState(
                job.Id,
                job.CorrelationId,
                stage,
                component,
                job.TenantId,
                job.ImageDigest)) ?? NoopScope.Instance;

        var correlationScope = ScannerCorrelationContextAccessor.Push(correlation);
        return new CompositeScope(logScope, correlationScope);
    }

    public static IDisposable BeginProgressScope(this ILogger? logger, ScanProgressEvent progress, string? component = null)
    {
        var correlationId = ScannerIdentifiers.CreateCorrelationId(progress.JobId, progress.Stage.ToString(), component);
        var correlation = new ScannerCorrelationContext(progress.JobId, correlationId, progress.Stage.ToString(), component);

        var logScope = logger is null
            ? NoopScope.Instance
            : logger.BeginScope(new Dictionary<string, object?>(6, StringComparer.Ordinal)
            {
                ["scanId"] = progress.JobId.ToString(),
                ["stage"] = progress.Stage.ToString(),
                ["sequence"] = progress.Sequence,
                ["kind"] = progress.Kind.ToString(),
                ["correlationId"] = correlationId,
                ["component"] = component ?? string.Empty
            }) ?? NoopScope.Instance;

        var correlationScope = ScannerCorrelationContextAccessor.Push(correlation);
        return new CompositeScope(logScope, correlationScope);
    }

    public static IDisposable BeginCorrelationScope(this ILogger? logger, ScannerCorrelationContext context)
    {
        var scope = logger is null
            ? NoopScope.Instance
            : logger.BeginScope(CreateScopeState(context.JobId, context.CorrelationId, context.Stage, context.Component, null, null)) ?? NoopScope.Instance;

        var correlationScope = ScannerCorrelationContextAccessor.Push(context);
        return new CompositeScope(scope, correlationScope);
    }

    private static Dictionary<string, object?> CreateScopeState(
        ScanJobId jobId,
        string correlationId,
        string? stage,
        string? component,
        string? tenantId,
        string? imageDigest)
    {
        var state = new Dictionary<string, object?>(6, StringComparer.Ordinal)
        {
            ["scanId"] = jobId.ToString(),
            ["correlationId"] = correlationId,
            ["stage"] = stage ?? string.Empty,
            ["component"] = component ?? string.Empty,
            ["tenantId"] = tenantId ?? string.Empty
        };

        if (!string.IsNullOrEmpty(imageDigest))
        {
            state["imageDigest"] = imageDigest;
        }

        return state;
    }
}
