use super::*;

use libc::c_long;

#[allow(non_camel_case_types)]
#[repr(transparent)]
struct CRYPTO_BUFFER {
    _unused: c_void,
}

#[repr(C)]
#[allow(non_camel_case_types)]
pub(super) struct SSL_QUIC_METHOD {
    set_read_secret: Option<
        unsafe extern "C" fn(
            ssl: *mut SSL,
            level: crypto::Level,
            cipher: *const SSL_CIPHER,
            secret: *const u8,
            secret_len: usize,
        ) -> c_int,
    >,

    set_write_secret: Option<
        unsafe extern "C" fn(
            ssl: *mut SSL,
            level: crypto::Level,
            cipher: *const SSL_CIPHER,
            secret: *const u8,
            secret_len: usize,
        ) -> c_int,
    >,

    add_handshake_data: Option<
        unsafe extern "C" fn(
            ssl: *mut SSL,
            level: crypto::Level,
            data: *const u8,
            len: usize,
        ) -> c_int,
    >,

    flush_flight: Option<extern "C" fn(ssl: *mut SSL) -> c_int>,

    send_alert: Option<
        extern "C" fn(ssl: *mut SSL, level: crypto::Level, alert: u8) -> c_int,
    >,
}

#[cfg(test)]
#[repr(C)]
#[allow(non_camel_case_types)]
struct SSL_PRIVATE_KEY_METHOD {
    sign: Option<
        unsafe extern "C" fn(
            ssl: *mut SSL,
            out: *mut u8,
            out_len: *mut usize,
            max_out: usize,
            signature_algorithm: u16,
            r#in: *const u8,
            in_len: usize,
        ) -> ssl_private_key_result_t,
    >,

    decrypt: Option<
        unsafe extern "C" fn(
            ssl: *mut SSL,
            out: *mut u8,
            out_len: *mut usize,
            max_out: usize,
            r#in: *const u8,
            in_len: usize,
        ) -> ssl_private_key_result_t,
    >,

    complete: Option<
        unsafe extern "C" fn(
            ssl: *mut SSL,
            out: *mut u8,
            out_len: *mut usize,
            max_out: usize,
        ) -> ssl_private_key_result_t,
    >,
}

pub(super) static QUICHE_STREAM_METHOD: SSL_QUIC_METHOD = SSL_QUIC_METHOD {
    set_read_secret: Some(set_read_secret),
    set_write_secret: Some(set_write_secret),
    add_handshake_data: Some(add_handshake_data),
    flush_flight: Some(flush_flight),
    send_alert: Some(send_alert),
};

impl Context {
    pub fn set_early_data_enabled(&mut self, _enabled: bool) {
        unsafe {
            SSL_CTX_set_early_data_enabled(
                self.as_mut_ptr(),
                i32::from(_enabled),
            );
        }
    }
}

impl Handshake {
    pub fn set_quic_early_data_context(&mut self, context: &[u8]) -> Result<()> {
        map_result(unsafe {
            SSL_set_quic_early_data_context(
                self.as_mut_ptr(),
                context.as_ptr(),
                context.len(),
            )
        })
    }

    pub fn set_session(&mut self, session: &[u8]) -> Result<()> {
        unsafe {
            let ctx = SSL_get_SSL_CTX(self.as_ptr());

            if ctx.is_null() {
                return Err(Error::TlsFail);
            }

            let session =
                SSL_SESSION_from_bytes(session.as_ptr(), session.len(), ctx);

            if session.is_null() {
                return Err(Error::TlsFail);
            }

            let rc = SSL_set_session(self.as_mut_ptr(), session);
            SSL_SESSION_free(session);

            map_result(rc)
        }
    }

    pub fn reset_early_data_reject(&mut self) {
        unsafe { SSL_reset_early_data_reject(self.as_mut_ptr()) };
    }

