use falcorn_proto::control::DEFAULT_CONTROL_SOCKET;
use falcorn_proto::logging::{
    DEFAULT_LOG_SOCKET, PLUGIN_FILTER_ACCESS_LOG, PLUGIN_FILTER_ALL, PLUGIN_FILTER_LOG_MESSAGE,
    PLUGIN_FILTER_METRICS,
};
use falcorn_sdk::{ControlClient as SdkControlClient, Error as SdkError};
use falcorn_sdk::{
    LoggingClient as SdkLoggingClient, LoggingClientBuilder as SdkLoggingClientBuilder,
};
use pyo3::exceptions::PyRuntimeError;
use pyo3::prelude::*;
use pyo3::types::{PyDict, PyList};

#[pyclass]
pub struct LoggingClient {
    inner: SdkLoggingClient,
}

#[pymethods]
impl LoggingClient {
    #[new]
    #[pyo3(
        signature = (
            socket=None,
            auth_token=None,
            client_name=None,
            filter_mask=None,
            read_timeout_ms=None,
            write_timeout_ms=None,
        )
    )]
    fn new(
        socket: Option<String>,
        auth_token: Option<String>,
        client_name: Option<String>,
        filter_mask: Option<u8>,
        read_timeout_ms: Option<u64>,
        write_timeout_ms: Option<u64>,
    ) -> PyResult<Self> {
        let socket = socket.unwrap_or_else(|| DEFAULT_LOG_SOCKET.to_string());
        let mut builder = SdkLoggingClientBuilder::new(socket);

        if let Some(token) = auth_token {
            builder = builder.auth_token(token);
        }
        if let Some(name) = client_name {
            builder = builder.client_name(name);
        }
        if let Some(mask) = filter_mask {
            builder = builder.filter_mask(mask);
        }
        if let Some(timeout) = read_timeout_ms {
            builder = builder.read_timeout(std::time::Duration::from_millis(timeout));
        }
        if let Some(timeout) = write_timeout_ms {
            builder = builder.write_timeout(std::time::Duration::from_millis(timeout));
        }

        let client = builder.connect().map_err(map_sdk_error)?;
        Ok(Self { inner: client })
    }

    /// Read the next plugin event (blocking).
    pub fn next_event(&mut self, py: Python) -> PyResult<Py<PyAny>> {
        let event = py
            .detach(|| self.inner.next_event())
            .map_err(map_sdk_error)?;
        event_to_py(py, event)
    }
}

#[pyclass]
pub struct ControlClient {
    inner: SdkControlClient,
}

#[pymethods]
impl ControlClient {
    #[new]
    #[pyo3(
        signature = (
            socket=None,
            auth_token=None,
            client_name=None,
            read_timeout_ms=None,
            write_timeout_ms=None,
        )
    )]
    fn new(
        socket: Option<String>,
        auth_token: Option<String>,
        client_name: Option<String>,
        read_timeout_ms: Option<u64>,
        write_timeout_ms: Option<u64>,
    ) -> PyResult<Self> {
        let socket = socket.unwrap_or_else(|| DEFAULT_CONTROL_SOCKET.to_string());
        let mut builder = falcorn_sdk::ControlClient::builder(socket);

        if let Some(token) = auth_token {
            builder = builder.auth_token(token);
        }
        if let Some(name) = client_name {
            builder = builder.client_name(name);
        }
        if let Some(timeout) = read_timeout_ms {
            builder = builder.read_timeout(std::time::Duration::from_millis(timeout));
        }
        if let Some(timeout) = write_timeout_ms {
            builder = builder.write_timeout(std::time::Duration::from_millis(timeout));
        }

        let client = builder.connect().map_err(map_sdk_error)?;
        Ok(Self { inner: client })
    }

    pub fn get_status(&mut self, py: Python) -> PyResult<Py<PyAny>> {
        let status = py
            .detach(|| self.inner.get_status())
            .map_err(map_sdk_error)?;
        status_to_py(py, status)
    }

    pub fn get_workers(&mut self, py: Python) -> PyResult<Py<PyAny>> {
        let workers = py
            .detach(|| self.inner.get_workers())
            .map_err(map_sdk_error)?;
        let list = PyList::empty(py);
        for worker in workers {
            list.append(worker_to_py(py, worker)?)?;
        }
        Ok(list.into())
    }

    pub fn show_config(&mut self, py: Python) -> PyResult<String> {
        py.detach(|| self.inner.show_config())
            .map_err(map_sdk_error)
    }

    pub fn scale_to(&mut self, py: Python, workers: usize) -> PyResult<String> {
        py.detach(|| self.inner.scale_to(workers))
            .map_err(map_sdk_error)
    }

    pub fn restart_worker(
        &mut self,
        py: Python,
        id: Option<u32>,
        graceful: Option<bool>,
    ) -> PyResult<String> {
        let graceful = graceful.unwrap_or(true);
        py.detach(|| self.inner.restart_worker(id, graceful))
            .map_err(map_sdk_error)
    }

    pub fn shutdown(&mut self, py: Python, graceful: Option<bool>) -> PyResult<String> {
        let graceful = graceful.unwrap_or(true);
        py.detach(|| self.inner.shutdown(graceful))
            .map_err(map_sdk_error)
    }

    pub fn reload_config(
        &mut self,
        py: Python,
        path: Option<String>,
        rolling: Option<bool>,
    ) -> PyResult<String> {
        let rolling = rolling.unwrap_or(true);
        py.detach(|| self.inner.reload_config(path, rolling))
            .map_err(map_sdk_error)
    }
}

