//! # Ditto needs an `Identity` to start syncing with other peers.
//!
//! The various identity configurations that you can use when initializing a `Ditto` instance.
//!
//! - [`OfflinePlayground`]: Develop peer-to-peer apps with no cloud connection. This mode offers no
//!   security and must only be used for development.
//! - [`OnlineWithAuthentication`]: Run Ditto in secure production mode, logging on to Ditto Cloud
//!   or on on-premises authentication server. User permissions are centrally managed.
//! - [`OnlinePlayground`]: Test a Ditto Cloud app with weak shared token authentication
//!   ("Playground mode"). This mode is not secure and must only be used for development.
//! - [`SharedKey`]: A mode where any device is trusted provided they  know the secret key. This is
//!   a simplistic authentication model normally only suitable for private apps where users and
//!   devices are both trusted.
//! - [`Manual`]: A Ditto peer identity that was created manually rather than generated by an
//!   identity service. This is a text bundle beginning with the line `-----BEGIN DITTO
//!   IDENTITY-----`. To learn how to create and use Manual Identities please refer to Ditto's
//!   online documentation.

use std::sync::{Arc, Mutex};

use ffi_sdk::BoxedIdentityConfig;

use crate::{
    ditto::AppId,
    error::{DittoError, ErrorKind},
};
use_prelude!();

// TODO(v5): Remove pub
#[doc(hidden)]
pub mod auth;

use self::auth::LoginProvider;
pub use self::auth::{
    AuthenticationClientFeedback, DittoAuthenticationEventHandler, DittoAuthenticator,
};

// Identity is pub because it is part of the Builder's public interface (and technically
// this very path: `::dittolive_ditto::identity::Identity`).
// It is therefore Sealed to prevent downstream implementations
#[doc(hidden)]
#[deprecated(
    note = "Identity types are removed in SDK 5.0. Use `DittoConfig` with `Ditto::open(config)` \
            or `Ditto::open_sync(config)` instead."
)]
pub trait Identity: private::Sealed + Send + Sync {
    /// Consumes the identity's built IdentityConfig
    fn identity_config(&self) -> Option<BoxedIdentityConfig>;

    /// Returns whether a `DittoAuthenticator` is to be involved.
    fn involves_authenticator(&self) -> bool {
        false
    }

    /// REFACTOR(Ham): This is necessary for a couple of reasons:
    ///
    /// we only want to pass down our login provider down into the Rust core once we've had a
    /// chance to make the FFI `Ditto` object and store a reference to it in the
    /// `DittoAuthenticator`
    fn set_login_provider_with_ditto_pointer(
        &self,
        _ditto_fields: &Arc<crate::ditto::DittoFields>,
    ) {
    }

    /// Indicates if cloud sync should be enabled by default
    fn is_cloud_sync_enabled(&self) -> bool;

    /// Returns the configured URL for Auth
    fn auth_url(&self) -> Result<String, DittoError>;

    // TODO(v5): This shouldn't be necessary now that we're using the "transports behind core"
    // implementation to manage transports
    /// Returns the configured URL for WebSocket sync
    fn sync_url(&self) -> Result<String, DittoError>;

    /// Indicates whether the specific Identity type requires an offline only license token to be
    /// set.
    fn requires_offline_only_license_token(&self) -> bool;
}

/// Run Ditto in secure production mode, logging on to Ditto Cloud or an
/// on-premises authentication server. User permissions are centrally managed.
/// Sync will not work until a successful login has occurred.
///
/// The only required configuration is the application's UUID, which can be
/// found on the Ditto portal where the app is registered.
///
/// By default cloud sync is enabled. This means the SDK will sync to a Big Peer
/// in Ditto's cloud when an internet connection is available. This is
/// controlled by the `enable_cloud_sync` parameter. If `true` (default), a
/// suitable wss:// URL will be added to the [`TransportConfig`].
///
/// To prevent cloud sync, or to specify your own URL later, pass `false`.
///
/// Authentication requests are handled by Ditto's cloud by default, configured
/// in the portal at `<https://portal.ditto.live>`.
///
/// To use a different or on-premises authentication service, pass a custom
/// HTTPS base URL as the * `auth_url` parameter.
#[deprecated(
    note = "Identity types are removed in SDK 5.0. Use `DittoConfig` with `Ditto::open(config)` \
            or `Ditto::open_sync(config)` instead."
)]
pub struct OnlineWithAuthentication {
    app_id: AppId,
    enable_cloud_sync: bool,
    auth_url: String,
    identity_config: Mutex<Option<BoxedIdentityConfig>>,
    auth_event_handler: Arc<dyn 'static + DittoAuthenticationEventHandler>,
}

