use std::path::PathBuf;

use serde::{Deserialize, Serialize};
use serde_with::{base64::Base64, serde_as};
use url::Url;

/// Configuration options required to initialize a [`Ditto`] instance.
///
/// # Example
///
/// ```
/// # use dittolive_ditto::prelude::*;
/// # fn example(database_id: &str) {
/// let connect = DittoConfigConnect::Server {
///     url: "REPLACE_ME_WITH_YOUR_SERVER_URL".parse().unwrap(),
/// };
/// let connect = DittoConfigConnect::SmallPeersOnly {
///     private_key: Some("REPLACE_ME_WITH_YOUR_PRIVATE_KEY".as_bytes().to_vec()),
/// };
/// let connect = DittoConfigConnect::SmallPeersOnly { private_key: None };
/// let config = DittoConfig::new(database_id, connect);
/// # }
/// ```
///
/// [`Ditto`]: crate::Ditto
#[non_exhaustive]
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DittoConfig {
    /// An ID used to distinguish between Ditto database instantations.
    ///
    /// **Note**: This was formerly known as `appId`.
    pub database_id: String,
    /// The connection configuration this device uses to connect to a Ditto mesh.
    pub connect: DittoConfigConnect,
    /// The directory where Ditto will store its data.
    #[serde(skip_serializing_if = "Option::is_none")]
    pub persistence_directory: Option<PathBuf>,
    #[doc(hidden)]
    pub experimental: DittoConfigExperimental,
}

impl DittoConfig {
    /// Create a new [`DittoConfig`] instance from a database ID and connection configuration.
    pub fn new<S: Into<String>>(database_id: S, connect: DittoConfigConnect) -> Self {
        DittoConfig {
            database_id: database_id.into(),
            connect,
            persistence_directory: None,
            experimental: Default::default(),
        }
    }

    /// Optionally specify a custom persistence directory.
    pub fn with_persistence_directory<P: Into<PathBuf>>(mut self, path: P) -> Self {
        self.persistence_directory = Some(path.into());
        self
    }
}

/// Specifies the connection mechanism to be used by the [`Ditto`] instance.
///
/// # Example
///
/// ```
/// use dittolive_ditto::prelude::*;
/// let connect_config = DittoConfigConnect::Server {
///     url: "https://example.com/your-server-url".parse().unwrap(),
/// };
/// let connect_config = DittoConfigConnect::SmallPeersOnly {
///     private_key: Some("REPLACE_ME_WITH_YOUR_PRIVATE_KEY".bytes().collect()),
/// };
/// let connect_config = DittoConfigConnect::SmallPeersOnly { private_key: None };
/// ```
///
/// [`Ditto`]: crate::Ditto
#[serde_as]
#[non_exhaustive]
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(tag = "type", rename_all = "snake_case")]
pub enum DittoConfigConnect {
    /// Connect to a Ditto "Big Peer" using the provided URL.
    ///
    /// When using `Server` mode, you _must_ provide an authentication expiration
    /// handler using [`ditto.auth()?.set_expiration_handler(...)`][0].
    ///
    /// [0]: crate::prelude::DittoAuthenticator::set_expiration_handler
    Server {
        /// The "Auth URL" from the Ditto Portal, if using a Ditto-hosted Big Peer.
        ///
        /// If using a self-hosted Big Peer, provide your custom authentication URL.
        ///
        /// # Example
        ///
        /// ```text
        /// // Where this subdomain is your Ditto `databaseId` (formerly `AppId`)
        /// https://00000000-0000-0000-0000-000000000000.cloud.ditto.live
        /// ```
        url: Url,
    },
    /// Connect only to other Ditto small peers, using the provided private key.
    ///
    /// If no private key is provided, sync traffic will be UNENCRYPTED. This
    /// mode should NOT be used in production, only for development and testing
    /// purposes.
    SmallPeersOnly {
        #[serde_as(as = "Option<Base64>")]
        #[serde(skip_serializing_if = "Option::is_none")]
        private_key: Option<Vec<u8>>,
    },
}

#[doc(hidden)]
#[non_exhaustive]
#[derive(Debug, Default, Clone, Serialize, Deserialize)]
pub struct DittoConfigExperimental {
    #[serde(skip_serializing_if = "Option::is_none")]
    pub passphrase: Option<String>,
}
