//! quicproquo-mobile — C FFI layer for mobile integration.
//!
//! Provides a minimal C API that proves QUIC connection migration works
//! (wifi → cellular handoff without message loss). Each FFI function uses
//! `runtime.block_on(local.run_until(...))` to satisfy capnp-rpc's `!Send`
//! requirement.
//!
//! # Safety
//!
//! All FFI functions are `unsafe extern "C"` — callers must ensure pointers
//! are valid and buffers are correctly sized.

use std::ffi::c_char;
use std::net::SocketAddr;
use std::sync::Arc;

use quinn::Endpoint;
use tokio::runtime::Runtime;

/// Opaque handle returned by `qnpc_connect`.
#[allow(dead_code)]
pub struct MobileHandle {
    runtime: Runtime,
    endpoint: Endpoint,
    connection: Option<quinn::Connection>,
    server_addr: SocketAddr,
    server_name: String,
}

/// Status codes returned by FFI functions.
#[repr(C)]
pub enum QnpcStatus {
    Ok = 0,
    Error = 1,
    Timeout = 2,
    NotConnected = 3,
}

/// Connect to a quicproquo server. Returns a handle pointer (null on failure).
///
/// # Safety
/// `server_addr` and `server_name` must be valid null-terminated C strings.
#[no_mangle]
pub unsafe extern "C" fn qnpc_connect(
    server_addr: *const c_char,
    server_name: *const c_char,
) -> *mut MobileHandle {
    let addr_str = match std::ffi::CStr::from_ptr(server_addr).to_str() {
        Ok(s) => s,
        Err(_) => return std::ptr::null_mut(),
    };
    let name_str = match std::ffi::CStr::from_ptr(server_name).to_str() {
        Ok(s) => s,
        Err(_) => return std::ptr::null_mut(),
    };

    let addr: SocketAddr = match addr_str.parse() {
        Ok(a) => a,
        Err(_) => return std::ptr::null_mut(),
    };

    let rt = match Runtime::new() {
        Ok(r) => r,
        Err(_) => return std::ptr::null_mut(),
    };

    let result = rt.block_on(async {
        connect_inner(addr, name_str).await
    });

    match result {
        Ok((endpoint, connection)) => {
            let handle = Box::new(MobileHandle {
                runtime: rt,
                endpoint,
                connection: Some(connection),
                server_addr: addr,
                server_name: name_str.to_string(),
            });
            Box::into_raw(handle)
        }
        Err(_) => std::ptr::null_mut(),
    }
}

async fn connect_inner(
    addr: SocketAddr,
    server_name: &str,
) -> anyhow::Result<(Endpoint, quinn::Connection)> {
    let _ = rustls::crypto::ring::default_provider().install_default();

    let crypto = build_client_tls_config()?;

    let mut client_config = quinn::ClientConfig::new(Arc::new(
        quinn::crypto::rustls::QuicClientConfig::try_from(crypto)
            .map_err(|e| anyhow::anyhow!("QUIC client config: {e}"))?,
    ));

    let mut transport = quinn::TransportConfig::default();
    transport.max_idle_timeout(Some(
        std::time::Duration::from_secs(120)
            .try_into()
            .expect("120s valid"),
    ));
    client_config.transport_config(Arc::new(transport));

    let mut endpoint = Endpoint::client("0.0.0.0:0".parse().unwrap())?;
    endpoint.set_default_client_config(client_config);

    let connection = endpoint.connect(addr, server_name)?.await?;
    Ok((endpoint, connection))
}