#[doc(hidden)]
#[deprecated(note = "use OnlinePlayground instead")]
#[allow(deprecated)]
pub type OnlinePlaygroundV2 = OnlinePlayground;

/// Test a Ditto Cloud app with a simple shared token ("Playground mode"). This
/// mode offers no security and must only be used for development. Other
/// behavior mirrors the [`OnlineWithAuthentication`] identity.
#[deprecated(
    note = "Identity types are removed in SDK 5.0. Use `DittoConfig` with `Ditto::open(config)` \
            or `Ditto::open_sync(config)` instead."
)]
pub struct OnlinePlayground {
    app_id: AppId,
    enable_cloud_sync: bool,
    auth_url: String,
    identity_config: Mutex<Option<BoxedIdentityConfig>>,
}

/// Develop peer-to-peer apps with no cloud connection. This mode offers no
/// security and must only be used for development. In this mode, any string can
/// be used as the name of the app.
#[deprecated(
    note = "Identity types are removed in SDK 5.0. Use `DittoConfig` with `Ditto::open(config)` \
            or `Ditto::open_sync(config)` instead."
)]
pub struct OfflinePlayground {
    identity_config: Mutex<Option<BoxedIdentityConfig>>,
}

/// An identity where any device is trusted provided they know the secret key.
/// This is a simplistic authentication model normally only suitable for private
/// apps where users and devices are both trusted. In this mode, any string may
/// be used as the app id.
#[deprecated(
    note = "Identity types are removed in SDK 5.0. Use `DittoConfig` with `Ditto::open(config)` \
            or `Ditto::open_sync(config)` instead."
)]
pub struct SharedKey {
    identity_config: Mutex<Option<BoxedIdentityConfig>>,
}

/// An identity where devices are manually configured with a x509 certificate
/// bundle
#[deprecated(
    note = "Identity types are removed in SDK 5.0. Use `DittoConfig` with `Ditto::open(config)` \
            or `Ditto::open_sync(config)` instead."
)]
pub struct Manual {
    identity_config: Mutex<Option<BoxedIdentityConfig>>,
}

#[allow(deprecated)]
impl OnlineWithAuthentication {
    /// Construct an OnlineWithAuthentication Identity.
    ///
    /// - `ditto_root`: The directory containing ditto local storage
    /// - `app_id`: The unique identifier of the application, usually a UUIDv4 string
    /// - `auth_event_handler`: A user created type which implements the
    ///   `DittoAuthenticationEventHandler` trait
    /// - `enable_cloud_sync`: whether cloud sync should be started by default
    /// - `custom_auth_url`: an optional custom authentication URL; default is is `cloud.ditto.live`
    pub fn new(
        _ditto_root: Arc<dyn DittoRoot>, // TODO: Remove this in a future major release (v5)
        app_id: AppId,
        auth_event_handler: impl DittoAuthenticationEventHandler + 'static,
        enable_cloud_sync: bool,
        custom_auth_url: Option<&str>,
    ) -> Result<Self, DittoError> {
        let auth_url = match custom_auth_url {
            Some(url) => url.to_owned(),
            None => app_id.default_auth_url(),
        };
        let c_app_id = char_p::new(app_id.to_string());
        let c_auth_url = char_p::new(auth_url.as_str());

        let identity_config = Mutex::new(Some(
            ffi_sdk::ditto_identity_config_make_online_with_authentication(
                c_app_id.as_ref(),
                c_auth_url.as_ref(),
            )
            .ok_or(ErrorKind::Config)?,
        ));

        Ok(OnlineWithAuthentication {
            app_id,
            enable_cloud_sync,
            auth_url,
            identity_config,
            auth_event_handler: Arc::new(auth_event_handler),
        })
    }
}

#[allow(deprecated)]
impl Identity for OnlineWithAuthentication {
    fn identity_config(&self) -> Option<BoxedIdentityConfig> {
        let mut config = self.identity_config.lock().unwrap();
        config.take()
    }

    fn involves_authenticator(&self) -> bool {
        true
    }

    fn set_login_provider_with_ditto_pointer(&self, ditto_fields: &Arc<crate::ditto::DittoFields>) {
        let authenticator = DittoAuthenticator {
            ditto_fields: Arc::downgrade(ditto_fields),
        };
        let auth_event_handler = self.auth_event_handler.retain();
        let LoginProvider(c_provider) = LoginProvider::new(auth_event_handler, authenticator);
        ffi_sdk::ditto_auth_set_login_provider(&ditto_fields.ditto, Some(c_provider));
    }

