//! GmSSL FFI 绑定
//!
//!
//!

// #[allow()]
pub mod sm2 {
    use std::fmt::Display;

    use anyhow::Context;

    #[repr(C)]
    #[derive(Debug)]

    pub struct SM2_POINT {
        pub x: [u8; 32],
        pub y: [u8; 32],
    }

    impl Display for SM2_POINT {
        fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
            unsafe {
                let mut out = [0; 65];
                sm2_point_to_uncompressed_octets(self, &mut out);
                let s = hex::encode(out);
                f.write_str(&s)
            }
        }
    }

    impl TryFrom<&[u8]> for SM2_POINT {
        type Error = anyhow::Error;

        fn try_from(value: &[u8]) -> Result<Self, Self::Error> {
            unsafe {
                let mut result = std::mem::zeroed();
                let r = sm2_point_from_octets(&mut result, value.as_ptr(), value.len());
                if r == 1 {
                    Ok(result)
                } else {
                    Err(anyhow::Error::msg(format!(
                        "FFI sm2_point_from_octets Error {}",
                        r
                    )))
                }
            }
        }
    }

    impl TryFrom<&str> for SM2_POINT {
        type Error = anyhow::Error;

        fn try_from(value: &str) -> Result<Self, Self::Error> {
            let value = hex::decode(value).context("SM2_POINT try_from str Error")?;
            TryFrom::<&[u8]>::try_from(&value)
        }
    }

    #[repr(C)]
    #[derive(Debug)]
    pub struct SM2_KEY {
        pub public_key: SM2_POINT,
        pub private_key: [u8; 32],
    }

    pub enum PassOrder {
        C1C2C3,
        C1C3C2,
    }

    impl Display for SM2_KEY {
        fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
            f.write_fmt(format_args!(
                "public_key:{}\nprivate_key:{}",
                self.public_key,
                hex::encode(self.private_key)
            ))
        }
    }

    impl SM2_KEY {
        /// Note: 会生成public_key
        pub fn set_sk(&mut self, s: &str) -> usize {
            let r = hex::decode(s).unwrap();
            unsafe { sm2_key_set_private_key(self, r.as_ptr() as _) }
        }

        /// Note: 会清除private_key
        pub fn set_pk(&mut self, point: SM2_POINT) -> usize {
            unsafe { sm2_key_set_public_key(self, &point) }
        }

        pub fn get_sk(&self) -> String {
            hex::encode(self.private_key)
        }

        pub fn get_pk(&self, compressed: bool) -> String {
            unsafe {
                let mut out = Vec::with_capacity(65);
                if compressed {
                    sm2_point_to_compressed_octets(&self.public_key, out.as_mut_ptr() as _);
                    out.set_len(33)
                } else {
                    sm2_point_to_uncompressed_octets(&self.public_key, out.as_mut_ptr() as _);
                    out.set_len(65)
                };

                hex::encode(out)
            }
        }

        pub fn key_generate() -> Self {
            unsafe {
                let mut result = std::mem::zeroed();
                let r = sm2_key_generate(&mut result);
                if r != 1 {
                    panic!("key_generate Error")
                }
                result
            }
        }

        pub fn encrypt(&self, data: impl AsRef<[u8]>, order: PassOrder) -> anyhow::Result<Vec<u8>> {
            unsafe {
                let data = data.as_ref();
                let len = data.len();
                if !(1..=255).contains(&len) {
                    return Err(anyhow::Error::msg("sm2_encrypt error 数据长度错误"));
                }

                let mut out = std::mem::zeroed();
                let ffi_r = sm2_do_encrypt(self, data.as_ptr(), len, &mut out);
                if ffi_r != 1 {
                    return Err(anyhow::Error::msg("sm2_do_encrypt error "));
                }
                // let point = [0x4]
                //     .into_iter()
                //     .chain(out.point.x.iter().copied())
                //     .chain(out.point.y.iter().copied());
                let mut point = [0; 65];
                sm2_point_to_uncompressed_octets(&out.point, &mut point);
                let result = match order {
                    PassOrder::C1C2C3 => point
                        .into_iter()
                        .chain(out.ciphertext[..out.ciphertext_size as _].iter().copied())
                        .chain(out.hash.iter().copied())
                        .collect(),
                    PassOrder::C1C3C2 => point
                        .into_iter()
                        .chain(out.hash.iter().copied())
                        .chain(out.ciphertext[..out.ciphertext_size as _].iter().copied())
                        .collect(),
                };
                Ok(result)
            }
        }

        pub fn decrypt(&self, data: impl AsRef<[u8]>, order: PassOrder) -> anyhow::Result<Vec<u8>> {
            unsafe {
                let mut data = data.as_ref();
                let (point, pass, hash) = {
                    let point = {
                        let size = if data.len() > 33 && (data[0] == 0x3 || data[0] == 0x2) {
                            33
                        } else if data.len() > 65 && data[0] == 0x4 {
                            65
                        } else if data.len() > 64 {
                            64
                        } else {
                            return Err(anyhow::Error::msg("decrypt C1 value Error"));
                        };
                        let mut p = std::mem::zeroed();
                        let ffi_r = if size == 64 {
                            let mut _in = Vec::with_capacity(65);
                            _in.push(0x4);
                            _in.extend(data[..size].iter());
                            sm2_point_from_octets(&mut p, _in.as_ptr(), 65)
                        } else {
                            sm2_point_from_octets(&mut p, data[..size].as_ptr(), size)
                        };
                        if ffi_r != 1 {
                            return Err(anyhow::Error::msg(format!(
                                "decrypt Error sm2_point_from_octets: {}",
                                ffi_r
                            )));
                        }
                        data = &data[size..];
                        p
                    };

                    if data.len() < 32 {
                        return Err(anyhow::Error::msg("decrypt C2/C3 value Error"));
                    }

                    let (pass, hash) = match order {
                        PassOrder::C1C2C3 => (&data[..data.len() - 32], &data[data.len() - 32..]),
                        PassOrder::C1C3C2 => (&data[32..], &data[..32]),
                    };
                    (point, pass, hash)
                };
                let mut _in: SM2_CIPHERTEXT = std::mem::zeroed();
                _in.ciphertext_size = pass.len() as _;
                _in.ciphertext[..pass.len()].clone_from_slice(pass);
                _in.point = point;
                _in.hash.clone_from_slice(hash);
                let mut out = Vec::with_capacity(255);
                let mut outlen = 0;
                let ffi_r = sm2_do_decrypt(self, &_in, out.as_mut_ptr(), &mut outlen);
                if ffi_r != 1 {
                    return Err(anyhow::Error::msg("sm2_do_decrypt Error"));
                }
                out.set_len(outlen);
                Ok(out)
            }
        }
    }

    #[repr(C)]
    #[derive(Debug)]
    pub struct SM2_CIPHERTEXT {
        pub point: SM2_POINT,
        pub hash: [u8; 32],
        pub ciphertext_size: u8,
        pub ciphertext: [u8; 255],
    }

    extern "C" {
        pub fn sm2_key_generate(key: *mut SM2_KEY) -> isize;
        pub fn sm2_encrypt(
            key: *const SM2_KEY,
            _in: *const u8,
            inlen: usize,
            out: *mut u8,
            outlen: *mut usize,
        ) -> isize;
        pub fn sm2_decrypt(
            key: *const SM2_KEY,
            _in: *const u8,
            inlen: usize,
            out: *mut u8,
            outlen: *mut usize,
        ) -> isize;
        pub fn sm2_key_set_private_key(key: *mut SM2_KEY, private_key: *const [u8; 32]) -> usize;
        pub fn sm2_key_set_public_key(key: *mut SM2_KEY, point: *const SM2_POINT) -> usize;
        /// Note: 源码中未使用 _gmssl_export
        pub fn sm2_do_encrypt(
            key: *const SM2_KEY,
            _in: *const u8,
            inlen: usize,
            out: *mut SM2_CIPHERTEXT,
        ) -> usize;
        /// Note: 源码中未使用 _gmssl_export
        pub fn sm2_do_decrypt(
            key: *const SM2_KEY,
            _in: *const SM2_CIPHERTEXT,
            out: *mut u8,
            outlen: *mut usize,
        ) -> usize;
        /// Note: 源码中未使用 _gmssl_export
        fn sm2_point_from_octets(p: *mut SM2_POINT, _in: *const u8, inlen: usize) -> usize;
        /// Note: 源码中未使用 _gmssl_export
        fn sm2_point_to_compressed_octets(p: *const SM2_POINT, out: *mut [u8; 33]);
        /// Note: 源码中未使用 _gmssl_export
        fn sm2_point_to_uncompressed_octets(p: *const SM2_POINT, out: *mut [u8; 65]);
    }

    #[cfg(test)]
    mod tests {

        use super::*;

        #[test]
        fn test_de() {
            let mut key = SM2_KEY::key_generate();
            let r = key.set_sk("caaba4e6910b1b510e9347e004bc115cb5fb90a16ddde3cc0566522e946d0368");
            println!("key.set_sk {}", r);
            let data = hex::decode("0485bae5445c8d2896bf09b84a69db998daad510489dc86ae1b14e2dff0bb5c0ab0cbe66517eefa55e8009a09679f9ceebf705b1f4796e52daa1fad1b3cb6f56e3cf7ef35ac7ebda3a4e6e5e8108fc228981323205bcfbf49b7337fc317d7b2f4213d36744e27dd27406ed66a86648ebdf9cfa9051742d78addb638f6a3ec0e0847702d1d1e1cc611a94e3acbfb43ec8b87f8e0cc2668e319f617750e1ea392a43cf5e4087a8eb833f5091d5f544301c134b93afcbf905c5e080a793450b391a1ad61d9226603e1c68cf1235ba64721574f1c08ff84b37").unwrap();
            let r = key.decrypt(data, PassOrder::C1C3C2).unwrap();
            let r = String::from_utf8(r).unwrap();
            println!("key.decrypt {}", r);

        }

        #[test]
        fn test_key_generate() {
            let key = SM2_KEY::key_generate();
            let pk = key.get_pk(false);
            let sk = key.get_sk();
            println!("\npk: {pk}\nsk: {sk}")
        }

    }
}