pub fn add_plugin_constants(m: &Bound<'_, PyModule>) -> PyResult<()> {
    m.add("DEFAULT_LOG_SOCKET", DEFAULT_LOG_SOCKET)?;
    m.add("DEFAULT_CONTROL_SOCKET", DEFAULT_CONTROL_SOCKET)?;
    m.add("PLUGIN_FILTER_LOG_MESSAGE", PLUGIN_FILTER_LOG_MESSAGE)?;
    m.add("PLUGIN_FILTER_ACCESS_LOG", PLUGIN_FILTER_ACCESS_LOG)?;
    m.add("PLUGIN_FILTER_METRICS", PLUGIN_FILTER_METRICS)?;
    m.add("PLUGIN_FILTER_ALL", PLUGIN_FILTER_ALL)?;
    Ok(())
}

fn map_sdk_error(err: SdkError) -> PyErr {
    PyRuntimeError::new_err(err.to_string())
}

fn event_to_py(py: Python, event: falcorn_proto::logging::PluginEvent) -> PyResult<Py<PyAny>> {
    let dict = PyDict::new(py);
    match event {
        falcorn_proto::logging::PluginEvent::LogMessage {
            ts_millis,
            level,
            pid,
            worker_id,
            message,
        } => {
            dict.set_item("type", "log")?;
            dict.set_item("ts_millis", ts_millis)?;
            dict.set_item("level", level.as_str())?;
            dict.set_item("pid", pid)?;
            dict.set_item("worker_id", worker_id)?;
            dict.set_item("message", message)?;
        }
        falcorn_proto::logging::PluginEvent::AccessLog {
            ts_millis,
            remote,
            method,
            uri,
            version,
            status,
            size,
            duration_micros,
        } => {
            dict.set_item("type", "access")?;
            dict.set_item("ts_millis", ts_millis)?;
            dict.set_item("remote", remote)?;
            dict.set_item("method", method)?;
            dict.set_item("uri", uri)?;
            dict.set_item("version", version)?;
            dict.set_item("status", status)?;
            dict.set_item("size", size)?;
            dict.set_item("duration_micros", duration_micros)?;
        }
        falcorn_proto::logging::PluginEvent::Metrics(metrics) => {
            dict.set_item("type", "metrics")?;
            dict.set_item("uptime_secs", metrics.uptime_secs)?;
            dict.set_item("total_requests", metrics.total_requests)?;
            dict.set_item("successful_requests", metrics.successful_requests)?;
            dict.set_item("failed_requests", metrics.failed_requests)?;
            dict.set_item("requests_per_second", metrics.requests_per_second)?;
            dict.set_item("success_rate", metrics.success_rate)?;
            dict.set_item("failure_rate", metrics.failure_rate)?;
            dict.set_item("avg_response_time_micros", metrics.avg_response_time_micros)?;
            dict.set_item("min_response_time_micros", metrics.min_response_time_micros)?;
            dict.set_item("max_response_time_micros", metrics.max_response_time_micros)?;
            dict.set_item("p50_response_time_micros", metrics.p50_response_time_micros)?;
            dict.set_item("p95_response_time_micros", metrics.p95_response_time_micros)?;
            dict.set_item("p99_response_time_micros", metrics.p99_response_time_micros)?;
            dict.set_item(
                "p999_response_time_micros",
                metrics.p999_response_time_micros,
            )?;
            dict.set_item("active_connections", metrics.active_connections)?;
            dict.set_item("total_connections", metrics.total_connections)?;
            dict.set_item("worker_restarts", metrics.worker_restarts)?;
            dict.set_item("status_2xx", metrics.status_2xx)?;
            dict.set_item("status_3xx", metrics.status_3xx)?;
            dict.set_item("status_4xx", metrics.status_4xx)?;
            dict.set_item("status_5xx", metrics.status_5xx)?;
            dict.set_item("backlog", metrics.backlog)?;
            dict.set_item("cpu_percent", metrics.cpu_percent)?;
            dict.set_item("mem_bytes", metrics.mem_bytes)?;
            dict.set_item("error_rate", metrics.error_rate)?;
            dict.set_item("error_4xx", metrics.error_4xx)?;
            dict.set_item("error_5xx", metrics.error_5xx)?;
            let top_errors: Option<Py<PyAny>> = if let Some(items) = metrics.top_errors {
                let list = PyList::empty(py);
                for item in items {
                    let entry = PyDict::new(py);
                    entry.set_item("code", item.code)?;
                    entry.set_item("count", item.count)?;
                    list.append(entry)?;
                }
                Some(list.into())
            } else {
                None
            };
            dict.set_item("top_errors", top_errors)?;

            let workers: Option<Py<PyAny>> = if let Some(items) = metrics.workers {
                let list = PyList::empty(py);
                for worker in items {
                    let entry = PyDict::new(py);
                    entry.set_item("id", worker.id)?;
                    entry.set_item("pid", worker.pid)?;
                    entry.set_item("active", worker.active)?;
                    entry.set_item("restarts", worker.restarts)?;
                    entry.set_item("uptime_secs", worker.uptime_secs)?;
                    entry.set_item("req_total", worker.req_total)?;
                    entry.set_item("rps", worker.rps)?;
                    entry.set_item("success_rate", worker.success_rate)?;
                    entry.set_item("error_rate", worker.error_rate)?;
                    entry.set_item("active_connections", worker.active_connections)?;
                    entry.set_item("cpu_percent", worker.cpu_percent)?;
                    entry.set_item("mem_bytes", worker.mem_bytes)?;
                    list.append(entry)?;
                }
                Some(list.into())
            } else {
                None
            };
            dict.set_item("workers", workers)?;
        }
    }
    Ok(dict.into())
}

fn status_to_py(py: Python, status: falcorn_proto::control::StatusSnapshot) -> PyResult<Py<PyAny>> {
    let dict = PyDict::new(py);
    dict.set_item("version", status.version)?;
    dict.set_item("pid", status.pid)?;
    dict.set_item("bind_addr", status.bind_addr)?;
    dict.set_item("worker_count", status.worker_count)?;
    dict.set_item("worker_class", status.worker_class)?;
    dict.set_item("app_module", status.app_module)?;
    dict.set_item("app_callable", status.app_callable)?;
    dict.set_item("uptime_secs", status.uptime_secs)?;
    Ok(dict.into())
}

fn worker_to_py(py: Python, worker: falcorn_proto::control::WorkerSnapshot) -> PyResult<Py<PyAny>> {
    let dict = PyDict::new(py);
    dict.set_item("id", worker.id)?;
    dict.set_item("pid", worker.pid)?;
    dict.set_item("state", worker.state)?;
    dict.set_item("restarts", worker.restarts)?;
    dict.set_item("uptime_secs", worker.uptime_secs)?;
    Ok(dict.into())
}
