#![allow(non_local_definitions)]

use pyo3::exceptions::PyRuntimeError;
use pyo3::prelude::*;
use std::sync::Arc;

use hippius_hermes_core::client::Client as CoreClient;
use hippius_hermes_core::config::Config as CoreConfig;
use hippius_hermes_core::network::message::HermesMessage;
use pyo3::types::PyBytes;

#[pyclass(name = "Config")]
#[derive(Clone)]
pub struct PyConfig {
    inner: CoreConfig,
}

#[pymethods]
impl PyConfig {
    #[new]
    #[pyo3(signature = (node_secret_key_path, ss58_address, api_token, storage_directory, rpc_url=None, sync_engine_url=None, subnet_ids=None))]
    fn new(
        node_secret_key_path: String,
        ss58_address: String,
        api_token: String,
        storage_directory: String,
        rpc_url: Option<String>,
        sync_engine_url: Option<String>,
        subnet_ids: Option<Vec<u16>>,
    ) -> Self {
        Self {
            inner: CoreConfig {
                node_secret_key_path: node_secret_key_path.into(),
                ss58_address,
                api_token,
                storage_directory: storage_directory.into(),
                rpc_url: rpc_url.unwrap_or_else(|| "wss://rpc.hippius.network:443".to_string()),
                sync_engine_url: sync_engine_url
                    .unwrap_or_else(|| "https://hippius.store".to_string()),
                subnet_ids: subnet_ids.unwrap_or_default(),
            },
        }
    }

    /// Loads the configuration securely from a JSON file.
    #[staticmethod]
    fn from_file(path: String) -> PyResult<Self> {
        let inner = CoreConfig::from_file(&path)
            .map_err(|e| PyRuntimeError::new_err(format!("Config error: {}", e)))?;
        Ok(Self { inner })
    }
}

#[pyclass(name = "HermesClient")]
pub struct PyHermesClient {
    // Arc to strictly enforce thread-safe Python sharing
    inner: Arc<CoreClient>,
}

#[pymethods]
impl PyHermesClient {
    /// Asynchronously initialize the core Hermes node and attach it to Python.
    #[staticmethod]
    fn create(py: Python<'_>, config: PyConfig) -> PyResult<&PyAny> {
        let conf = config.inner.clone();

        pyo3_asyncio::tokio::future_into_py(py, async move {
            let client = CoreClient::new(conf).await.map_err(|e| {
                PyRuntimeError::new_err(format!("Failed to start Hermes core: {}", e))
            })?;

            Ok(PyHermesClient {
                inner: Arc::new(client),
            })
        })
    }