    fn is_cloud_sync_enabled(&self) -> bool {
        self.enable_cloud_sync
    }

    fn auth_url(&self) -> Result<String, DittoError> {
        Ok(self.auth_url.to_owned())
    }

    fn sync_url(&self) -> Result<String, DittoError> {
        Ok(self.app_id.default_sync_url())
    }

    fn requires_offline_only_license_token(&self) -> bool {
        false
    }
}

#[allow(deprecated)]
impl OnlinePlayground {
    /// Construct a new `OnlinePlayground` identity.
    ///
    /// - `ditto_root`: Instance of DittoRoot indicating local storage directory
    /// - `app_id`: A unique AppId which must be a valid UUIDv4
    /// - `shared_token`: A shared token used to set up the `OnlinePlayground` session. This token
    ///   is provided by the portal when setting up the application.
    /// - `enable_cloud_sync`: Should WebSocket sync with `wss://<app_id>.cloud.ditto.live` be
    ///   enabled by default. Do not enable this if you want to provide a custom sync URL later
    /// - `custom_auth_url`: An optional alternative URL for authentication requests. Defaults to `https://<app_id>.cloud.ditto.live/`
    pub fn new(
        _ditto_root: Arc<dyn DittoRoot>, // TODO: Remove this in a future major release (v5)
        app_id: AppId,
        shared_token: String,
        enable_cloud_sync: bool,
        custom_auth_url: Option<&str>,
    ) -> Result<Self, DittoError> {
        let c_app_id = app_id.to_c_string();
        let c_shared_token = char_p::new(shared_token.as_str());
        let auth_url = match custom_auth_url {
            Some(url) => url.to_owned(),
            None => app_id.default_auth_url(),
        };
        let c_auth_url = char_p::new(auth_url.as_str());
        let identity_config = Mutex::new(Some(
            ffi_sdk::ditto_identity_config_make_online_playground(
                c_app_id.as_ref(),
                c_shared_token.as_ref(),
                c_auth_url.as_ref(),
            )
            .ok_or(ErrorKind::Config)?,
        ));

        Ok(Self {
            app_id,
            enable_cloud_sync,
            auth_url,
            identity_config,
        })
    }
}

#[allow(deprecated)]
impl Identity for OnlinePlayground {
    fn identity_config(&self) -> Option<BoxedIdentityConfig> {
        let mut config = self.identity_config.lock().unwrap();
        config.take()
    }

    fn involves_authenticator(&self) -> bool {
        true
    }

    fn auth_url(&self) -> Result<String, DittoError> {
        Ok(self.auth_url.to_owned())
    }

    fn sync_url(&self) -> Result<String, DittoError> {
        Ok(self.app_id.default_sync_url())
    }

    fn is_cloud_sync_enabled(&self) -> bool {
        self.enable_cloud_sync
    }

    fn requires_offline_only_license_token(&self) -> bool {
        false
    }
}

#[allow(deprecated)]
impl OfflinePlayground {
    /// Construct an `OfflinePlayground` identity.
    ///
    /// - `ditto_root`: Location for the local database
    /// - `app_id`: A unique string identifying the App. For an `OfflinePlayground` identity this
    ///   does not need to be a UUIDv4.
    // TODO: Remove `ditto_root` in a future major release (v5)
    pub fn new(_ditto_root: Arc<dyn DittoRoot>, app_id: AppId) -> Result<Self, DittoError> {
        let c_app_id = app_id.to_c_string();
        let identity_config = Mutex::new(Some(
            ffi_sdk::ditto_identity_config_make_offline_playground(c_app_id.as_ref(), 0)
                .ok_or(ErrorKind::Config)?,
        ));
        Ok(Self { identity_config })
    }

    /// Generate an `OfflinePlayground` identity with a random AppId for testing
    pub fn random(ditto_root: Arc<dyn DittoRoot>) -> Result<Self, DittoError> {
        let app_id = AppId::generate();
        Self::new(ditto_root, app_id)
    }
}

#[allow(deprecated)]
impl Identity for OfflinePlayground {
    fn identity_config(&self) -> Option<BoxedIdentityConfig> {
        let mut config = self.identity_config.lock().unwrap();
        config.take()
    }

