use crate::{error::Error, secret::S3Secret};
use aws_config::{BehaviorVersion, Region};
use aws_credential_types::{Credentials, provider::SharedCredentialsProvider};
use aws_sdk_s3::Client;
use futures::executor::block_on;
use std::{convert::TryFrom, sync::Arc};

#[derive(Clone)]
pub struct S3Endpoint {
  connection: Arc<Client>,
  bucket: String,
}

impl TryFrom<&S3Secret> for S3Endpoint {
  type Error = Error;

  fn try_from(secret: &S3Secret) -> Result<Self, Self::Error> {
    let connection = Self::connect(secret)?;
    let connection = Arc::new(connection);
    Ok(Self {
      connection,
      bucket: secret.bucket().to_string(),
    })
  }
}

impl S3Endpoint {
  pub fn connect(secret: &S3Secret) -> Result<Client, Error> {
    let region = secret.region()?;
    let keys = Credentials::from_keys(secret.access_key(), secret.secret_key(), None);
    let config_loader = aws_config::defaults(BehaviorVersion::latest())
      .credentials_provider(keys)
      .region(region.unwrap_or(Region::new("us-east-1")));

    let config_loader = if let Some(hostname) = secret.hostname() {
      config_loader.endpoint_url(hostname)
    } else {
      config_loader
    };

    let config = block_on(config_loader.load());

    // Role takes precedence over raw credential for authentication method
    if let Some(role_name) = &secret.role_name() {
      let provider = block_on(
        aws_config::sts::AssumeRoleProvider::builder(role_name.clone())
          .session_name(
            secret
              .session_name
              .clone()
              .unwrap_or("rs_transfer_session".into()),
          )
          .configure(&config)
          .build(),
      );

      let sts_config = config
        .to_builder()
        .credentials_provider(SharedCredentialsProvider::new(provider))
        .build();

      let client = aws_sdk_sts::Client::new(&sts_config);

      let req = client.get_caller_identity();
      let resp = block_on(req.send());
      match resp {
        Ok(payload) => {
          log::info!("Successfully assumed role for session.");
          log::debug!(
            "UserID :               {}",
            payload.user_id().unwrap_or_default()
          );
          log::debug!(
            "Account:               {}",
            payload.account().unwrap_or_default()
          );
          log::debug!(
            "Arn    :               {}",
            payload.arn().unwrap_or_default()
          );
        }
        Err(e) => log::error!("{e:?}"),
      }

      Ok(Client::new(&sts_config))
    } else {
      Ok(Client::new(&config))
    }
  }

  pub fn connection(&self) -> Arc<Client> {
    self.connection.clone()
  }

  pub fn bucket(&self) -> &str {
    &self.bucket
  }
}
