using System;
using System.Collections.Generic;
using System.Diagnostics.Metrics;

namespace StellaOps.Feedser.Source.CertBund.Internal;

/// <summary>
/// Emits OpenTelemetry counters and histograms for the CERT-Bund connector.
/// </summary>
public sealed class CertBundDiagnostics : IDisposable
{
    private const string MeterName = "StellaOps.Feedser.Source.CertBund";
    private const string MeterVersion = "1.0.0";

    private readonly Meter _meter;
    private readonly Counter<long> _feedFetchAttempts;
    private readonly Counter<long> _feedFetchSuccess;
    private readonly Counter<long> _feedFetchFailures;
    private readonly Histogram<long> _feedItemCount;
    private readonly Histogram<long> _feedEnqueuedCount;
    private readonly Histogram<double> _feedCoverageDays;
    private readonly Counter<long> _detailFetchAttempts;
    private readonly Counter<long> _detailFetchSuccess;
    private readonly Counter<long> _detailFetchNotModified;
    private readonly Counter<long> _detailFetchFailures;
    private readonly Counter<long> _parseSuccess;
    private readonly Counter<long> _parseFailures;
    private readonly Histogram<long> _parseProductCount;
    private readonly Histogram<long> _parseCveCount;
    private readonly Counter<long> _mapSuccess;
    private readonly Counter<long> _mapFailures;
    private readonly Histogram<long> _mapPackageCount;
    private readonly Histogram<long> _mapAliasCount;

    public CertBundDiagnostics()
    {
        _meter = new Meter(MeterName, MeterVersion);
        _feedFetchAttempts = _meter.CreateCounter<long>(
            name: "certbund.feed.fetch.attempts",
            unit: "operations",
            description: "Number of RSS feed load attempts.");
        _feedFetchSuccess = _meter.CreateCounter<long>(
            name: "certbund.feed.fetch.success",
            unit: "operations",
            description: "Number of successful RSS feed loads.");
        _feedFetchFailures = _meter.CreateCounter<long>(
            name: "certbund.feed.fetch.failures",
            unit: "operations",
            description: "Number of RSS feed load failures.");
        _feedItemCount = _meter.CreateHistogram<long>(
            name: "certbund.feed.items.count",
            unit: "items",
            description: "Distribution of RSS item counts per fetch.");
        _feedEnqueuedCount = _meter.CreateHistogram<long>(
            name: "certbund.feed.enqueued.count",
            unit: "documents",
            description: "Distribution of advisory documents enqueued per fetch.");
        _feedCoverageDays = _meter.CreateHistogram<double>(
            name: "certbund.feed.coverage.days",
            unit: "days",
            description: "Coverage window in days between fetch time and the oldest published advisory in the feed.");
        _detailFetchAttempts = _meter.CreateCounter<long>(
            name: "certbund.detail.fetch.attempts",
            unit: "operations",
            description: "Number of detail fetch attempts.");
        _detailFetchSuccess = _meter.CreateCounter<long>(
            name: "certbund.detail.fetch.success",
            unit: "operations",
            description: "Number of detail fetches that persisted a document.");
        _detailFetchNotModified = _meter.CreateCounter<long>(
            name: "certbund.detail.fetch.not_modified",
            unit: "operations",
            description: "Number of detail fetches returning HTTP 304.");
        _detailFetchFailures = _meter.CreateCounter<long>(
            name: "certbund.detail.fetch.failures",
            unit: "operations",
            description: "Number of detail fetches that failed.");
        _parseSuccess = _meter.CreateCounter<long>(
            name: "certbund.parse.success",
            unit: "documents",
            description: "Number of documents parsed into CERT-Bund DTOs.");
        _parseFailures = _meter.CreateCounter<long>(
            name: "certbund.parse.failures",
            unit: "documents",
            description: "Number of documents that failed to parse.");
        _parseProductCount = _meter.CreateHistogram<long>(
            name: "certbund.parse.products.count",
            unit: "products",
            description: "Distribution of product entries captured per advisory.");
        _parseCveCount = _meter.CreateHistogram<long>(
            name: "certbund.parse.cve.count",
            unit: "aliases",
            description: "Distribution of CVE identifiers captured per advisory.");
        _mapSuccess = _meter.CreateCounter<long>(
            name: "certbund.map.success",
            unit: "advisories",
            description: "Number of canonical advisories emitted by the mapper.");
        _mapFailures = _meter.CreateCounter<long>(
            name: "certbund.map.failures",
            unit: "advisories",
            description: "Number of mapping failures.");
        _mapPackageCount = _meter.CreateHistogram<long>(
            name: "certbund.map.affected.count",
            unit: "packages",
            description: "Distribution of affected packages emitted per advisory.");
        _mapAliasCount = _meter.CreateHistogram<long>(
            name: "certbund.map.aliases.count",
            unit: "aliases",
            description: "Distribution of alias counts per advisory.");
    }

    public void FeedFetchAttempt() => _feedFetchAttempts.Add(1);

    public void FeedFetchSuccess(int itemCount)
    {
        _feedFetchSuccess.Add(1);
        if (itemCount >= 0)
        {
            _feedItemCount.Record(itemCount);
        }
    }

    public void FeedFetchFailure(string reason = "error")
        => _feedFetchFailures.Add(1, ReasonTag(reason));

    public void RecordFeedCoverage(double? coverageDays)
    {
        if (coverageDays is { } days && days >= 0)
        {
            _feedCoverageDays.Record(days);
        }
    }

    public void DetailFetchAttempt() => _detailFetchAttempts.Add(1);

    public void DetailFetchSuccess() => _detailFetchSuccess.Add(1);

    public void DetailFetchNotModified() => _detailFetchNotModified.Add(1);

    public void DetailFetchFailure(string reason = "error")
        => _detailFetchFailures.Add(1, ReasonTag(reason));

    public void DetailFetchEnqueued(int count)
    {
        if (count >= 0)
        {
            _feedEnqueuedCount.Record(count);
        }
    }

    public void ParseSuccess(int productCount, int cveCount)
    {
        _parseSuccess.Add(1);

        if (productCount >= 0)
        {
            _parseProductCount.Record(productCount);
        }

        if (cveCount >= 0)
        {
            _parseCveCount.Record(cveCount);
        }
    }

    public void ParseFailure(string reason = "error")
        => _parseFailures.Add(1, ReasonTag(reason));

    public void MapSuccess(int affectedPackages, int aliasCount)
    {
        _mapSuccess.Add(1);

        if (affectedPackages >= 0)
        {
            _mapPackageCount.Record(affectedPackages);
        }

        if (aliasCount >= 0)
        {
            _mapAliasCount.Record(aliasCount);
        }
    }

    public void MapFailure(string reason = "error")
        => _mapFailures.Add(1, ReasonTag(reason));

    private static KeyValuePair<string, object?> ReasonTag(string reason)
        => new("reason", string.IsNullOrWhiteSpace(reason) ? "unknown" : reason.ToLowerInvariant());

    public void Dispose() => _meter.Dispose();
}
