// Copyright 2026 The BoringSSL Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

//! TLS context builder and context type

use alloc::{boxed::Box, sync::Arc};
use core::{marker::PhantomData, mem::forget, ptr::NonNull};

use crate::{
    config::CompliancePolicy,
    connection::{Client, Server, TlsConnection, methods::HasTlsConnectionMethod},
    context::methods::HasTlsContextMethod,
    errors::Error,
};

mod credentials;
mod methods;

/// TLS or DTLS mode
pub enum TlsMode {}

/// QUIC mode
pub enum QuicMode {}

/// A collection of supported mode of operations.
pub trait SupportedMode: HasTlsContextMethod + HasTlsConnectionMethod {}

impl SupportedMode for TlsMode {}
impl SupportedMode for QuicMode {}

/// General TLS configuration
///
/// The `Mode` generic can be either [`TlsMode`] or [`QuicMode`].
/// This generic governs the kind of [`TlsConnection`] that can be constructed.
pub struct TlsContextBuilder<Mode = TlsMode> {
    ptr: NonNull<bssl_sys::SSL_CTX>,
    cert_cache: Option<Arc<CertificateCache>>,
    _p: PhantomData<fn() -> Mode>,
}

impl<M> TlsContextBuilder<M> {
    fn ptr(&self) -> *mut bssl_sys::SSL_CTX {
        self.ptr.as_ptr()
    }
}

impl<M> TlsContextBuilder<M>
where
    M: HasTlsContextMethod,
{
    fn new_inner(method: *const bssl_sys::SSL_METHOD) -> Self {
        let Some(ptr) = NonNull::new(unsafe {
            // Safety: this call only makes allocations
            bssl_sys::SSL_CTX_new(method)
        }) else {
            panic!("allocation failure")
        };
        let this = TlsContextBuilder {
            ptr,
            cert_cache: None,
            _p: PhantomData,
        };
        let rc = unsafe {
            // Safety: `ctx` is still valid
            bssl_sys::SSL_CTX_set_ex_data(
                ptr.as_ptr(),
                M::registration(),
                Box::into_raw(Box::new(methods::RustContextMethods::<M>::new())) as _,
            )
        };
        assert!(rc == 1);
        this
    }
}

/// # Make a TLS context builder
impl TlsContextBuilder<TlsMode> {
    /// Creates a new TLS context builder.
    pub fn new_tls() -> Self {
        Self::new_inner(unsafe {
            // Safety: this call returns a static immutable data
            bssl_sys::TLS_method()
        })
    }

    /// Creates a new DTLS context builder.
    pub fn new_dtls() -> Self {
        Self::new_inner(unsafe {
            // Safety: this call returns a static immutable data
            bssl_sys::DTLS_method()
        })
    }
}

/// # Configure the context through a context builder
impl<M> TlsContextBuilder<M>
where
    M: HasTlsContextMethod,
{
    /// Builds and returns the configured TLS context.
    pub fn build(mut self) -> TlsContext<M> {
        let TlsContextBuilder {
            ptr,
            ref mut cert_cache,
            ..
        } = self;
        let cert_cache = cert_cache.take();
        // We must disarm the drop activated by the builder and pass on the ownership.
        // Now `self` has no destructors to call.
        forget(self);
        TlsContext {
            ptr,
            cert_cache,
            _p: PhantomData,
        }
    }

    #[allow(unused)]
    fn get_context_methods(&mut self) -> &mut methods::RustContextMethods<M> {
        let methods = unsafe {
            // Safety: the validity of the handle `self.0` is witnessed by `self`.
            bssl_sys::SSL_CTX_get_ex_data(self.ptr(), M::registration())
        };
        if methods.is_null() {
            panic!("context method goes missing")
        }
        unsafe {
            // Safety: `methods` must be constructed by `new_inner`
            &mut *(methods as *mut methods::RustContextMethods<M>)
        }
    }
}

impl<M> Drop for TlsContextBuilder<M> {
    fn drop(&mut self) {
        unsafe {
            // Safety: self.0 is created from SSL_CTX_new so it is a valid pointer
            bssl_sys::SSL_CTX_free(self.ptr());
        }
    }
}

/// A TLS context that is finalised and can be shared across connections
pub struct TlsContext<M = TlsMode> {
    ptr: NonNull<bssl_sys::SSL_CTX>,
    cert_cache: Option<Arc<CertificateCache>>,
    _p: PhantomData<fn() -> M>,
}