/// Simulate QUIC connection migration by rebinding the endpoint to a new local address.
///
/// This is the key proof-of-concept: after rebind, the QUIC connection survives
/// and messages continue flowing without loss.
///
/// # Safety
/// `handle` must be a valid pointer from `qnpc_connect`.
#[no_mangle]
pub unsafe extern "C" fn qnpc_migrate(
    handle: *mut MobileHandle,
    new_port: u16,
) -> QnpcStatus {
    let handle = match handle.as_mut() {
        Some(h) => h,
        None => return QnpcStatus::Error,
    };

    let new_addr: SocketAddr = format!("0.0.0.0:{new_port}").parse().unwrap();
    let socket = match std::net::UdpSocket::bind(new_addr) {
        Ok(s) => s,
        Err(_) => return QnpcStatus::Error,
    };

    match handle.endpoint.rebind(socket) {
        Ok(_) => QnpcStatus::Ok,
        Err(_) => QnpcStatus::Error,
    }
}

/// Disconnect and free the handle.
///
/// # Safety
/// `handle` must be a valid pointer from `qnpc_connect`, and must not be used after this call.
#[no_mangle]
pub unsafe extern "C" fn qnpc_disconnect(handle: *mut MobileHandle) {
    if !handle.is_null() {
        let handle = Box::from_raw(handle);
        if let Some(conn) = &handle.connection {
            conn.close(0u32.into(), b"disconnect");
        }
        drop(handle);
    }
}

// ── TLS configuration ───────────────────────────────────────────────────────

/// Build the rustls `ClientConfig` for the QUIC transport.
///
/// Without the `insecure-dev` feature, this uses the platform's native root
/// certificates for server verification. With `insecure-dev` enabled, all
/// certificate verification is skipped (MITM-vulnerable — dev/testing only).
fn build_client_tls_config() -> anyhow::Result<rustls::ClientConfig> {
    #[cfg(feature = "insecure-dev")]
    {
        Ok(rustls::ClientConfig::builder()
            .dangerous()
            .with_custom_certificate_verifier(Arc::new(SkipServerVerification))
            .with_no_client_auth())
    }
    #[cfg(not(feature = "insecure-dev"))]
    {
        let mut root_store = rustls::RootCertStore::empty();
        root_store.extend(webpki_roots::TLS_SERVER_ROOTS.iter().cloned());
        Ok(rustls::ClientConfig::builder()
            .with_root_certificates(root_store)
            .with_no_client_auth())
    }
}

#[cfg(feature = "insecure-dev")]
#[derive(Debug)]
struct SkipServerVerification;

#[cfg(feature = "insecure-dev")]
impl rustls::client::danger::ServerCertVerifier for SkipServerVerification {
    fn verify_server_cert(
        &self,
        _end_entity: &rustls::pki_types::CertificateDer<'_>,
        _intermediates: &[rustls::pki_types::CertificateDer<'_>],
        _server_name: &rustls::pki_types::ServerName<'_>,
        _ocsp_response: &[u8],
        _now: rustls::pki_types::UnixTime,
    ) -> Result<rustls::client::danger::ServerCertVerified, rustls::Error> {
        Ok(rustls::client::danger::ServerCertVerified::assertion())
    }

    fn verify_tls12_signature(
        &self,
        _message: &[u8],
        _cert: &rustls::pki_types::CertificateDer<'_>,
        _dss: &rustls::DigitallySignedStruct,
    ) -> Result<rustls::client::danger::HandshakeSignatureValid, rustls::Error> {
        Ok(rustls::client::danger::HandshakeSignatureValid::assertion())
    }

    fn verify_tls13_signature(
        &self,
        _message: &[u8],
        _cert: &rustls::pki_types::CertificateDer<'_>,
        _dss: &rustls::DigitallySignedStruct,
    ) -> Result<rustls::client::danger::HandshakeSignatureValid, rustls::Error> {
        Ok(rustls::client::danger::HandshakeSignatureValid::assertion())
    }

    fn supported_verify_schemes(&self) -> Vec<rustls::SignatureScheme> {
        vec![
            rustls::SignatureScheme::ECDSA_NISTP256_SHA256,
            rustls::SignatureScheme::ECDSA_NISTP384_SHA384,
            rustls::SignatureScheme::ED25519,
            rustls::SignatureScheme::RSA_PSS_SHA256,
            rustls::SignatureScheme::RSA_PSS_SHA384,
            rustls::SignatureScheme::RSA_PSS_SHA512,
            rustls::SignatureScheme::RSA_PKCS1_SHA256,
            rustls::SignatureScheme::RSA_PKCS1_SHA384,
            rustls::SignatureScheme::RSA_PKCS1_SHA512,
        ]
    }
}

// ── Tests ───────────────────────────────────────────────────────────────────

#[cfg(test)]
mod tests {
    use super::*;
    use std::net::UdpSocket;

