//! ASGI adapter - Converts HTTP requests to ASGI scope and handles async Python apps

use bytes::Bytes;
use http::{HeaderMap, StatusCode};
use pyo3::types::{PyBytes, PyDict};
use pyo3::{IntoPyObject, prelude::*};
use std::sync::Arc;

use crate::errors::{FalcornError, Result};
use crate::http::{Body, HttpRequest, HttpResponse};
// use crate::python::asgi_callback::{AsgiReceive, AsgiSend, AsgiScope};
pub use super::super::{convert_py_error, convert_to_py_error, log_python_exception};
use super::context::AsgiContext;

/// ASGI environment builder
pub struct AsgiEnvironBuilder;

impl AsgiEnvironBuilder {
    /// Build ASGI scope from HTTP request
    pub fn from_request(py: Python, request: &HttpRequest) -> Result<Py<PyDict>> {
        let ctx = AsgiContext::global();
        let scope = ctx.take_scope(py)?;
        let scope_ref = scope.bind(py);

        // Base scope already contains "type" and "asgi" entries.

        // HTTP version
        let http_version = match request.version {
            http::Version::HTTP_11 => "1.1",
            http::Version::HTTP_10 => "1.0",
            http::Version::HTTP_2 => "2",
            _ => "1.1",
        };
        scope_ref.set_item(ctx.key("http_version", py), http_version)?;

        // Method
        scope_ref.set_item(ctx.key("method", py), request.method.as_str())?;

        // Path
        let path_str =
            std::str::from_utf8(&request.path).map_err(|_| FalcornError::AsgiProtocolError {
                reason: "Invalid path encoding".into(),
            })?;
        scope_ref.set_item(ctx.key("path", py), path_str)?;

        // Raw path (bytes)
        scope_ref.set_item(
            ctx.key("raw_path", py),
            PyBytes::new(py, request.path.as_ref()),
        )?;

        // Query string
        let query = request
            .query_string
            .as_ref()
            .map(|q| q.as_ref())
            .unwrap_or(b"");
        scope_ref.set_item(ctx.key("query_string", py), PyBytes::new(py, query))?;

        // Headers (as list of tuples of byte strings)
        let headers = Self::build_headers_list(py, &request.headers)?;
        scope_ref.set_item(ctx.key("headers", py), headers)?;

        // Server address
        let server = Self::build_server_tuple(py, request)?;
        scope_ref.set_item(ctx.key("server", py), server)?;

        // Client address
        let client = Self::build_client_tuple(py, request)?;
        scope_ref.set_item(ctx.key("client", py), client)?;

        // Extensions (optional)
        scope_ref.set_item(ctx.key("extensions", py), PyDict::new(py))?;

        Ok(scope)
    }

    /// Build headers list (ASGI format: list of 2-tuples of byte strings)
    fn build_headers_list(
        py: Python,
        headers: &[(bytes::Bytes, bytes::Bytes)],
    ) -> Result<Py<pyo3::types::PyList>> {
        use pyo3::types::PyList;

        let list = PyList::empty(py);

        for (name, value) in headers.iter() {
            let tuple = (
                PyBytes::new(py, name.as_ref()),
                PyBytes::new(py, value.as_ref()),
            );
            list.append(tuple)?;
        }

        Ok(list.unbind())
    }

    /// Build server tuple (host, port)
    fn build_server_tuple(py: Python, request: &HttpRequest) -> Result<Py<pyo3::types::PyTuple>> {
        // use pyo3::types::PyTuple;

        // Try to get from Host header (avoid allocations)
        let host_header = request
            .headers
            .iter()
            .find(|(name, _)| name.as_ref() == b"host")
            .map(|(_, value)| value.as_ref());

        if let Some(host) = host_header {
            let (host_part, port_part) = if let Some(idx) = host.iter().position(|b| *b == b':') {
                (&host[..idx], Some(&host[idx + 1..]))
            } else {
                (host, None)
            };
            let host_str = std::str::from_utf8(host_part).unwrap_or("localhost");
            let port: u16 = if let Some(p) = port_part {
                std::str::from_utf8(p)
                    .ok()
                    .and_then(|s| s.parse().ok())
                    .unwrap_or(80)
            } else {
                80
            };

            // return Ok(PyTuple::new(py, &[host_str.into_pyobject(py), port.into_pyobject(py)]).into());
            return Ok((host_str, port)
                .into_pyobject(py)
                .map_err(|e| convert_py_error(e))?
                .unbind());
        }

        // Default
        Ok(("localhost", 8000)
            .into_pyobject(py)
            .map_err(|e| convert_py_error(e))?
            .unbind())
    }

    /// Build client tuple (host, port)
    fn build_client_tuple(py: Python, request: &HttpRequest) -> Result<Py<pyo3::types::PyTuple>> {
        let addr = request.peer_addr;
        let host = addr.ip().to_string();
        let port = addr.port();

        Ok((host, port)
            .into_pyobject(py)
            .map_err(|e| convert_py_error(e))?
            .unbind())
    }
}

/// ASGI Response adapter
pub struct AsgiResponseAdapter {
    pub status: StatusCode,
    pub headers: HeaderMap,
    pub body_chunks: Vec<Bytes>,
}

