use reqwest::blocking::Client;
use reqwest::Error as ReqwestError;
use serde_json::Value;
use std::collections::HashMap;
use std::env;
use std::error::Error;
use std::time::{Duration, SystemTime};

pub const OAUTH_TOKEN_URL: &str = "https://authe.jpmchase.com/as/token.oauth2";
pub const OAUTH_RESOURCE_ID: &str = "JPMC:URI:RS-06785-DataQueryExternalApi-PROD";
#[derive(Debug, Clone)]
pub struct OAuthClient {
    client_id: String,
    client_secret: String,
    token_url: String,
    resource_id: String,
    access_token: Option<String>,
    expires_at: Option<SystemTime>, // Stores the token's expiration time
}

impl OAuthClient {
    pub fn new(client_id: String, client_secret: String) -> Self {
        OAuthClient {
            client_id,
            client_secret,
            token_url: OAUTH_TOKEN_URL.to_string(),
            resource_id: OAUTH_RESOURCE_ID.to_string(),
            access_token: None,
            expires_at: None, // Initially None until a token is fetched
        }
    }

    fn fetch_token(&mut self) -> Result<(), ReqwestError> {
        let client = Client::new();

        // Set up the form parameters for the request
        let mut params = HashMap::new();
        params.insert("grant_type", "client_credentials");
        params.insert("client_id", &self.client_id);
        params.insert("client_secret", &self.client_secret);
        params.insert("aud", &self.resource_id);

        // Make the POST request to retrieve the token
        let response = client.post(&self.token_url).form(&params).send()?;

        if response.status().is_success() {
            let json: Value = response.json()?;
            if let Some(token) = json["access_token"].as_str() {
                self.access_token = Some(token.to_string());
                if let Some(expires_in) = json["expires_in"].as_u64() {
                    self.expires_at = Some(SystemTime::now() + Duration::from_secs(expires_in));
                } else {
                    self.expires_at = Some(SystemTime::now() + Duration::from_secs(3600));
                }
            } else {
                eprintln!("Failed to retrieve access token from response.");
            }
        } else {
            eprintln!("Token request failed with status: {}", response.status());
        }

        Ok(())
    }

    fn is_token_expired(&self) -> bool {
        match self.expires_at {
            Some(expiration) => SystemTime::now() >= expiration,
            None => true, // If there's no expiration set, assume it's expired
        }
    }

    pub fn get_headers(&mut self) -> Result<HashMap<String, String>, Box<dyn Error>> {
        if self.is_token_expired() {
            // println!("Token has expired. Fetching a new token...");
            self.fetch_token()?;
        }
        let mut headers = HashMap::new();
        if let Some(token) = &self.access_token {
            headers.insert("Authorization".to_string(), format!("Bearer {}", token));
        } else {
            return Err("No access token available.".into());
        }

        Ok(headers)
    }
}
impl Default for OAuthClient {
    fn default() -> Self {
        OAuthClient {
            client_id: env::var("DQ_CLIENT_ID".to_string()).unwrap().to_string(),
            client_secret: env::var("DQ_CLIENT_SECRET".to_string())
                .unwrap()
                .to_string(),
            token_url: OAUTH_TOKEN_URL.to_string(),
            resource_id: OAUTH_RESOURCE_ID.to_string(),
            access_token: None,
            expires_at: None,
        }
    }
}
