use super::{AuthenticationData, EveJwtClaims, EveJwtKey};
use crate::{
  auth::{EveJwtKeys, EveSsoMetaData},
  error::Error,
  Esi,
  EsiResult,
  LOGIN_MEMBERS,
  LOGIN_URLS,
  SSO_META_DATA_URL,
};
use jsonwebtoken::{DecodingKey, TokenData, Validation};
use oauth2::{
  AuthorizationCode,
  CsrfToken,
  EmptyExtraTokenFields,
  RefreshToken,
  Scope,
  StandardTokenResponse,
};
use reqwest::{redirect::Policy, ClientBuilder};

impl Esi {
  /**
   *
   */
  pub fn create_login_url(&self, scopes: Vec<Scope>) -> EsiResult<AuthenticationData> {
    let (eve_oauth_url, csrf_token) = self
      .token_client
      .authorize_url(CsrfToken::new_random)
      .add_scopes(scopes)
      .url();

    Ok(AuthenticationData {
      login_url: eve_oauth_url.to_string(),
      state: csrf_token.secret().to_string(),
    })
  }

  /**
   *
   */
  pub async fn get_token(
    &self,
    code: String,
  ) -> EsiResult<StandardTokenResponse<EmptyExtraTokenFields, oauth2::basic::BasicTokenType>> {
    let http_client = ClientBuilder::new().redirect(Policy::none()).build()?;
    match self
      .token_client
      .exchange_code(AuthorizationCode::new(code.to_string()))
      .request_async(&http_client)
      .await
    {
      Ok(token) => Ok(token),
      Err(err) => Err(Error::new(500, err.to_string())),
    }
  }

  /**
   *
   */
  pub async fn refresh_token(
    &self,
    refresh_token: String,
  ) -> EsiResult<StandardTokenResponse<EmptyExtraTokenFields, oauth2::basic::BasicTokenType>> {
    let http_client = ClientBuilder::new().redirect(Policy::none()).build()?;
    match self
      .token_client
      .exchange_refresh_token(&RefreshToken::new(refresh_token))
      .request_async(&http_client)
      .await
    {
      Ok(token) => Ok(token),
      Err(err) => Err(Error::new(500, err.to_string())),
    }
  }

  /**
   *
   */
  pub async fn validate_token(&self, token: &str) -> EsiResult<TokenData<EveJwtClaims>> {
    async fn get_eve_jwt_keys(esi: &Esi) -> EsiResult<EveJwtKeys> {
      let res: EveSsoMetaData = esi
        .client
        .get(SSO_META_DATA_URL)
        .send()
        .await?
        .json()
        .await?;
      let response = esi.client.get(res.jwks_uri).send().await?.json().await?;
      Ok(response)
    }

    fn select_key(keys: Vec<EveJwtKey>) -> Option<EveJwtKey> {
      for key in keys {
        if let EveJwtKey::RS256 {
          e: _,
          kid: _,
          kty: _,
          n: _,
          r#use: _,
        } = &key
        {
          return Some(key);
        }
      }
      None
    }

    let jwk_keys = get_eve_jwt_keys(self).await?;
    let jwk_key = match select_key(jwk_keys.keys) {
      Some(key) => key,
      None => return Err(Error::new(500, "Failed to find RS256 key".to_string())),
    };

    let jwk_n: String;
    let jwk_e: String;

    if let EveJwtKey::RS256 {
      e,
      kid: _,
      kty: _,
      n,
      r#use: _,
    } = jwk_key
    {
      jwk_n = n;
      jwk_e = e;
    } else {
      return Err(Error::new(500, "Failed to find RS256 key".to_string()));
    }

    let mut validation = Validation::new(jsonwebtoken::Algorithm::RS256);
    validation.set_audience(&LOGIN_MEMBERS);
    validation.set_issuer(&LOGIN_URLS);

    let decoding_key = &DecodingKey::from_rsa_components(&jwk_n, &jwk_e)?;
    let token = jsonwebtoken::decode::<EveJwtClaims>(&token, decoding_key, &validation)?;

    Ok(token)
  }
}
