#![doc = include_str!("../README.md")]

pub mod query;
pub mod result;

use prometheus_http_query::Client;
use query::IntoQuery;
use reqwest::header::HeaderValue;
use result::{IntoQryResult, QryResult};
use url::Url;

/// The primary way to send queries and get deserialized responses from the metrics server
///
/// The default instance uses no authentication, initializes it's own reqwest client and the
/// server url is set to "http://127.0.0.1:9090" and sends requests using the GET HTTP method.
#[derive(Default, Clone)]
pub struct QueryManager {
    auth: Auth,
    client: Client,
    timeout: Option<i64>,
    method: Method,
}

impl QueryManager {
    /// Build a new QueryManager using a valid base URL (without Prometheus API path)
    pub fn new(url: Url) -> Self {
        QueryManager {
            #[allow(clippy::unwrap_used)] // url is already validated
            client: Client::try_from(url.as_str()).expect("unexpected failure"),
            ..Default::default()
        }
    }

    /// Build a new QueryManager using a valid base URL (without Prometheus API path) and an existing reqwest Client
    pub fn from_client(url: Url, client: reqwest::Client) -> Self {
        QueryManager {
            auth: Default::default(),
            #[allow(clippy::unwrap_used)] // url is already validated
            client: Client::from(client, url.as_str()).expect("unexpected failure"),
            ..Default::default()
        }
    }

    /// Set authentication method (no auth is used by default)
    ///
    /// Currently only supports bearer tokens
    pub fn auth(&mut self, auth: Auth) -> &mut Self {
        self.auth = auth;
        self
    }

    /// Set timeout for Prometheus queries
    pub fn timeout(&mut self, timeout: i64) -> &mut Self {
        self.timeout.replace(timeout);
        self
    }

    /// Set HTTP method to use to send queries to the server (default: `Get`)
    pub fn method(&mut self, method: Method) -> &mut Self {
        self.method = method;
        self
    }

    /// Send a query to the Prometheus server and retreived deserialized data
    ///
    /// This method accepts any type that implements the `IntoQuery` trait.
    /// The trait is implemented for `&str`, `String`, `&String`, and __mquery's__
    /// internal types such as [`query::Metric`], [`query::Scalar`] and others that result
    /// from the query builder API.
    pub async fn query<Q: IntoQuery>(&self, query: Q) -> QryResult {
        let mut builder = self.client.query(query.into_query().as_ref());
        if let Some((name, val)) = self.auth.get_header() {
            builder = builder.header(name, val);
        }
        if let Some(timeout) = self.timeout.as_ref() {
            builder = builder.timeout(*timeout);
        }
        match self.method {
            Method::Get => builder.get().await.into_qry_result(),
            Method::Post => builder.post().await.into_qry_result(),
        }
    }

    /// Send a ranged query to the Prometheus server and retreived deserialized data
    ///
    /// This method accepts any type that implements the `IntoQuery` trait.
    /// The trait is implemented for `&str`, `String`, `&String`, and __mquery's__
    /// internal types such as [`query::Metric`], [`query::Scalar`] and others that result
    /// from the query builder API.
    pub async fn query_range<Q: IntoQuery>(
        &self,
        query: Q,
        start: i64,
        end: i64,
        step: f64,
    ) -> QryResult {
        let mut builder = self
            .client
            .query_range(query.into_query().as_ref(), start, end, step);
        if let Some((name, val)) = self.auth.get_header() {
            builder = builder.header(name, val);
        }
        if let Some(timeout) = self.timeout.as_ref() {
            builder = builder.timeout(*timeout);
        }
        match self.method {
            Method::Get => builder.get().await.into_qry_result(),
            Method::Post => builder.post().await.into_qry_result(),
        }
    }
}

// TODO: Add support for basic auth as well
/// Authentication method
///
/// Used in [`QueryManager::auth`] to set the authentication type
#[derive(Default, Clone, Debug)]
pub enum Auth {
    #[default]
    None,
    Bearer(String),
}

impl Auth {
    const AUTH_HEADER: &'static str = "Authorization";

    fn get_header(&self) -> Option<(&'static str, HeaderValue)> {
        match self {
            Auth::None => None,
            Auth::Bearer(token) => Some((
                Self::AUTH_HEADER,
                #[allow(clippy::unwrap_used)] // building from valid chars
                format!("Bearer {}", token)
                    .parse()
                    .expect("unexpected failure"),
            )),
        }
    }
}

/// HTTP method to use to send queries
///
/// This is used by the [`QueryManager::method`] method to set the HTTP
/// method used to send queries to the server.
#[derive(Clone, Copy, Debug, Default)]
pub enum Method {
    #[default]
    Get,
    Post,
}

// Private trait used to seal other traits to disallow users from implementing
// them directly
pub(crate) mod seal {
    pub(crate) trait Sealed {}
}