using System.Net;
using Serilog;

namespace adas_core.module.ProxyDevices.Devices;

/// <summary>
/// Device that can be used to get content via http with digest auth
/// </summary>
public class HttpDevice : IProxyDevice
{
    /// <summary>
    /// Domain for digest auth. Optional, only used if username is provided
    /// </summary>
    private string _domain = "";

    /// <summary>
    /// Indicates whether the device has been initialized with parameters. This is important to prevent multiple initializations and to ensure that the device is ready for processing requests.
    /// </summary>
    private bool _isInitialized;

    /// <summary>
    /// Name of the device. Optional, can be used for logging or identification purposes.
    /// </summary>
    private string _name = "";

    /// <summary>
    /// Password for digest auth. Optional, only used if username is provided
    /// </summary>
    private string _password = "";

    /// <summary>
    /// URI of the device. This is a required parameter and must be a valid absolute URI. It represents the endpoint that the device will interact with when processing requests.
    /// </summary>
    private Uri _uri = null!;

    /// <summary>
    /// Username for digest auth. Optional, if not provided, the device will attempt to access the URI without authentication. 
    /// If provided, it will be used in conjunction with the password and domain (if specified) for authentication purposes.
    /// </summary>
    private string _username = "";

    /// <summary>
    /// Initializes the device with the provided parameters. The parameters are expected to be in a dictionary format, where the key is a string representing the parameter name and the value is an object that can be cast to the appropriate type. 
    /// The method checks for the presence of required parameters (like "url") and validates them, throwing exceptions if any issues are found. Optional parameters (like "name", "username", "password", and "domain") are also processed if they are present in the dictionary.
    /// </summary>
    /// <param name="deviceParameters">A dictionary containing the parameters required to initialize the device.</param>
    /// <exception cref="InvalidOperationException">Thrown when the device is already initialized.</exception>
    /// <exception cref="ArgumentNullException">Thrown when a required parameter is missing or invalid.</exception>
    public void Initialize(Dictionary<string, object?> deviceParameters)
    {
        if (_isInitialized) throw new InvalidOperationException("Device already initialized");
        if (!deviceParameters.TryGetValue("url", out var urlobj) || urlobj is not string url ||
            string.IsNullOrEmpty(url.Trim()) || !Uri.TryCreate(url, UriKind.Absolute, out var uri))
            throw new ArgumentNullException(nameof(deviceParameters),
                "Device parameter 'url' is required and must be a valid ur");
        _uri = uri;
        if (deviceParameters.TryGetValue("name", out var name)) _name = name?.ToString() ?? "";
        if (deviceParameters.TryGetValue("username", out var username)) _username = username?.ToString() ?? "";
        if (deviceParameters.TryGetValue("password", out var password)) _password = password?.ToString() ?? "";
        if (deviceParameters.TryGetValue("domain", out var domain)) _domain = domain?.ToString() ?? "";
        _isInitialized = true;
    }


    /// <summary>
    /// Processes a request by sending an HTTP GET request to the specified URI using the HttpClient. If authentication parameters were provided during initialization, they will be used for the request. 
    /// The method handles potential timeouts by catching TaskCanceledException and rethrowing it as a TimeoutException with a descriptive message. The response from the HTTP request is returned as an HttpResponseMessage.
    /// </summary>
    /// <returns>An HttpResponseMessage representing the response from the HTTP request.</returns>
    /// <exception cref="TimeoutException">Thrown when the request times out.</exception>
    public async Task<HttpResponseMessage> Process()
    {
        try
        {
            var client = GetHttpClient();
            return await client.GetAsync(_uri);
        }
        catch (TaskCanceledException ex)
        {
            Log.Error("Error getting Http Client get async. Exception:{ex}", ex.ToString());
            throw new TimeoutException("La solicitud fue cancelada debido al tiempo de espera.", ex);
        }
    }

    /// <summary>
    /// Streams content from the specified URI using an HTTP GET request with the option to read response headers immediately. This method is similar to Process(), but it allows for streaming the response content as it is received, 
    /// which can be useful for large responses or when you want to start processing the data before the entire response is available. 
    /// Like Process(), it handles potential timeouts by catching TaskCanceledException and rethrowing it as a TimeoutException with a descriptive message.
    /// </summary>
    /// <returns>An HttpResponseMessage representing the response from the HTTP request.</returns>
    /// <exception cref="TimeoutException">Thrown when the request times out.</exception>
    public async Task<HttpResponseMessage> Stream()
    {
        try
        {
            var client = GetHttpClient();
            var response = await client.GetAsync(_uri, HttpCompletionOption.ResponseHeadersRead);
            return response;
        }
        catch (TaskCanceledException ex)
        {
            Log.Error("Exception getting response. Ex: {ex}", ex.ToString());
            throw new TimeoutException("La solicitud fue cancelada debido al tiempo de espera.", ex);
        }
    }

    /// <summary>
    /// Creates and configures an HttpClient instance based on the initialization parameters of the device. 
    /// If authentication parameters (username, password, and optionally domain) were provided during initialization, they will be set in the HttpClientHandler's Credentials property to enable digest authentication for the HTTP requests.
    /// </summary>
    /// <returns>An HttpClient instance configured with the appropriate credentials.</returns>
    /// <exception cref="InvalidOperationException">Thrown when the device is not initialized.</exception>
    private HttpClient GetHttpClient()
    {
        if (!_isInitialized) throw new InvalidOperationException("Device not initialized");
        // get content via http with digest auth
        var clientHandler = new HttpClientHandler();
        if (!string.IsNullOrEmpty(_username))
            clientHandler.Credentials = new NetworkCredential(_username, _password, _domain);
        return new HttpClient(clientHandler);
    }

    /// <summary>
    /// Returns a string representation of the HttpDevice instance, including its name, URI, username, password, and domain. This can be useful for logging or debugging purposes to quickly identify the configuration of the device.
    /// </summary>
    /// <returns>A string representation of the HttpDevice instance.</returns>
    public override string ToString()
    {
        return $"[HttpDevice] ({_name} {_uri} {_username} {_password} {_domain})";
    }
}