    pub fn curve(&self) -> Option<String> {
        let curve = unsafe {
            let curve_id = SSL_get_curve_id(self.as_ptr());
            if curve_id == 0 {
                return None;
            }

            let curve_name = SSL_get_curve_name(curve_id);
            match ffi::CStr::from_ptr(curve_name).to_str() {
                Ok(v) => v,

                Err(_) => return None,
            }
        };

        Some(curve.to_string())
    }

    pub fn sigalg(&self) -> Option<String> {
        let sigalg = unsafe {
            let sigalg_id = SSL_get_peer_signature_algorithm(self.as_ptr());
            if sigalg_id == 0 {
                return None;
            }

            let sigalg_name = SSL_get_signature_algorithm_name(sigalg_id, 1);
            match ffi::CStr::from_ptr(sigalg_name).to_str() {
                Ok(v) => v,

                Err(_) => return None,
            }
        };

        Some(sigalg.to_string())
    }

    pub fn peer_cert_chain(&self) -> Option<Vec<&[u8]>> {
        let cert_chain = unsafe {
            let chain =
                map_result_ptr(SSL_get0_peer_certificates(self.as_ptr())).ok()?;

            let num = sk_num(chain);
            if num == 0 {
                return None;
            }

            let mut cert_chain = vec![];
            for i in 0..num {
                let buffer =
                    map_result_ptr(sk_value(chain, i) as *const CRYPTO_BUFFER)
                        .ok()?;

                let out_len = CRYPTO_BUFFER_len(buffer);
                if out_len == 0 {
                    return None;
                }

                let out = CRYPTO_BUFFER_data(buffer);
                let slice = slice::from_raw_parts(out, out_len);

                cert_chain.push(slice);
            }

            cert_chain
        };

        Some(cert_chain)
    }

    pub fn peer_cert(&self) -> Option<&[u8]> {
        let peer_cert = unsafe {
            let chain =
                map_result_ptr(SSL_get0_peer_certificates(self.as_ptr())).ok()?;
            if sk_num(chain) == 0 {
                return None;
            }

            let buffer =
                map_result_ptr(sk_value(chain, 0) as *const CRYPTO_BUFFER)
                    .ok()?;

            let out_len = CRYPTO_BUFFER_len(buffer);
            if out_len == 0 {
                return None;
            }

            let out = CRYPTO_BUFFER_data(buffer);
            slice::from_raw_parts(out, out_len)
        };

        Some(peer_cert)
    }

    // Only used for testing handling of failure during key signing.
    #[cfg(test)]
    pub fn set_failing_private_key_method(&mut self) {
        extern "C" fn failing_sign(
            _ssl: *mut SSL, _out: *mut u8, _out_len: *mut usize, _max_out: usize,
            _signature_algorithm: u16, _in: *const u8, _in_len: usize,
        ) -> ssl_private_key_result_t {
            ssl_private_key_result_t::ssl_private_key_failure
        }

        extern "C" fn failing_decrypt(
            _ssl: *mut SSL, _out: *mut u8, _out_len: *mut usize, _max_out: usize,
            _in: *const u8, _in_len: usize,
        ) -> ssl_private_key_result_t {
            ssl_private_key_result_t::ssl_private_key_failure
        }

        extern "C" fn failing_complete(
            _ssl: *mut SSL, _out: *mut u8, _out_len: *mut usize, _max_out: usize,
        ) -> ssl_private_key_result_t {
            ssl_private_key_result_t::ssl_private_key_failure
        }

        static QUICHE_PRIVATE_KEY_METHOD: SSL_PRIVATE_KEY_METHOD =
            SSL_PRIVATE_KEY_METHOD {
                decrypt: Some(failing_decrypt),
                sign: Some(failing_sign),
                complete: Some(failing_complete),
            };

        unsafe {
            SSL_set_private_key_method(
                self.as_mut_ptr(),
                &QUICHE_PRIVATE_KEY_METHOD,
            );
        }
    }

    pub fn is_in_early_data(&self) -> bool {
        unsafe { SSL_in_early_data(self.as_ptr()) == 1 }
    }

