using System.Collections.ObjectModel;
using System.Net;
using System.Net.Http;
using System.Net.Security;
using System.Security.Cryptography.X509Certificates;

namespace StellaOps.Feedser.Source.Common.Http;

/// <summary>
/// Configuration applied to named HTTP clients used by connectors.
/// </summary>
public sealed class SourceHttpClientOptions
{
    private readonly HashSet<string> _allowedHosts = new(StringComparer.OrdinalIgnoreCase);
    private readonly Dictionary<string, string> _defaultHeaders = new(StringComparer.OrdinalIgnoreCase);

    /// <summary>
    /// Gets or sets the base address used for relative requests.
    /// </summary>
    public Uri? BaseAddress { get; set; }

    /// <summary>
    /// Gets or sets the client timeout.
    /// </summary>
    public TimeSpan Timeout { get; set; } = TimeSpan.FromSeconds(30);

    /// <summary>
    /// Gets or sets the user-agent string applied to outgoing requests.
    /// </summary>
    public string UserAgent { get; set; } = "StellaOps.Feedser/1.0";

    /// <summary>
    /// Gets or sets whether redirects are allowed. Defaults to <c>true</c>.
    /// </summary>
    public bool AllowAutoRedirect { get; set; } = true;

    /// <summary>
    /// Maximum number of retry attempts for transient failures.
    /// </summary>
    public int MaxAttempts { get; set; } = 3;

    /// <summary>
    /// Base delay applied to the exponential backoff policy.
    /// </summary>
    public TimeSpan BaseDelay { get; set; } = TimeSpan.FromSeconds(2);

    /// <summary>
    /// Hosts that this client is allowed to contact.
    /// </summary>
    public ISet<string> AllowedHosts => _allowedHosts;

    /// <summary>
    /// Gets or sets the default HTTP version requested by the client. Defaults to HTTP/2.
    /// </summary>
    public Version RequestVersion { get; set; } = HttpVersion.Version20;

    /// <summary>
    /// Gets or sets the policy that determines how HTTP version negotiation occurs. Defaults to <see cref="HttpVersionPolicy.RequestVersionOrLower"/>.
    /// </summary>
    public HttpVersionPolicy VersionPolicy { get; set; } = HttpVersionPolicy.RequestVersionOrLower;

    /// <summary>
    /// Gets or sets a value indicating whether multiple HTTP/2 connections may be established to the same endpoint.
    /// </summary>
    public bool EnableMultipleHttp2Connections { get; set; } = true;

    /// <summary>
    /// Optional callback to customise the underlying <see cref="SocketsHttpHandler"/>.
    /// </summary>
    public Action<SocketsHttpHandler>? ConfigureHandler { get; set; }

    /// <summary>
    /// Optional proxy address used for outbound requests.
    /// </summary>
    public Uri? ProxyAddress { get; set; }

    /// <summary>
    /// Indicates whether the proxy should be bypassed for local addresses. Defaults to <c>true</c>.
    /// </summary>
    public bool ProxyBypassOnLocal { get; set; } = true;

    /// <summary>
    /// Optional explicit bypass list applied to the proxy.
    /// </summary>
    public IList<string> ProxyBypassList { get; } = new List<string>();

    /// <summary>
    /// Indicates whether the default credentials should be used for the proxy.
    /// </summary>
    public bool ProxyUseDefaultCredentials { get; set; }

    /// <summary>
    /// Optional proxy username.
    /// </summary>
    public string? ProxyUsername { get; set; }

    /// <summary>
    /// Optional proxy password.
    /// </summary>
    public string? ProxyPassword { get; set; }

    /// <summary>
    /// Gets or sets a value indicating whether server certificate validation should be bypassed.
    /// </summary>
    public bool AllowInvalidServerCertificates { get; set; }

    /// <summary>
    /// Additional trusted root certificates appended to the default trust store when negotiating TLS.
    /// </summary>
    public IList<X509Certificate2> TrustedRootCertificates { get; } = new List<X509Certificate2>();

    /// <summary>
    /// Optional callback invoked to validate remote certificates when <see cref="TrustedRootCertificates"/> is insufficient.
    /// </summary>
    public Func<X509Certificate2?, X509Chain?, SslPolicyErrors, bool>? ServerCertificateCustomValidation { get; set; }

    /// <summary>
    /// Default request headers appended to each outgoing request.
    /// </summary>
    public IDictionary<string, string> DefaultRequestHeaders => _defaultHeaders;

    internal SourceHttpClientOptions Clone()
    {
        var clone = new SourceHttpClientOptions
        {
            BaseAddress = BaseAddress,
            Timeout = Timeout,
            UserAgent = UserAgent,
            AllowAutoRedirect = AllowAutoRedirect,
            MaxAttempts = MaxAttempts,
            BaseDelay = BaseDelay,
            RequestVersion = RequestVersion,
            VersionPolicy = VersionPolicy,
            EnableMultipleHttp2Connections = EnableMultipleHttp2Connections,
            ConfigureHandler = ConfigureHandler,
            AllowInvalidServerCertificates = AllowInvalidServerCertificates,
            ServerCertificateCustomValidation = ServerCertificateCustomValidation,
            ProxyAddress = ProxyAddress,
            ProxyBypassOnLocal = ProxyBypassOnLocal,
            ProxyUseDefaultCredentials = ProxyUseDefaultCredentials,
            ProxyUsername = ProxyUsername,
            ProxyPassword = ProxyPassword,
        };

        foreach (var host in _allowedHosts)
        {
            clone.AllowedHosts.Add(host);
        }

        foreach (var header in _defaultHeaders)
        {
            clone.DefaultRequestHeaders[header.Key] = header.Value;
        }

        foreach (var certificate in TrustedRootCertificates)
        {
            clone.TrustedRootCertificates.Add(certificate);
        }

        foreach (var entry in ProxyBypassList)
        {
            clone.ProxyBypassList.Add(entry);
        }

        return clone;
    }

    internal IReadOnlyCollection<string> GetAllowedHostsSnapshot()
        => new ReadOnlyCollection<string>(_allowedHosts.ToArray());
}
