using System.Diagnostics;
using System.Diagnostics.Metrics;

namespace StellaOps.Feedser.Core.Jobs;

public sealed class JobDiagnostics : IDisposable
{
    public const string ActivitySourceName = "StellaOps.Feedser.Jobs";
    public const string MeterName = "StellaOps.Feedser.Jobs";
    public const string TriggerActivityName = "feedser.job.trigger";
    public const string ExecuteActivityName = "feedser.job.execute";
    public const string SchedulerActivityName = "feedser.scheduler.evaluate";

    private readonly Counter<long> _triggersAccepted;
    private readonly Counter<long> _triggersRejected;
    private readonly Counter<long> _runsCompleted;
    private readonly UpDownCounter<long> _runsActive;
    private readonly Histogram<double> _runDurationSeconds;
    private readonly Histogram<double> _schedulerSkewMilliseconds;

    public JobDiagnostics()
    {
        ActivitySource = new ActivitySource(ActivitySourceName);
        Meter = new Meter(MeterName);

        _triggersAccepted = Meter.CreateCounter<long>(
            name: "feedser.jobs.triggers.accepted",
            unit: "count",
            description: "Number of job trigger requests accepted for execution.");

        _triggersRejected = Meter.CreateCounter<long>(
            name: "feedser.jobs.triggers.rejected",
            unit: "count",
            description: "Number of job trigger requests rejected or ignored by the coordinator.");

        _runsCompleted = Meter.CreateCounter<long>(
            name: "feedser.jobs.runs.completed",
            unit: "count",
            description: "Number of job executions that have finished grouped by outcome.");

        _runsActive = Meter.CreateUpDownCounter<long>(
            name: "feedser.jobs.runs.active",
            unit: "count",
            description: "Current number of running job executions.");

        _runDurationSeconds = Meter.CreateHistogram<double>(
            name: "feedser.jobs.runs.duration",
            unit: "s",
            description: "Distribution of job execution durations in seconds.");

        _schedulerSkewMilliseconds = Meter.CreateHistogram<double>(
            name: "feedser.scheduler.skew",
            unit: "ms",
            description: "Difference between the intended and actual scheduler fire time in milliseconds.");
    }

    public ActivitySource ActivitySource { get; }

    public Meter Meter { get; }

    public Activity? StartTriggerActivity(string kind, string trigger)
    {
        var activity = ActivitySource.StartActivity(TriggerActivityName, ActivityKind.Internal);
        if (activity is not null)
        {
            activity.SetTag("job.kind", kind);
            activity.SetTag("job.trigger", trigger);
        }

        return activity;
    }

    public Activity? StartSchedulerActivity(string kind, DateTimeOffset scheduledFor, DateTimeOffset invokedAt)
    {
        var activity = ActivitySource.StartActivity(SchedulerActivityName, ActivityKind.Internal);
        if (activity is not null)
        {
            activity.SetTag("job.kind", kind);
            activity.SetTag("job.scheduled_for", scheduledFor.UtcDateTime);
            activity.SetTag("job.invoked_at", invokedAt.UtcDateTime);
            activity.SetTag("job.scheduler_delay_ms", (invokedAt - scheduledFor).TotalMilliseconds);
        }

        return activity;
    }

    public Activity? StartExecutionActivity(string kind, string trigger, Guid runId)
    {
        var activity = ActivitySource.StartActivity(ExecuteActivityName, ActivityKind.Internal);
        if (activity is not null)
        {
            activity.SetTag("job.kind", kind);
            activity.SetTag("job.trigger", trigger);
            activity.SetTag("job.run_id", runId);
        }

        return activity;
    }

    public void RecordTriggerAccepted(string kind, string trigger)
    {
        var tags = new TagList
        {
            { "job.kind", kind },
            { "job.trigger", trigger },
        };
        _triggersAccepted.Add(1, tags);
    }

    public void RecordTriggerRejected(string kind, string trigger, string reason)
    {
        var tags = new TagList
        {
            { "job.kind", kind },
            { "job.trigger", trigger },
            { "job.reason", reason },
        };
        _triggersRejected.Add(1, tags);
    }

    public void RecordRunStarted(string kind)
    {
        var tags = new TagList { { "job.kind", kind } };
        _runsActive.Add(1, tags);
    }

    public void RecordRunCompleted(string kind, JobRunStatus status, TimeSpan? duration, string? error)
    {
        var outcome = status.ToString();

        var completionTags = new TagList
        {
            { "job.kind", kind },
            { "job.status", outcome },
        };

        if (!string.IsNullOrWhiteSpace(error))
        {
            completionTags.Add("job.error", error);
        }

        _runsCompleted.Add(1, completionTags);

        var activeTags = new TagList { { "job.kind", kind } };
        _runsActive.Add(-1, activeTags);

        if (duration.HasValue)
        {
            var seconds = Math.Max(duration.Value.TotalSeconds, 0d);
            var durationTags = new TagList
            {
                { "job.kind", kind },
                { "job.status", outcome },
            };
            _runDurationSeconds.Record(seconds, durationTags);
        }
    }

    public void RecordSchedulerSkew(string kind, DateTimeOffset scheduledFor, DateTimeOffset invokedAt)
    {
        var skew = (invokedAt - scheduledFor).TotalMilliseconds;
        var tags = new TagList { { "job.kind", kind } };
        _schedulerSkewMilliseconds.Record(skew, tags);
    }

    public void Dispose()
    {
        ActivitySource.Dispose();
        Meter.Dispose();
    }
}