    pub fn early_data_reason(&self) -> u32 {
        let reuse_reason_status =
            unsafe { SSL_get_early_data_reason(self.as_ptr()) };
        reuse_reason_status.0
    }
}

pub(super) fn get_session_bytes(session: *mut SSL_SESSION) -> Result<Vec<u8>> {
    let session_bytes = unsafe {
        let mut out: *mut u8 = ptr::null_mut();
        let mut out_len: usize = 0;

        if SSL_SESSION_to_bytes(session, &mut out, &mut out_len) == 0 {
            return Err(Error::TlsFail);
        }
        let session_bytes = slice::from_raw_parts(out, out_len).to_vec();
        OPENSSL_free(out as *mut c_void);
        session_bytes
    };

    Ok(session_bytes)
}
pub(super) const TLS_ERROR: c_int = 3;

#[allow(non_camel_case_types)]
#[repr(transparent)]
#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)]
pub struct ssl_early_data_reason_t(pub ::std::os::raw::c_uint);
extern "C" {
    // SSL_METHOD specific for boringssl.
    pub(super) fn SSL_CTX_set_tlsext_ticket_keys(
        ctx: *mut SSL_CTX, key: *const u8, key_len: usize,
    ) -> c_int;
    fn SSL_CTX_set_early_data_enabled(ctx: *mut SSL_CTX, enabled: i32);

    pub(super) fn SSL_CTX_set_session_cache_mode(
        ctx: *mut SSL_CTX, mode: c_int,
    ) -> c_int;
    pub(super) fn SSL_get_ex_new_index(
        argl: c_long, argp: *const c_void, unused: *const c_void,
        dup_unused: *const c_void, free_func: *const c_void,
    ) -> c_int;

    fn SSL_get_curve_id(ssl: *const SSL) -> u16;
    fn SSL_get_curve_name(curve: u16) -> *const c_char;

    fn SSL_get_peer_signature_algorithm(ssl: *const SSL) -> u16;
    fn SSL_get_signature_algorithm_name(
        sigalg: u16, include_curve: i32,
    ) -> *const c_char;

    fn SSL_get0_peer_certificates(ssl: *const SSL) -> *const STACK_OF;

    pub(super) fn SSL_set_min_proto_version(ssl: *mut SSL, version: u16)
        -> c_int;

    pub(super) fn SSL_set_max_proto_version(ssl: *mut SSL, version: u16)
        -> c_int;

    pub(super) fn SSL_set_tlsext_host_name(
        ssl: *mut SSL, name: *const c_char,
    ) -> c_int;

    fn SSL_set_quic_early_data_context(
        ssl: *mut SSL, context: *const u8, context_len: usize,
    ) -> c_int;

    #[cfg(test)]
    fn SSL_set_private_key_method(
        ssl: *mut SSL, key_method: *const SSL_PRIVATE_KEY_METHOD,
    );

    fn SSL_reset_early_data_reject(ssl: *mut SSL);

    fn SSL_in_early_data(ssl: *const SSL) -> c_int;

    fn SSL_get_early_data_reason(ssl: *const SSL) -> ssl_early_data_reason_t;

    fn SSL_SESSION_to_bytes(
        session: *const SSL_SESSION, out: *mut *mut u8, out_len: *mut usize,
    ) -> c_int;

    fn SSL_SESSION_from_bytes(
        input: *const u8, input_len: usize, ctx: *const SSL_CTX,
    ) -> *mut SSL_SESSION;

    // STACK_OF

    fn sk_num(stack: *const STACK_OF) -> usize;

    fn sk_value(stack: *const STACK_OF, idx: usize) -> *mut c_void;

    // CRYPTO_BUFFER

    fn CRYPTO_BUFFER_len(buffer: *const CRYPTO_BUFFER) -> usize;

    fn CRYPTO_BUFFER_data(buffer: *const CRYPTO_BUFFER) -> *const u8;
}
