using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Collections.ObjectModel;

namespace StellaOps.Scanner.Worker.Options;

public sealed class ScannerWorkerOptions
{
    public const string SectionName = "Scanner:Worker";

    public int MaxConcurrentJobs { get; set; } = 2;

    public QueueOptions Queue { get; } = new();

    public PollingOptions Polling { get; } = new();

    public AuthorityOptions Authority { get; } = new();

    public TelemetryOptions Telemetry { get; } = new();

    public ShutdownOptions Shutdown { get; } = new();

    public sealed class QueueOptions
    {
        public int MaxAttempts { get; set; } = 5;

        public double HeartbeatSafetyFactor { get; set; } = 3.0;

        public int MaxHeartbeatJitterMilliseconds { get; set; } = 750;

        public IReadOnlyList<TimeSpan> HeartbeatRetryDelays => _heartbeatRetryDelays;

        public TimeSpan MinHeartbeatInterval { get; set; } = TimeSpan.FromSeconds(10);

        public TimeSpan MaxHeartbeatInterval { get; set; } = TimeSpan.FromSeconds(30);

        public void SetHeartbeatRetryDelays(IEnumerable<TimeSpan> delays)
        {
            _heartbeatRetryDelays = NormalizeDelays(delays);
        }

        internal IReadOnlyList<TimeSpan> NormalizedHeartbeatRetryDelays => _heartbeatRetryDelays;

        private static IReadOnlyList<TimeSpan> NormalizeDelays(IEnumerable<TimeSpan> delays)
        {
            var buffer = new List<TimeSpan>();
            foreach (var delay in delays)
            {
                if (delay <= TimeSpan.Zero)
                {
                    continue;
                }

                buffer.Add(delay);
            }

            buffer.Sort();
            return new ReadOnlyCollection<TimeSpan>(buffer);
        }

        private IReadOnlyList<TimeSpan> _heartbeatRetryDelays = new ReadOnlyCollection<TimeSpan>(new TimeSpan[]
        {
            TimeSpan.FromSeconds(2),
            TimeSpan.FromSeconds(5),
            TimeSpan.FromSeconds(10),
        });
    }

    public sealed class PollingOptions
    {
        public TimeSpan InitialDelay { get; set; } = TimeSpan.FromMilliseconds(200);

        public TimeSpan MaxDelay { get; set; } = TimeSpan.FromSeconds(5);

        public double JitterRatio { get; set; } = 0.2;
    }

    public sealed class AuthorityOptions
    {
        public bool Enabled { get; set; }

        public string? Issuer { get; set; }

        public string? ClientId { get; set; }

        public string? ClientSecret { get; set; }

        public bool RequireHttpsMetadata { get; set; } = true;

        public string? MetadataAddress { get; set; }

        public int BackchannelTimeoutSeconds { get; set; } = 20;

        public int TokenClockSkewSeconds { get; set; } = 30;

        public IList<string> Scopes { get; } = new List<string> { "scanner.scan" };

        public ResilienceOptions Resilience { get; } = new();
    }

    public sealed class ResilienceOptions
    {
        public bool? EnableRetries { get; set; }

        public IList<TimeSpan> RetryDelays { get; } = new List<TimeSpan>
        {
            TimeSpan.FromMilliseconds(250),
            TimeSpan.FromMilliseconds(500),
            TimeSpan.FromSeconds(1),
            TimeSpan.FromSeconds(5),
        };

        public bool? AllowOfflineCacheFallback { get; set; }

        public TimeSpan? OfflineCacheTolerance { get; set; }
    }

    public sealed class TelemetryOptions
    {
        public bool EnableLogging { get; set; } = true;

        public bool EnableTelemetry { get; set; } = true;

        public bool EnableTracing { get; set; }

        public bool EnableMetrics { get; set; } = true;

        public string ServiceName { get; set; } = "stellaops-scanner-worker";

        public string? OtlpEndpoint { get; set; }

        public bool ExportConsole { get; set; }

        public IDictionary<string, string?> ResourceAttributes { get; } = new ConcurrentDictionary<string, string?>(StringComparer.OrdinalIgnoreCase);
    }

    public sealed class ShutdownOptions
    {
        public TimeSpan Timeout { get; set; } = TimeSpan.FromSeconds(30);
    }
}