    /// Test-only insecure verifier (always available in test builds).
    #[derive(Debug)]
    struct TestSkipServerVerification;

    impl rustls::client::danger::ServerCertVerifier for TestSkipServerVerification {
        fn verify_server_cert(
            &self,
            _end_entity: &rustls::pki_types::CertificateDer<'_>,
            _intermediates: &[rustls::pki_types::CertificateDer<'_>],
            _server_name: &rustls::pki_types::ServerName<'_>,
            _ocsp_response: &[u8],
            _now: rustls::pki_types::UnixTime,
        ) -> Result<rustls::client::danger::ServerCertVerified, rustls::Error> {
            Ok(rustls::client::danger::ServerCertVerified::assertion())
        }

        fn verify_tls12_signature(
            &self,
            _message: &[u8],
            _cert: &rustls::pki_types::CertificateDer<'_>,
            _dss: &rustls::DigitallySignedStruct,
        ) -> Result<rustls::client::danger::HandshakeSignatureValid, rustls::Error> {
            Ok(rustls::client::danger::HandshakeSignatureValid::assertion())
        }

        fn verify_tls13_signature(
            &self,
            _message: &[u8],
            _cert: &rustls::pki_types::CertificateDer<'_>,
            _dss: &rustls::DigitallySignedStruct,
        ) -> Result<rustls::client::danger::HandshakeSignatureValid, rustls::Error> {
            Ok(rustls::client::danger::HandshakeSignatureValid::assertion())
        }

        fn supported_verify_schemes(&self) -> Vec<rustls::SignatureScheme> {
            vec![
                rustls::SignatureScheme::ECDSA_NISTP256_SHA256,
                rustls::SignatureScheme::ECDSA_NISTP384_SHA384,
                rustls::SignatureScheme::ED25519,
                rustls::SignatureScheme::RSA_PSS_SHA256,
                rustls::SignatureScheme::RSA_PSS_SHA384,
                rustls::SignatureScheme::RSA_PSS_SHA512,
                rustls::SignatureScheme::RSA_PKCS1_SHA256,
                rustls::SignatureScheme::RSA_PKCS1_SHA384,
                rustls::SignatureScheme::RSA_PKCS1_SHA512,
            ]
        }
    }

    /// Connect to a test server using the insecure cert verifier.
    async fn test_connect_inner(
        addr: SocketAddr,
        server_name: &str,
    ) -> anyhow::Result<(Endpoint, quinn::Connection)> {
        let _ = rustls::crypto::ring::default_provider().install_default();

        let crypto = rustls::ClientConfig::builder()
            .dangerous()
            .with_custom_certificate_verifier(Arc::new(TestSkipServerVerification))
            .with_no_client_auth();

        let mut client_config = quinn::ClientConfig::new(Arc::new(
            quinn::crypto::rustls::QuicClientConfig::try_from(crypto)
                .map_err(|e| anyhow::anyhow!("QUIC client config: {e}"))?,
        ));

        let mut transport = quinn::TransportConfig::default();
        transport.max_idle_timeout(Some(
            std::time::Duration::from_secs(120)
                .try_into()
                .expect("120s valid"),
        ));
        client_config.transport_config(Arc::new(transport));

        let mut endpoint = Endpoint::client("0.0.0.0:0".parse().unwrap())?;
        endpoint.set_default_client_config(client_config);

        let connection = endpoint.connect(addr, server_name)?.await?;
        Ok((endpoint, connection))
    }

