// ! WSGI Applicatioin instance

pub use super::app_loader::AppLoader;
pub use super::asgi::AsgiExecutor;
pub use super::python_errors::{convert_py_error, convert_to_py_error, log_python_exception};
pub use super::wsgi::{ResponseState, StartResponse, WsgiEnvironBuilder, WsgiResponseAdapter};
use crate::config::ProtocolType;
use crate::errors::{FalcornError, Result};
use crate::http::{HttpRequest, HttpResponse};
use crate::python::wsgi::WsgiContext;

use pyo3::prelude::*;
use std::path::PathBuf;
use std::sync::Arc;
// use http::{HeaderMap, StatusCode};

/// Represents a loaded Python application instance
pub struct ApplicationInstance {
    app: Option<Py<PyAny>>,
    asgi_executor: Option<AsgiExecutor>,
}

unsafe impl Send for ApplicationInstance {}
unsafe impl Sync for ApplicationInstance {}

impl ApplicationInstance {
    /// Load a Python WSGI application
    pub fn load(
        module_name: &str,
        callable_name: &str,
        pythonpath: &[PathBuf],
        protocol: &ProtocolType,
        asgi_event_loop: &str,
        asgi_event_loop_fallback: bool,
        asgi_submit_timeout_ms: u64,
        asgi_max_inflight: usize,
        asgi_send_pool_size: usize,
        asgi_scheduler: &str,
    ) -> Result<Arc<Self>> {
        let loader = AppLoader::new(Vec::from(pythonpath));
        let app = loader.load(module_name, callable_name, protocol)?;
        let (app, asgi_executor) = if matches!(protocol, ProtocolType::Asgi) {
            let exec = AsgiExecutor::new(
                app,
                asgi_event_loop,
                asgi_event_loop_fallback,
                asgi_submit_timeout_ms,
                asgi_max_inflight,
                asgi_send_pool_size,
                asgi_scheduler,
            )?;
            (None, Some(exec))
        } else {
            (Some(app), None)
        };

        Ok(Arc::new(Self { app, asgi_executor }))
    }

    /// Call WSGI application synchronously
    pub fn call_wsgi(&self, request: &HttpRequest) -> Result<HttpResponse> {
        Python::attach(|py| {
            // Build WSGI environ
            let environ = WsgiEnvironBuilder::from_request(py, request)?;

            // Create Callback and response state
            let (state, start_response) = Self::create_start_response_callback(py)?;

            // Call application
            let app = self
                .app
                .as_ref()
                .ok_or_else(|| FalcornError::runtime("WSGI app is not initialized"))?;
            let response_iter = app
                .call(py, (environ.clone_ref(py), start_response), None)
                .map_err(|e| {
                    println!("{:#?}", e.traceback(py).unwrap().format());
                    FalcornError::python(e.to_string())
                })?;

            // Parse response
            let result = Self::parse_response(py, &response_iter.into_bound(py), state);

            // Recycle environ
            let _ = WsgiContext::global().recycle_environ(py, environ);

            result
        })
    }

    /// Call ASGI application
    pub async fn call_asgi(&self, request: &HttpRequest) -> Result<HttpResponse> {
        if let Some(executor) = &self.asgi_executor {
            return executor.call(request).await;
        }

        Err(FalcornError::runtime("ASGI executor is not initialized"))
    }

    /// Parse WSGI response
    fn parse_response<'py>(
        py: Python,
        response_iter: &Bound<'py, PyAny>,
        state: Arc<ResponseState>,
    ) -> Result<HttpResponse> {
        WsgiResponseAdapter::from_wsgi_output(py, response_iter, state)?.to_http_response()
    }

    /// Create the start_response callback function
    fn create_start_response_callback(
        py: Python,
    ) -> Result<(Arc<ResponseState>, Py<StartResponse>)> {
        let state = Arc::new(ResponseState::new());

        let callback = Py::new(
            py,
            StartResponse {
                state: Arc::clone(&state),
            },
        )?;

        Ok((state, callback))
    }
}

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

    #[test]
    fn test_wsgi_environ_building() {
        let request = HttpRequest {
            raw: bytes::Bytes::new(),
            method: crate::http::HttpMethod::Get,
            path: bytes::Bytes::from_static(b"/test"),
            query_string: Some(bytes::Bytes::from_static(b"param=value")),
            version: http::Version::HTTP_11,
            headers: Vec::new(),
            body: None,
            peer_addr: "127.0.0.1:8000".parse().unwrap(),
        };

        Python::attach(|py| {
            let environ = WsgiEnvironBuilder::from_request(py, &request);
            assert!(environ.is_ok());
        });
    }
}
