using Microsoft.Extensions.Logging;

namespace ZB.MOM.WW.LmxProxy.Client;

/// <summary>
/// Fluent builder for creating configured <see cref="LmxProxyClient"/> instances.
/// </summary>
public class LmxProxyClientBuilder
{
    private string? _host;
    private int _port = 50051;
    private string? _apiKey;
    private ILogger<LmxProxyClient>? _logger;
    private TimeSpan _defaultTimeout = TimeSpan.FromSeconds(30);
    private int _maxRetryAttempts = 3;
    private TimeSpan _retryDelay = TimeSpan.FromSeconds(1);
    private bool _enableMetrics;
    private string? _correlationIdHeader;
    private ClientTlsConfiguration? _tlsConfiguration;

    /// <summary>Sets the host address of the LmxProxy server. Required.</summary>
    public LmxProxyClientBuilder WithHost(string host)
    {
        if (string.IsNullOrWhiteSpace(host))
            throw new ArgumentException("Host must not be null or empty.", nameof(host));
        _host = host;
        return this;
    }

    /// <summary>Sets the port of the LmxProxy server. Default is 50051.</summary>
    public LmxProxyClientBuilder WithPort(int port)
    {
        if (port < 1 || port > 65535)
            throw new ArgumentOutOfRangeException(nameof(port), "Port must be between 1 and 65535.");
        _port = port;
        return this;
    }

    /// <summary>Sets the API key for authentication.</summary>
    public LmxProxyClientBuilder WithApiKey(string? apiKey)
    {
        _apiKey = apiKey;
        return this;
    }

    /// <summary>Sets the logger instance for the client.</summary>
    public LmxProxyClientBuilder WithLogger(ILogger<LmxProxyClient> logger)
    {
        _logger = logger ?? throw new ArgumentNullException(nameof(logger));
        return this;
    }

    /// <summary>Sets the default timeout for operations. Must be between 1 second and 10 minutes.</summary>
    public LmxProxyClientBuilder WithTimeout(TimeSpan timeout)
    {
        if (timeout <= TimeSpan.Zero || timeout > TimeSpan.FromMinutes(10))
            throw new ArgumentOutOfRangeException(nameof(timeout), "Timeout must be greater than zero and at most 10 minutes.");
        _defaultTimeout = timeout;
        return this;
    }

    /// <summary>Enables TLS with an optional server CA certificate path.</summary>
    public LmxProxyClientBuilder WithSslCredentials(string? certificatePath)
    {
        _tlsConfiguration ??= new ClientTlsConfiguration();
        _tlsConfiguration.UseTls = true;
        _tlsConfiguration.ServerCaCertificatePath = certificatePath;
        return this;
    }

    /// <summary>Sets a full TLS configuration.</summary>
    public LmxProxyClientBuilder WithTlsConfiguration(ClientTlsConfiguration config)
    {
        _tlsConfiguration = config ?? throw new ArgumentNullException(nameof(config));
        return this;
    }

    /// <summary>Configures the retry policy. maxAttempts must be positive, retryDelay must be positive.</summary>
    public LmxProxyClientBuilder WithRetryPolicy(int maxAttempts, TimeSpan retryDelay)
    {
        if (maxAttempts <= 0)
            throw new ArgumentOutOfRangeException(nameof(maxAttempts), "Max retry attempts must be greater than zero.");
        if (retryDelay <= TimeSpan.Zero)
            throw new ArgumentOutOfRangeException(nameof(retryDelay), "Retry delay must be greater than zero.");
        _maxRetryAttempts = maxAttempts;
        _retryDelay = retryDelay;
        return this;
    }

    /// <summary>Enables client-side metrics collection.</summary>
    public LmxProxyClientBuilder WithMetrics()
    {
        _enableMetrics = true;
        return this;
    }

    /// <summary>Sets the correlation ID header name for request tracing.</summary>
    public LmxProxyClientBuilder WithCorrelationIdHeader(string headerName)
    {
        if (string.IsNullOrEmpty(headerName))
            throw new ArgumentException("Header name must not be null or empty.", nameof(headerName));
        _correlationIdHeader = headerName;
        return this;
    }

    /// <summary>
    /// Builds and returns a configured <see cref="LmxProxyClient"/> instance.
    /// </summary>
    /// <exception cref="InvalidOperationException">Thrown when host is not set.</exception>
    /// <exception cref="FileNotFoundException">Thrown when TLS certificate paths don't exist.</exception>
    public LmxProxyClient Build()
    {
        if (string.IsNullOrWhiteSpace(_host))
            throw new InvalidOperationException("Host must be specified. Call WithHost() before Build().");

        ValidateTlsConfiguration();

        var client = new LmxProxyClient(_host, _port, _apiKey, _tlsConfiguration, _logger)
        {
            DefaultTimeout = _defaultTimeout
        };

        client.SetBuilderConfiguration(new ClientConfiguration
        {
            MaxRetryAttempts = _maxRetryAttempts,
            RetryDelay = _retryDelay,
            EnableMetrics = _enableMetrics,
            CorrelationIdHeader = _correlationIdHeader
        });

        return client;
    }

    private void ValidateTlsConfiguration()
    {
        if (_tlsConfiguration?.UseTls != true)
            return;

        if (!string.IsNullOrEmpty(_tlsConfiguration.ServerCaCertificatePath) &&
            !File.Exists(_tlsConfiguration.ServerCaCertificatePath))
            throw new FileNotFoundException(
                $"Server CA certificate not found: {_tlsConfiguration.ServerCaCertificatePath}",
                _tlsConfiguration.ServerCaCertificatePath);

        if (!string.IsNullOrEmpty(_tlsConfiguration.ClientCertificatePath) &&
            !File.Exists(_tlsConfiguration.ClientCertificatePath))
            throw new FileNotFoundException(
                $"Client certificate not found: {_tlsConfiguration.ClientCertificatePath}",
                _tlsConfiguration.ClientCertificatePath);

        if (!string.IsNullOrEmpty(_tlsConfiguration.ClientKeyPath) &&
            !File.Exists(_tlsConfiguration.ClientKeyPath))
            throw new FileNotFoundException(
                $"Client key not found: {_tlsConfiguration.ClientKeyPath}",
                _tlsConfiguration.ClientKeyPath);
    }
}
