using System.Collections.ObjectModel;
using System.Text.Json.Serialization;
using StellaOps.Scanner.Core.Utility;

namespace StellaOps.Scanner.Core.Contracts;

[JsonConverter(typeof(JsonStringEnumConverter<ScanStage>))]
public enum ScanStage
{
    Unknown = 0,
    ResolveImage,
    FetchLayers,
    MountLayers,
    AnalyzeOperatingSystem,
    AnalyzeLanguageEcosystems,
    AnalyzeNativeArtifacts,
    ComposeSbom,
    BuildDiffs,
    EmitArtifacts,
    SignArtifacts,
    Complete
}

[JsonConverter(typeof(JsonStringEnumConverter<ScanProgressEventKind>))]
public enum ScanProgressEventKind
{
    Progress = 0,
    StageStarted,
    StageCompleted,
    Warning,
    Error
}

public sealed class ScanProgressEvent
{
    private static readonly IReadOnlyDictionary<string, string> EmptyAttributes =
        new ReadOnlyDictionary<string, string>(new Dictionary<string, string>(0, StringComparer.Ordinal));

    [JsonConstructor]
    public ScanProgressEvent(
        ScanJobId jobId,
        ScanStage stage,
        ScanProgressEventKind kind,
        int sequence,
        DateTimeOffset timestamp,
        double? percentComplete = null,
        string? message = null,
        IReadOnlyDictionary<string, string>? attributes = null,
        ScannerError? error = null)
    {
        if (sequence < 0)
        {
            throw new ArgumentOutOfRangeException(nameof(sequence), sequence, "Sequence cannot be negative.");
        }

        JobId = jobId;
        Stage = stage;
        Kind = kind;
        Sequence = sequence;
        Timestamp = ScannerTimestamps.Normalize(timestamp);
        PercentComplete = percentComplete is < 0 or > 100 ? null : percentComplete;
        Message = message is { Length: > 0 } ? message.Trim() : null;
        Attributes = attributes is null or { Count: 0 }
            ? EmptyAttributes
            : new ReadOnlyDictionary<string, string>(new Dictionary<string, string>(attributes, StringComparer.Ordinal));
        Error = error;
    }

    [JsonPropertyName("jobId")]
    [JsonPropertyOrder(0)]
    public ScanJobId JobId { get; }

    [JsonPropertyName("stage")]
    [JsonPropertyOrder(1)]
    public ScanStage Stage { get; }

    [JsonPropertyName("kind")]
    [JsonPropertyOrder(2)]
    public ScanProgressEventKind Kind { get; }

    [JsonPropertyName("sequence")]
    [JsonPropertyOrder(3)]
    public int Sequence { get; }

    [JsonPropertyName("timestamp")]
    [JsonPropertyOrder(4)]
    public DateTimeOffset Timestamp { get; }

    [JsonPropertyName("percentComplete")]
    [JsonPropertyOrder(5)]
    public double? PercentComplete { get; }

    [JsonPropertyName("message")]
    [JsonPropertyOrder(6)]
    public string? Message { get; }

    [JsonPropertyName("attributes")]
    [JsonPropertyOrder(7)]
    public IReadOnlyDictionary<string, string> Attributes { get; }

    [JsonPropertyName("error")]
    [JsonPropertyOrder(8)]
    public ScannerError? Error { get; }

    public ScanProgressEvent With(
        ScanProgressEventKind? kind = null,
        double? percentComplete = null,
        string? message = null,
        IReadOnlyDictionary<string, string>? attributes = null,
        ScannerError? error = null)
        => new(
            JobId,
            Stage,
            kind ?? Kind,
            Sequence,
            Timestamp,
            percentComplete ?? PercentComplete,
            message ?? Message,
            attributes ?? Attributes,
            error ?? Error);
}