    fn is_cloud_sync_enabled(&self) -> bool {
        false
    }

    fn auth_url(&self) -> Result<String, DittoError> {
        Err(DittoError::new(
            ErrorKind::InvalidInput,
            "Cloud Auth is not enabled for OfflinePlayground Identities".to_string(),
        ))
    }
    fn sync_url(&self) -> Result<String, DittoError> {
        Err(DittoError::new(
            ErrorKind::InvalidInput,
            "Cloud sync is not enabled for OfflinePlayground Identities".to_string(),
        ))
    }

    fn requires_offline_only_license_token(&self) -> bool {
        true
    }
}

#[allow(deprecated)]
impl SharedKey {
    /// Construct a `SharedKey` identity.
    ///
    /// - `ditto_root`: An instance of `DittoRoot` for local storage
    /// - `app_id`: The unique UUIDv4 identifying this app; must be shared across all instances
    /// - `key_der_b64`: A pre-shared key for securing peer-to-peer communication encoded as a
    ///   Base64 string
    pub fn new(
        _ditto_root: Arc<dyn DittoRoot>, // TODO: Remove this in a future major release (v5)
        app_id: AppId,
        key_der_b64: &str,
    ) -> Result<Self, DittoError> {
        let c_app_id = app_id.to_c_string();
        let c_key_der = char_p::new(key_der_b64);
        let identity_config = Mutex::new(Some(
            ffi_sdk::ditto_identity_config_make_shared_key(
                c_app_id.as_ref(),
                c_key_der.as_ref(),
                0,
            )
            .ok_or(ErrorKind::Config)?,
        ));
        Ok(Self { identity_config })
    }
}

#[allow(deprecated)]
impl Identity for SharedKey {
    fn identity_config(&self) -> Option<BoxedIdentityConfig> {
        let mut config = self.identity_config.lock().unwrap();
        config.take()
    }

    fn auth_url(&self) -> Result<String, DittoError> {
        let msg = "SharedKey Identities don't support auth urls".to_owned();
        Err(DittoError::new(ErrorKind::Config, msg))
    }

    fn sync_url(&self) -> Result<String, DittoError> {
        let msg = "SharedKey Identities to support cloud sync".to_owned();
        Err(DittoError::new(ErrorKind::Config, msg))
    }

    fn is_cloud_sync_enabled(&self) -> bool {
        false
    }

    fn requires_offline_only_license_token(&self) -> bool {
        true
    }
}

#[allow(deprecated)]
impl Manual {
    /// Construct a `Manual` identity.
    ///
    /// A Ditto peer identity that was created manually rather than generated by an identity
    /// service. This is a text bundle beginning with the line "-----BEGIN DITTO IDENTITY-----".
    /// To learn how to create and use Manual Identities please refer to Ditto's online
    /// documentation.
    ///
    /// - `manual_identity`: a multiline string containing a Manual Identity bundle
    pub fn new(manual_identity: impl Into<String>) -> Result<Self, DittoError> {
        let manual_identity_str = char_p::new(manual_identity.into());
        let identity_config = Mutex::new(Some(
            ffi_sdk::ditto_identity_config_make_manual(manual_identity_str.as_ref())
                .ok_or(ErrorKind::Config)?,
        ));
        Ok(Self { identity_config })
    }
}

#[allow(deprecated)]
impl Identity for Manual {
    fn identity_config(&self) -> Option<BoxedIdentityConfig> {
        let mut config = self.identity_config.lock().unwrap();
        config.take()
    }

    fn auth_url(&self) -> Result<String, DittoError> {
        let msg = "Manual Identities don't support auth urls".to_owned();
        Err(DittoError::new(ErrorKind::Config, msg))
    }

    fn sync_url(&self) -> Result<String, DittoError> {
        let msg = "Manual Identities to support cloud sync".to_owned();
        Err(DittoError::new(ErrorKind::Config, msg))
    }

    fn is_cloud_sync_enabled(&self) -> bool {
        false
    }

    fn requires_offline_only_license_token(&self) -> bool {
        true
    }
}

mod private {
    use super::*;
    pub trait Sealed {}
    #[allow(deprecated)]
    impl Sealed for OnlineWithAuthentication {}
    #[allow(deprecated)]
    impl Sealed for OnlinePlayground {}
    #[allow(deprecated)]
    impl Sealed for OfflinePlayground {}
    #[allow(deprecated)]
    impl Sealed for SharedKey {}
    #[allow(deprecated)]
    impl Sealed for Manual {}
}
