use crate::config::api_url;
use crate::credentials::Credentials;
use crate::error::Error;
use chrono::Utc;
use eyre::{Ok, WrapErr};
use reqwest::StatusCode;
use serde::{Deserialize, Serialize};
use tokio::sync::OnceCell;

#[derive(Clone)]
pub struct Client {
    access_token: String,
    client: reqwest::Client,
}

static CREDENTIALS: OnceCell<Credentials> = OnceCell::const_new();

impl Client {
    pub async fn new(is_directly: bool) -> eyre::Result<Self> {
        if is_directly {
            return Ok(Client {
                access_token: "".into(),
                client: reqwest::Client::new(),
            });
        }

        let credentials = CREDENTIALS
            .get_or_try_init(async || Credentials::new().await)
            .await?;

        // If credentials expired — request to re-login
        if credentials.expires_at.timestamp() <= Utc::now().timestamp() {
            return Err(eyre::eyre!("Credentials expired, please re-login."));
        }

        Ok(Client {
            access_token: credentials.token.clone(),
            client: reqwest::Client::new(),
        })
    }

    /// A POST request with the Authorization header
    pub fn post(&self, path: &str) -> reqwest::RequestBuilder {
        self.client
            .post(api_url(path))
            .header("Authorization", &self.access_token)
    }

    /// Encapsulate a typical POST request
    pub async fn request<B, R>(&self, path: &str, body: B) -> eyre::Result<R>
    where
        B: Serialize + for<'de> Deserialize<'de>,
        R: Serialize + for<'de> Deserialize<'de>,
    {
        let result = self
            .post(path)
            .json(&body)
            .send()
            .await
            .inspect_err(|err| log::error!("{err:?}"))
            .wrap_err(Error::new(
                "Network request failed",
                Some("Try again in a few seconds."),
            ))?;

        let status = result.status();
        let text = result.text().await?;
        log::info!("Got status from {path}: {status}");
        log::info!("Got response from {path}: {text}");

        if status != StatusCode::OK {
            let response: serde_json::Value = serde_json::from_str(&text).wrap_err(Error::new(
                "Invalid response from server",
                Some("Try again later."),
            ))?;

            return Err(Error::new(
                "Request failed",
                Some(
                    response
                        .get("error")
                        .unwrap_or(&serde_json::Value::Null)
                        .as_str()
                        .unwrap_or("Unknown error"),
                ),
            )
            .into());
        }

        Ok(serde_json::from_str(&text).wrap_err("Could not parse")?)
    }
}
