// Copyright (c) 2026, Salesforce, Inc.,
// All rights reserved.
// For full license text, see the LICENSE.txt file

use crate::client::LdapClient;
use crate::error::ConfigError;
use pdk_core::classy::extract::context::ConfigureContext;
use pdk_core::classy::extract::{Extract, FromContext};
use pdk_core::classy::hl::HttpClient;
use std::convert::Infallible;
use std::rc::Rc;

/// Builder pattern implementation for creating LDAP client instances.
///
/// This struct is extracted from the PDK configuration context and provides
/// access to HTTP client infrastructure needed for LDAP authentication operations.
/// It is the primary entry point for building [`LdapClient`] instances.
///
/// # Example
///
/// ```rust,ignore
/// // LdapBuilder is injected by the PDK framework via FromContext
/// let client = ldap_builder
///     .new()
///     .server_url("ldap://ldap.example.com:389".to_string())
///     .server_user_dn("cn=admin,dc=example,dc=com".to_string())
///     .server_user_password("secret".to_string())
///     .search_base("ou=users,dc=example,dc=com".to_string())
///     .search_filter("(uid={0})".to_string())
///     .search_in_subtree()
///     .build()?;
///
/// // Policy extracts credentials — the library does not
/// let username = "user";
/// let password = "pass";
/// client.authenticate(username, password).await?;
/// ```
pub struct LdapBuilder {
    http_client: Rc<HttpClient>,
}

impl FromContext<ConfigureContext> for LdapBuilder {
    type Error = Infallible;

    fn from_context(context: &ConfigureContext) -> Result<Self, Self::Error> {
        let http_client: HttpClient = context.extract()?;

        Ok(LdapBuilder {
            http_client: Rc::new(http_client),
        })
    }
}

impl LdapBuilder {
    /// Creates a new builder instance for configuring an LDAP client.
    ///
    /// # Arguments
    ///
    /// * `config` - LDAP server and search configuration
    ///
    /// # Returns
    ///
    /// A new [`LdapBuilderInstance`] that allows further configuration before
    /// building the final [`LdapClient`].
    ///
    /// Additional methods provided by [`LdapBuilderInstance`]:
    /// - [`build`][LdapBuilderInstance::build]: Creates the final [`LdapClient`].
    #[allow(clippy::new_ret_no_self)]
    pub fn new(&self) -> LdapBuilderInstance {
        LdapBuilderInstance {
            http_client: Rc::clone(&self.http_client),
            server_url: None,
            server_user_dn: None,
            server_user_password: None,
            search_base: None,
            search_filter: None,
            search_in_subtree: false,
        }
    }
}

/// A configurable instance of an LDAP client builder.
///
/// Created by [`LdapBuilder::new`]; call [`build`](LdapBuilderInstance::build) to
/// produce the final [`LdapClient`].
pub struct LdapBuilderInstance {
    http_client: Rc<HttpClient>,
    server_url: Option<String>,
    server_user_dn: Option<String>,
    server_user_password: Option<String>,
    search_base: Option<String>,
    search_filter: Option<String>,
    search_in_subtree: bool,
}

impl LdapBuilderInstance {
    /// Sets the LDAP server URL (e.g., `ldap://ldap.example.com:389`).
    ///
    /// This field is required. The URL should include the protocol and port.
    pub fn server_url(mut self, server_url: String) -> Self {
        self.server_url = Some(server_url);
        self
    }

    /// Sets the bind DN (Distinguished Name) for the LDAP server connection.
    ///
    /// This field is required. Example: `cn=admin,dc=example,dc=com`
    pub fn server_user_dn(mut self, server_user_dn: String) -> Self {
        self.server_user_dn = Some(server_user_dn);
        self
    }

    /// Sets the bind password for the LDAP server connection.
    ///
    /// This field is required.
    pub fn server_user_password(mut self, server_user_password: String) -> Self {
        self.server_user_password = Some(server_user_password);
        self
    }

    /// Sets the search base DN for user lookups.
    ///
    /// This field is required. Example: `ou=users,dc=example,dc=com`
    pub fn search_base(mut self, search_base: String) -> Self {
        self.search_base = Some(search_base);
        self
    }

    /// Sets the search filter for user lookups.
    ///
    /// This field is required. Use `{0}` as a placeholder for the username.
    /// Example: `(uid={0})`
    pub fn search_filter(mut self, search_filter: String) -> Self {
        self.search_filter = Some(search_filter);
        self
    }

    /// Enables searching in the entire subtree below the search base.
    ///
    /// If not called, search is limited to the immediate level of the search base.
    pub fn search_in_subtree(mut self) -> Self {
        self.search_in_subtree = true;
        self
    }

    /// Builds the final [`LdapClient`] with the configured options.
    ///
    /// # Returns
    ///
    /// * `Ok(LdapClient)` - A fully configured LDAP client
    /// * `Err(LdapBuildError)` - If any error occurs during build
    pub fn build(self) -> Result<LdapClient, ConfigError> {
        if self.server_url.is_none() {
            return Err(ConfigError::MissingServerUrl);
        }
        if self.server_user_dn.is_none() {
            return Err(ConfigError::MissingBindDn);
        }
        if self.server_user_password.is_none() {
            return Err(ConfigError::MissingBindPassword);
        }
        if self.search_base.is_none() {
            return Err(ConfigError::MissingSearchBase);
        }
        if self.search_filter.is_none() {
            return Err(ConfigError::MissingSearchFilter);
        }

        Ok(LdapClient {
            http_client: self.http_client,
            server_url: self.server_url.unwrap(),
            server_user_dn: self.server_user_dn.unwrap(),
            server_user_password: self.server_user_password.unwrap(),
            search_base: self.search_base.unwrap(),
            search_filter: self.search_filter.unwrap(),
            search_in_subtree: self.search_in_subtree,
        })
    }
}