impl AsgiResponseAdapter {
    /// Create from ASGI send events.
    ///
    /// This drains the event vector to avoid cloning headers/body on hot path.
    pub fn from_asgi_events(send_events: &mut Vec<AsgiSendEvent>) -> Result<Self> {
        let mut status = StatusCode::OK;
        let mut headers = HeaderMap::new();
        let mut body_chunks: Vec<Bytes> = Vec::new();
        let mut headers_sent = false;

        // Process all send events
        for event in send_events.drain(..) {
            match event {
                AsgiSendEvent::HttpResponseStart {
                    status: s,
                    headers: h,
                } => {
                    if headers_sent {
                        return Err(FalcornError::AsgiProtocolError {
                            reason: "http.response.start sent twice".to_string(),
                        });
                    }

                    status =
                        StatusCode::from_u16(s).map_err(|_| FalcornError::AsgiProtocolError {
                            reason: format!("Invalid status code: {}", s),
                        })?;

                    headers = Self::parse_headers(h)?;
                    headers_sent = true;
                }
                AsgiSendEvent::HttpResponseBody {
                    body: mut b,
                    more_body,
                } => {
                    if !headers_sent {
                        return Err(FalcornError::AsgiProtocolError {
                            reason: "http.response.body sent before http.response.start"
                                .to_string(),
                        });
                    }

                    if !b.is_empty() {
                        body_chunks.push(Bytes::from(std::mem::take(&mut b)));
                    }

                    if !more_body {
                        break;
                    }
                }
            }
        }

        if !headers_sent {
            return Err(FalcornError::AsgiProtocolError {
                reason: "No http.response.start event received".to_string(),
            });
        }

        Ok(Self {
            status,
            headers,
            body_chunks,
        })
    }

    /// Parse ASGI headers format to HeaderMap
    fn parse_headers(headers: Vec<(Vec<u8>, Vec<u8>)>) -> Result<HeaderMap> {
        let mut header_map = HeaderMap::with_capacity(headers.len());

        for (name_bytes, value_bytes) in headers {
            if let (Ok(header_name), Ok(header_value)) = (
                http::header::HeaderName::from_bytes(&name_bytes),
                http::header::HeaderValue::from_bytes(&value_bytes),
            ) {
                header_map.insert(header_name, header_value);
            }
        }

        Ok(header_map)
    }

    /// Convert to HttpResponse
    pub fn to_http_response(self) -> Result<HttpResponse> {
        let body = match self.body_chunks.len() {
            0 => Body::empty(),
            1 => Body::from_bytes(self.body_chunks.into_iter().next().unwrap()),
            _ => Body::from_async_chunks(self.body_chunks),
        };
        Ok(HttpResponse {
            status: self.status,
            headers: self.headers,
            body,
            version: http::Version::HTTP_11,
        })
    }
}

/// ASGI Send event types
#[derive(Debug, Clone)]
pub enum AsgiSendEvent {
    HttpResponseStart {
        status: u16,
        headers: Vec<(Vec<u8>, Vec<u8>)>,
    },
    HttpResponseBody {
        body: Vec<u8>,
        more_body: bool,
    },
}

/// ASGI Receive event types
#[derive(Debug, Clone)]
pub enum AsgiReceiveEvent {
    HttpRequest { body: Arc<Bytes>, more_body: bool },
    HttpDisconnect,
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::http::HttpMethod;
    use std::net::SocketAddr;

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

        Python::attach(|py| {
            let scope = AsgiEnvironBuilder::from_request(py, &request);
            assert!(scope.is_ok());

            let scope_dict = scope.unwrap();
            let scope_ref = scope_dict.bind(py);

            assert_eq!(
                scope_ref
                    .get_item("type")
                    .unwrap()
                    .unwrap()
                    .extract::<String>()
                    .unwrap(),
                "http"
            );
            assert_eq!(
                scope_ref
                    .get_item("method")
                    .unwrap()
                    .unwrap()
                    .extract::<String>()
                    .unwrap(),
                "GET"
            );
            assert_eq!(
                scope_ref
                    .get_item("path")
                    .unwrap()
                    .unwrap()
                    .extract::<String>()
                    .unwrap(),
                "/test"
            );
        });
    }

    #[test]
    fn test_asgi_response_adapter_success() {
        let mut events = vec![
            AsgiSendEvent::HttpResponseStart {
                status: 201,
                headers: vec![(b"content-type".to_vec(), b"text/plain".to_vec())],
            },
            AsgiSendEvent::HttpResponseBody {
                body: b"ok".to_vec(),
                more_body: false,
            },
        ];

        let adapted = AsgiResponseAdapter::from_asgi_events(&mut events).expect("adapter success");
        assert_eq!(adapted.status, StatusCode::CREATED);
        assert_eq!(adapted.body_chunks.len(), 1);
        assert_eq!(adapted.body_chunks[0].as_ref(), b"ok");
        assert_eq!(
            adapted
                .headers
                .get("content-type")
                .and_then(|v| v.to_str().ok()),
            Some("text/plain")
        );
    }

    #[test]
    fn test_asgi_response_adapter_body_before_start_is_error() {
        let mut events = vec![AsgiSendEvent::HttpResponseBody {
            body: b"late".to_vec(),
            more_body: false,
        }];

        let err = match AsgiResponseAdapter::from_asgi_events(&mut events) {
            Ok(_) => panic!("adapter should fail when body is sent before start"),
            Err(err) => err,
        };
        assert!(matches!(err, FalcornError::AsgiProtocolError { .. }));
    }

    #[test]
    fn test_asgi_response_adapter_double_start_is_error() {
        let mut events = vec![
            AsgiSendEvent::HttpResponseStart {
                status: 200,
                headers: vec![],
            },
            AsgiSendEvent::HttpResponseStart {
                status: 200,
                headers: vec![],
            },
            AsgiSendEvent::HttpResponseBody {
                body: b"late".to_vec(),
                more_body: false,
            },
        ];

        let err = match AsgiResponseAdapter::from_asgi_events(&mut events) {
            Ok(_) => panic!("adapter should fail when response.start is sent twice"),
            Err(err) => err,
        };
        assert!(matches!(err, FalcornError::AsgiProtocolError { .. }));
    }
}
