//! Connect to Minecraft servers.

#[cfg(feature = "online-mode")]
pub mod microsoft;
pub mod offline;

use std::{fmt::Debug, ops::Deref, pin::Pin, sync::Arc};

#[cfg(feature = "online-mode")]
use azalea_auth::sessionserver::ClientSessionServerError;
use bevy_ecs::component::Component;
use uuid::Uuid;

/// Something that can join Minecraft servers.
///
/// By default, Azalea only supports either authentication with Microsoft
/// (online-mode), or no authentication at all (offline-mode). If you'd like to
/// do authentication in some other way, consider looking at [`AccountTrait`].
///
/// To join a server using this account, you can either use
/// [`StartJoinServerEvent`] or `azalea::ClientBuilder`.
///
/// Note that `Account` is also an ECS component that's present on our client
/// entities.
///
/// # Examples
///
/// ```rust,no_run
/// # use azalea_client::Account;
/// #
/// # #[tokio::main]
/// # async fn main() {
/// let account = Account::microsoft("example@example.com").await;
/// // or Account::offline("example");
/// # }
/// ```
///
/// [`StartJoinServerEvent`]: crate::join::StartJoinServerEvent
/// [`azalea::ClientBuilder`]: https://docs.rs/azalea/latest/azalea/struct.ClientBuilder.html
#[derive(Clone, Component, Debug)]
pub struct Account(Arc<dyn AccountTrait>);

impl Account {
    #[deprecated = "moved to `uuid()`."]
    pub fn uuid_or_offline(&self) -> Uuid {
        self.uuid()
    }
}

pub(crate) type BoxFuture<'a, T> = Pin<Box<dyn Future<Output = T> + Send + 'a>>;

/// A trait that all types of accounts implement.
///
/// This can be used, for example, to join servers with a custom authentication
/// server.
///
/// Anything that implements [`AccountTrait`] can be converted to an [`Account`]
/// with `.into()`.
///
/// Consider reading the source code of
/// [`MicrosoftAccount`](microsoft::MicrosoftAccount) for an example of how to
/// implement this.
pub trait AccountTrait: Send + Sync + Debug {
    /// Returns the Minecraft username of the account.
    fn username(&self) -> &str;
    /// Returns the unique identifier for this player.
    ///
    /// For offline-mode accounts, this UUID is generated by calling
    /// [`azalea_crypto::offline::generate_uuid`].
    fn uuid(&self) -> Uuid;

    /// The access token for authentication.
    ///
    /// You can obtain one of these manually from `azalea-auth`.
    fn access_token(&self) -> Option<String>;

    /// Refreshes the access token for this account.
    #[cfg(feature = "online-mode")]
    fn refresh(&self) -> BoxFuture<'_, Result<(), azalea_auth::AuthError>> {
        Box::pin(async { Ok(()) })
    }
    /// Refreshes the access token for this account.
    ///
    /// The `online-mode` feature is disabled, so this won't do anything.
    #[cfg(not(feature = "online-mode"))]
    fn refresh(&self) -> BoxFuture<'_, Result<(), ()>> {
        Box::pin(async { Ok(()) })
    }

    #[cfg(feature = "online-mode")]
    fn certs(&self) -> Option<azalea_auth::certs::Certificates> {
        None
    }
    /// Override the chat signing certificates for this account.
    ///
    /// You can get the certificates needed for this from
    /// [`azalea_auth::certs::fetch_certificates`]. You typically don't need to
    /// call this yourself, as Azalea will do it for you.
    ///
    /// For accounts that don't support signing (i.e. offline-mode), this won't
    /// do anything.
    #[cfg(feature = "online-mode")]
    fn set_certs(&self, certs: azalea_auth::certs::Certificates) {
        let _ = certs;
    }

    /// Typically used to tell Mojang's sessionserver that we are going to join
    /// a server.
    ///
    /// This must be implemented for accounts that can join online-mode servers.
    ///
    /// This function is called internally by Azalea when the account tries to
    /// join a server, but only if [`AccountTrait::access_token`] is `Some`.
    #[cfg(feature = "online-mode")]
    fn join<'a>(
        &'a self,
        public_key: &'a [u8],
        private_key: &'a [u8; 16],
        server_id: &'a str,
        proxy: Option<reqwest::Proxy>,
    ) -> BoxFuture<'a, Result<(), ClientSessionServerError>> {
        let _ = (public_key, private_key, server_id, proxy);
        Box::pin(async { Ok(()) })
    }
    /// Typically used to tell Mojang's sessionserver that we are going to join
    /// a server.
    ///
    /// The `online-mode` feature is disabled, so this won't do anything.
    #[cfg(not(feature = "online-mode"))]
    fn join(
        &self,
        public_key: &[u8],
        private_key: &[u8; 16],
        server_id: &str,
        proxy: Option<()>,
    ) -> BoxFuture<'_, Result<(), ()>> {
        let _ = (public_key, private_key, server_id, proxy);
        Box::pin(async { Ok(()) })
    }
}
impl<T: AccountTrait + 'static> From<T> for Account {
    fn from(value: T) -> Self {
        Account(Arc::new(value))
    }
}
impl Deref for Account {
    type Target = dyn AccountTrait;

    fn deref(&self) -> &Self::Target {
        &*self.0
    }
}