    /// Prove QUIC connection migration: connect, send messages, rebind the
    /// UDP socket (simulating wifi→cellular), send more messages, verify
    /// all messages arrive.
    #[test]
    fn quic_connection_migration() {
        let _ = rustls::crypto::ring::default_provider().install_default();

        let rt = Runtime::new().unwrap();
        rt.block_on(async {
            // Start an in-process echo server.
            let server_addr = start_echo_server().await;

            // Connect client using test-only insecure verifier.
            let (endpoint, connection) = test_connect_inner(server_addr, "localhost")
                .await
                .expect("connect");

            // Send 5 messages before migration.
            for i in 0..5u32 {
                let (mut send, mut recv) = connection.open_bi().await.unwrap();
                let msg = format!("pre-migrate-{i}");
                send.write_all(msg.as_bytes()).await.unwrap();
                send.finish().unwrap();
                let response = recv.read_to_end(4096).await.unwrap();
                assert_eq!(response, msg.as_bytes(), "pre-migrate echo mismatch");
            }

            // Migrate: rebind to a new local UDP socket (simulates wifi→cellular).
            let new_socket = UdpSocket::bind("127.0.0.1:0").unwrap();
            let new_local = new_socket.local_addr().unwrap();
            endpoint.rebind(new_socket).expect("rebind should succeed");

            // Send 5 more messages after migration.
            for i in 0..5u32 {
                let (mut send, mut recv) = connection.open_bi().await.unwrap();
                let msg = format!("post-migrate-{i}");
                send.write_all(msg.as_bytes()).await.unwrap();
                send.finish().unwrap();
                let response = recv.read_to_end(4096).await.unwrap();
                assert_eq!(response, msg.as_bytes(), "post-migrate echo mismatch");
            }

            // Assert: connection still alive after migration.
            assert!(
                connection.close_reason().is_none(),
                "connection should still be open after migration"
            );

            // Verify the local address changed.
            let _ = new_local; // We successfully used a new socket.

            connection.close(0u32.into(), b"test done");
            endpoint.wait_idle().await;
        });
    }

    /// Start a simple QUIC echo server that echoes back whatever it receives.
    async fn start_echo_server() -> SocketAddr {
        let cert = rcgen::generate_simple_self_signed(vec!["localhost".into()]).unwrap();
        let cert_der = cert.cert.der().to_vec();
        let key_der = cert.key_pair.serialize_der();

        let cert_chain = vec![rustls::pki_types::CertificateDer::from(cert_der)];
        let key = rustls::pki_types::PrivateKeyDer::try_from(key_der).unwrap();

        let tls = rustls::ServerConfig::builder()
            .with_no_client_auth()
            .with_single_cert(cert_chain, key)
            .unwrap();

        let server_config = quinn::ServerConfig::with_crypto(Arc::new(
            quinn::crypto::rustls::QuicServerConfig::try_from(tls).unwrap(),
        ));

        let endpoint = Endpoint::server(
            server_config,
            "127.0.0.1:0".parse().unwrap(),
        )
        .unwrap();

        let addr = endpoint.local_addr().unwrap();

        // Spawn echo acceptor.
        tokio::spawn(async move {
            while let Some(incoming) = endpoint.accept().await {
                let connecting = match incoming.accept() {
                    Ok(c) => c,
                    Err(_) => continue,
                };
                tokio::spawn(async move {
                    let conn = match connecting.await {
                        Ok(c) => c,
                        Err(_) => return,
                    };
                    loop {
                        let (mut send, mut recv) = match conn.accept_bi().await {
                            Ok(s) => s,
                            Err(_) => break,
                        };
                        let data = match recv.read_to_end(4096).await {
                            Ok(d) => d,
                            Err(_) => break,
                        };
                        let _ = send.write_all(&data).await;
                        let _ = send.finish();
                    }
                });
            }
        });

        addr
    }
}