impl<M> TlsContext<M> {
    pub(crate) fn ptr(&self) -> *mut bssl_sys::SSL_CTX {
        self.ptr.as_ptr()
    }
}

/// # Create new connections associated to a context
impl<M> TlsContext<M>
where
    M: HasTlsContextMethod + HasTlsConnectionMethod,
{
    fn new_connection(
        &self,
        compliance_policy: Option<CompliancePolicy>,
    ) -> NonNull<bssl_sys::SSL> {
        let conn = unsafe {
            // Safety: in this type-state, our SSL_CTX is effectively immutable,
            // so we can freely alias.
            bssl_sys::SSL_new(self.ptr())
        };
        if let Some(policy) = compliance_policy {
            unsafe {
                // Safety: `policy` is a valid enum value per construction.
                bssl_sys::SSL_set_compliance_policy(conn, policy as _);
            }
        }
        NonNull::new(conn).expect("allocation failure")
    }

    /// Make a new client-half connection inheriting the configuration of this context
    pub fn new_client_connection(
        &self,
        compliance_policy: Option<CompliancePolicy>,
    ) -> Result<TlsConnection<Client, M>, Error> {
        let conn = self.new_connection(compliance_policy);
        unsafe {
            // Safety: the connection is still valid here
            bssl_sys::SSL_set_connect_state(conn.as_ptr());
        }
        Ok(TlsConnection::from_ssl(conn))
    }

    /// Make a new server-half connection inheriting the configuration of this context
    pub fn new_server_connection(
        &self,
        compliance_policy: Option<CompliancePolicy>,
    ) -> Result<TlsConnection<Server, M>, Error> {
        let conn = self.new_connection(compliance_policy);
        unsafe {
            // Safety: the connection is still valid here
            bssl_sys::SSL_set_accept_state(conn.as_ptr());
        }
        Ok(TlsConnection::from_ssl(conn))
    }

    /// Expose the fully built BoringSSL's `SSL_CTX` pointer.
    ///
    /// # Safety
    /// - `SSL_CTX` is **not fully thread-safe**; interface with this handle only behind a mutex.
    /// - interacting with the `ex_data` of the `SSL_CTX` instance that this method returns is
    ///   **undefined behaviour**.
    /// - `self` must outlive all uses of the returned handle.
    /// - this handle must be used with functions from the BoringSSL library this crate is linked
    ///   to; otherwise, this is **undefined behaviour**.
    pub fn as_mut_ptr(&mut self) -> *mut bssl_sys::SSL_CTX {
        self.ptr()
    }
}

/// Safety: at this type state, most of the underlying context is immutable,
/// while methods on shared accesses are protected behind a mutex, so they are thread-safe.
unsafe impl<M> Send for TlsContext<M> {}
unsafe impl<M> Sync for TlsContext<M> {}

impl<M> Drop for TlsContext<M> {
    fn drop(&mut self) {
        unsafe {
            // Safety: this handle is taken from the builder, so it must be
            // live and valid.
            bssl_sys::SSL_CTX_free(self.ptr());
        }
    }
}

impl<M> Clone for TlsContext<M> {
    fn clone(&self) -> Self {
        unsafe {
            // Safety: this handle is already valid by the witness of self.
            bssl_sys::SSL_CTX_up_ref(self.ptr());
            // BoringSSL will always return success on bumping reference.
        }
        TlsContext {
            ptr: self.ptr,
            cert_cache: self.cert_cache.clone(),
            _p: PhantomData,
        }
    }
}

/// A reusable certificate cache shared across [`TlsContext`]
pub struct CertificateCache(pub(crate) NonNull<bssl_sys::CRYPTO_BUFFER_POOL>);

// Safety: `CRYPTO_BUFFER_POOL` is mutex-protected
unsafe impl Send for CertificateCache {}
unsafe impl Sync for CertificateCache {}

impl CertificateCache {
    /// Construct a new certificate cache.
    pub fn new() -> Self {
        let pool = unsafe {
            // Safety: this call does not have side-effect other than allocation
            bssl_sys::CRYPTO_BUFFER_POOL_new()
        };
        Self(NonNull::new(pool).expect("allocation failure"))
    }

    pub(crate) fn ptr(&self) -> *mut bssl_sys::CRYPTO_BUFFER_POOL {
        self.0.as_ptr()
    }
}

impl Drop for CertificateCache {
    fn drop(&mut self) {
        unsafe {
            // Safety: the validity of `self.0` is witnessed by `self`
            bssl_sys::CRYPTO_BUFFER_POOL_free(self.0.as_ptr());
        }
    }
}