    /// Send an AES-GCM encrypted file directly to a peer (not yet implemented).
    fn send_file<'a>(
        &self,
        py: Python<'a>,
        dest_ss58: String,
        file_path: String,
    ) -> PyResult<&'a PyAny> {
        let client = self.inner.clone();

        pyo3_asyncio::tokio::future_into_py(py, async move {
            let hash = client
                .send_file(&dest_ss58, &file_path)
                .await
                .map_err(|e| PyRuntimeError::new_err(format!("Hermes delivery failed: {}", e)))?;
            Ok(hash)
        })
    }

    /// Receive and decrypt a file using DH key material (not yet implemented).
    fn receive_file<'a>(
        &self,
        py: Python<'a>,
        hash: String,
        encrypted_keys: Vec<u8>,
        ephemeral_pub: Vec<u8>,
        dh_nonce: Vec<u8>,
        out_dir: String,
    ) -> PyResult<&'a PyAny> {
        let client = self.inner.clone();

        pyo3_asyncio::tokio::future_into_py(py, async move {
            let path = client
                .receive_file(&hash, &encrypted_keys, &ephemeral_pub, &dh_nonce, &out_dir)
                .await
                .map_err(|e| {
                    PyRuntimeError::new_err(format!("Hermes payload decryption failed: {}", e))
                })?;

            Ok(path)
        })
    }

    /// Send a file directly to a peer via QUIC (direct P2P, no Sync-Engine).
    fn send_file_unencrypted<'a>(
        &self,
        py: Python<'a>,
        dest_ss58: String,
        file_path: String,
    ) -> PyResult<&'a PyAny> {
        let client = self.inner.clone();

        pyo3_asyncio::tokio::future_into_py(py, async move {
            let filename = client
                .send_file_unencrypted(&dest_ss58, &file_path)
                .await
                .map_err(|e| {
                    PyRuntimeError::new_err(format!("Direct P2P transfer failed: {}", e))
                })?;
            Ok(filename)
        })
    }

    /// Pull-based direct P2P receiving (not yet implemented).
    fn receive_file_unencrypted<'a>(
        &self,
        py: Python<'a>,
        hash: String,
        out_dir: String,
    ) -> PyResult<&'a PyAny> {
        let client = self.inner.clone();

        pyo3_asyncio::tokio::future_into_py(py, async move {
            let path = client
                .receive_file_unencrypted(&hash, &out_dir)
                .await
                .map_err(|e| {
                    PyRuntimeError::new_err(format!("Hermes payload download failed: {}", e))
                })?;

            Ok(path)
        })
    }

    /// Upload an encrypted file to the Hippius Sync-Engine.
    fn send_file_to_store<'a>(
        &self,
        py: Python<'a>,
        dest_ss58: String,
        file_path: String,
    ) -> PyResult<&'a PyAny> {
        let client = self.inner.clone();

        pyo3_asyncio::tokio::future_into_py(py, async move {
            let hash = client
                .send_file_to_store(&dest_ss58, &file_path)
                .await
                .map_err(|e| PyRuntimeError::new_err(format!("Hermes delivery failed: {}", e)))?;
            Ok(hash)
        })
    }

    /// Download and decrypt an encrypted file from the Hippius Sync-Engine.
    fn receive_file_from_store<'a>(
        &self,
        py: Python<'a>,
        hash: String,
        encrypted_keys: Vec<u8>,
        ephemeral_pub: Vec<u8>,
        dh_nonce: Vec<u8>,
        out_dir: String,
    ) -> PyResult<&'a PyAny> {
        let client = self.inner.clone();

        pyo3_asyncio::tokio::future_into_py(py, async move {
            let path = client
                .receive_file_from_store(
                    &hash,
                    &encrypted_keys,
                    &ephemeral_pub,
                    &dh_nonce,
                    &out_dir,
                )
                .await
                .map_err(|e| {
                    PyRuntimeError::new_err(format!("Hermes payload decryption failed: {}", e))
                })?;

            Ok(path)
        })
    }

    /// Upload an unencrypted file to the Hippius Sync-Engine.
    fn send_file_unencrypted_to_store<'a>(
        &self,
        py: Python<'a>,
        dest_ss58: String,
        file_path: String,
    ) -> PyResult<&'a PyAny> {
        let client = self.inner.clone();

        pyo3_asyncio::tokio::future_into_py(py, async move {
            let hash = client
                .send_file_unencrypted_to_store(&dest_ss58, &file_path)
                .await
                .map_err(|e| PyRuntimeError::new_err(format!("Hermes delivery failed: {}", e)))?;
            Ok(hash)
        })
    }

    /// Download an unencrypted file from the Hippius Sync-Engine.
    fn receive_file_unencrypted_from_store<'a>(
        &self,
        py: Python<'a>,
        hash: String,
        out_dir: String,
    ) -> PyResult<&'a PyAny> {
        let client = self.inner.clone();

        pyo3_asyncio::tokio::future_into_py(py, async move {
            let path = client
                .receive_file_unencrypted_from_store(&hash, &out_dir)
                .await
                .map_err(|e| {
                    PyRuntimeError::new_err(format!("Hermes payload download failed: {}", e))
                })?;

            Ok(path)
        })
    }

    /// Fires up the relentless background Sled queue retries out of band.
    fn start_retry_worker(&self) {
        self.inner.spawn_retry_worker();
    }

    /// Starts the QUIC listener with separate callbacks for control messages and direct P2P data.
    ///
    /// `callback` receives control messages: `(action: str, sender_ss58: str, payload: bytes)`
    /// `on_data` (optional) receives direct P2P files: `(sender_ss58: str, filename: str, local_path: str, file_size: int)`
    #[pyo3(signature = (callback, on_data=None))]
    fn start_listener(&self, callback: PyObject, on_data: Option<PyObject>) {
        let control_cb = move |msg: HermesMessage| {
            Python::with_gil(|py| {
                let payload_bytes = PyBytes::new(py, &msg.payload);
                if let Err(e) = callback.call1(py, (msg.action, msg.sender_ss58, payload_bytes)) {
                    eprintln!("[-] Python Hermes control callback crash: {}", e);
                }
            });
        };

        let data_cb = on_data.map(|py_cb| {
            move |sender_ss58: String, filename: String, local_path: String, file_size: u64| {
                Python::with_gil(|py| {
                    if let Err(e) = py_cb.call1(py, (sender_ss58, filename, local_path, file_size))
                    {
                        eprintln!("[-] Python Hermes data callback crash: {}", e);
                    }
                });
            }
        });

        self.inner.spawn_listener(control_cb, data_cb);
    }
}

/// A pure-Rust M2M Control and Data Plane over Iroh and Substrate.
#[pymodule]
fn core(_py: Python, m: &PyModule) -> PyResult<()> {
    m.add_class::<PyConfig>()?;
    m.add_class::<PyHermesClient>()?;
    Ok(())
}
