use crate::sip::{
    headers::untyped::*,
    headers::{Header, Headers},
    uri::Branch,
    Error, Method, StatusCode, Uri, Version,
};

pub trait HasHeaders {
    fn headers(&self) -> &Headers;
    fn headers_mut(&mut self) -> &mut Headers;
}

macro_rules! header_get {
    ($iter:expr, $variant:path, $err:expr) => {
        $iter
            .find_map(|h| {
                if let $variant(inner) = h {
                    Some(inner)
                } else {
                    None
                }
            })
            .ok_or_else(|| $err)
    };
}

macro_rules! header_get_mut {
    ($iter:expr, $variant:path, $err:expr) => {
        $iter
            .find_map(|h| {
                if let $variant(inner) = h {
                    Some(inner)
                } else {
                    None
                }
            })
            .ok_or_else(|| $err)
    };
}

macro_rules! header_opt {
    ($iter:expr, $variant:path) => {
        $iter.find_map(|h| {
            if let $variant(inner) = h {
                Some(inner)
            } else {
                None
            }
        })
    };
}

macro_rules! all_headers {
    ($iter:expr, $variant:path) => {
        $iter
            .filter_map(|h| {
                if let $variant(inner) = h {
                    Some(inner)
                } else {
                    None
                }
            })
            .collect()
    };
}

pub trait HeadersExt: HasHeaders {
    fn to_header(&self) -> Result<&To, Error> {
        header_get!(
            self.headers().iter(),
            Header::To,
            Error::MissingHeader("To".into())
        )
    }
    fn to_header_mut(&mut self) -> Result<&mut To, Error> {
        header_get_mut!(
            self.headers_mut().iter_mut(),
            Header::To,
            Error::MissingHeader("To".into())
        )
    }
    fn from_header(&self) -> Result<&From, Error> {
        header_get!(
            self.headers().iter(),
            Header::From,
            Error::MissingHeader("From".into())
        )
    }
    fn from_header_mut(&mut self) -> Result<&mut From, Error> {
        header_get_mut!(
            self.headers_mut().iter_mut(),
            Header::From,
            Error::MissingHeader("From".into())
        )
    }
    fn via_header(&self) -> Result<&Via, Error> {
        header_get!(
            self.headers().iter(),
            Header::Via,
            Error::MissingHeader("Via".into())
        )
    }
    fn top_via_header(&self) -> Result<Via, Error> {
        self.via_header()?.first_value()
    }
    fn via_header_mut(&mut self) -> Result<&mut Via, Error> {
        header_get_mut!(
            self.headers_mut().iter_mut(),
            Header::Via,
            Error::MissingHeader("Via".into())
        )
    }
    fn call_id_header(&self) -> Result<&CallId, Error> {
        header_get!(
            self.headers().iter(),
            Header::CallId,
            Error::MissingHeader("Call-ID".into())
        )
    }
    fn call_id_header_mut(&mut self) -> Result<&mut CallId, Error> {
        header_get_mut!(
            self.headers_mut().iter_mut(),
            Header::CallId,
            Error::MissingHeader("Call-ID".into())
        )
    }
    fn cseq_header(&self) -> Result<&CSeq, Error> {
        header_get!(
            self.headers().iter(),
            Header::CSeq,
            Error::MissingHeader("CSeq".into())
        )
    }
    fn cseq_header_mut(&mut self) -> Result<&mut CSeq, Error> {
        header_get_mut!(
            self.headers_mut().iter_mut(),
            Header::CSeq,
            Error::MissingHeader("CSeq".into())
        )
    }
    fn max_forwards_header(&self) -> Result<&MaxForwards, Error> {
        header_get!(
            self.headers().iter(),
            Header::MaxForwards,
            Error::MissingHeader("Max-Forwards".into())
        )
    }
    fn max_forwards_header_mut(&mut self) -> Result<&mut MaxForwards, Error> {
        header_get_mut!(
            self.headers_mut().iter_mut(),
            Header::MaxForwards,
            Error::MissingHeader("Max-Forwards".into())
        )
    }
    fn contact_header(&self) -> Result<&Contact, Error> {
        header_get!(
            self.headers().iter(),
            Header::Contact,
            Error::MissingHeader("Contact".into())
        )
    }
    fn contact_header_mut(&mut self) -> Result<&mut Contact, Error> {
        header_get_mut!(
            self.headers_mut().iter_mut(),
            Header::Contact,
            Error::MissingHeader("Contact".into())
        )
    }
    fn contact_headers(&self) -> Vec<&Contact> {
        all_headers!(self.headers().iter(), Header::Contact)
    }
    fn typed_contact_headers(&self) -> Result<Vec<crate::sip::typed::Contact>, Error> {
        let mut contacts = Vec::new();
        for contact in self.contact_headers() {
            contacts.extend(crate::sip::typed::Contact::parse_header_list(
                contact.value(),
            )?);
        }
        Ok(contacts)
    }
    fn record_route_headers(&self) -> Vec<&RecordRoute> {
        all_headers!(self.headers().iter(), Header::RecordRoute)
    }
    fn record_route_header(&self) -> Option<&RecordRoute> {
        header_opt!(self.headers().iter(), Header::RecordRoute)
    }
    fn typed_record_route_headers(&self) -> Result<Vec<crate::sip::typed::RecordRoute>, Error> {
        let mut rrs = Vec::new();
        for rr in self.record_route_headers() {
            rrs.extend(crate::sip::typed::RecordRoute::parse_header_list(
                rr.value(),
            )?);
        }
        Ok(rrs)
    }
    fn route_headers(&self) -> Vec<&Route> {
        all_headers!(self.headers().iter(), Header::Route)
    }
    fn route_header(&self) -> Option<&Route> {
        header_opt!(self.headers().iter(), Header::Route)
    }
    fn typed_route_headers(&self) -> Result<Vec<crate::sip::typed::Route>, Error> {
        let mut routes = Vec::new();
        for r in self.route_headers() {
            routes.extend(crate::sip::typed::Route::parse_header_list(r.value())?);
        }
        Ok(routes)
    }
    fn user_agent_header(&self) -> Option<&UserAgent> {
        header_opt!(self.headers().iter(), Header::UserAgent)
    }
    fn authorization_header(&self) -> Option<&Authorization> {
        header_opt!(self.headers().iter(), Header::Authorization)
    }
    fn www_authenticate_header(&self) -> Option<&WwwAuthenticate> {
        header_opt!(self.headers().iter(), Header::WwwAuthenticate)
    }
    fn expires_header(&self) -> Option<&Expires> {
        header_opt!(self.headers().iter(), Header::Expires)
    }
    fn min_expires_header(&self) -> Option<&MinExpires> {
        header_opt!(self.headers().iter(), Header::MinExpires)
    }
    fn reason_header(&self) -> Option<&crate::sip::headers::untyped::Reason> {
        header_opt!(self.headers().iter(), Header::Reason)
    }
    fn refer_to_header(&self) -> Option<&crate::sip::headers::untyped::ReferTo> {
        header_opt!(self.headers().iter(), Header::ReferTo)
    }
    fn referred_by_header(&self) -> Option<&crate::sip::headers::untyped::ReferredBy> {
        header_opt!(self.headers().iter(), Header::ReferredBy)
    }
    fn session_expires_header(&self) -> Option<&crate::sip::headers::untyped::SessionExpires> {
        header_opt!(self.headers().iter(), Header::SessionExpires)
    }
    fn p_asserted_identity_header(
        &self,
    ) -> Option<&crate::sip::headers::untyped::PAssertedIdentity> {
        header_opt!(self.headers().iter(), Header::PAssertedIdentity)
    }
    fn replaces_header(&self) -> Option<&crate::sip::headers::untyped::Replaces> {
        header_opt!(self.headers().iter(), Header::Replaces)
    }
    fn privacy_header(&self) -> Option<&crate::sip::headers::untyped::Privacy> {
        header_opt!(self.headers().iter(), Header::Privacy)
    }
    fn path_headers(&self) -> Vec<&crate::sip::headers::untyped::Path> {
        all_headers!(self.headers().iter(), Header::Path)
    }
    fn rseq_value(&self) -> Option<u32> {
        self.headers().iter().find_map(|h| {
            if let Header::RSeq(r) = h {
                r.value().trim().parse().ok()
            } else {
                None
            }
        })
    }
    fn rack_value(&self) -> Option<(u32, u32, Method)> {
        self.headers().iter().find_map(|h| {
            if let Header::RAck(r) = h {
                let v = r.value();
                let mut parts = v.split_whitespace();
                let rseq = parts.next()?.parse::<u32>().ok()?;
                let cseq = parts.next()?.parse::<u32>().ok()?;
                let method = parts.next()?.parse::<Method>().ok()?;
                Some((rseq, cseq, method))
            } else {
                None
            }
        })
    }
    fn header_value(&self, name: &str) -> Option<String> {
        self.headers().iter().find_map(|h| {
            if h.name().eq_ignore_ascii_case(name) {
                Some(h.value().trim().to_string())
            } else {
                None
            }
        })
    }
    fn header_contains_token(&self, name: &str, token: &str) -> bool {
        self.headers().iter().any(|header| match header {
            Header::Supported(value) if name.eq_ignore_ascii_case("Supported") => value
                .value()
                .split(',')
                .any(|item| item.trim().eq_ignore_ascii_case(token)),
            Header::Require(value) if name.eq_ignore_ascii_case("Require") => value
                .value()
                .split(',')
                .any(|item| item.trim().eq_ignore_ascii_case(token)),
            _ => false,
        })
    }
    fn transaction_id(&self) -> Result<Option<Branch>, Error> {
        use crate::sip::headers::untyped::ToTypedHeader;
        Ok(self.top_via_header()?.typed()?.branch().cloned())
    }
}

#[derive(Debug, PartialEq, Eq, Clone)]
pub struct Request {
    pub method: Method,
    pub uri: Uri,
    pub version: Version,
    pub headers: Headers,
    pub body: Vec<u8>,
}

impl Request {
    pub fn method(&self) -> &Method {
        &self.method
    }
    pub fn uri(&self) -> &Uri {
        &self.uri
    }
    pub fn destination(&self) -> Uri {
        for route in self.route_headers() {
            if let Ok(mut routes) = crate::sip::typed::Route::parse_header_list(route.value()) {
                if let Some(route) = routes.drain(..).next() {
                    return route.uri;
                }
            }
        }
        self.uri.clone()
    }
    pub fn version(&self) -> &Version {
        &self.version
    }
    pub fn body(&self) -> &Vec<u8> {
        &self.body
    }
    pub fn body_mut(&mut self) -> &mut Vec<u8> {
        &mut self.body
    }
}

impl HasHeaders for Request {
    fn headers(&self) -> &Headers {
        &self.headers
    }
    fn headers_mut(&mut self) -> &mut Headers {
        &mut self.headers
    }
}

impl HeadersExt for Request {}

impl std::fmt::Display for Request {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(
            f,
            "{} {} {}\r\n{}\r\n{}",
            self.method,
            self.uri,
            self.version,
            self.headers,
            String::from_utf8_lossy(&self.body)
        )
    }
}

impl Request {
    /// Serialize the request to its on-the-wire byte representation.
    ///
    /// The request line and headers are ASCII; the body is appended verbatim.
    /// A SIP message body is an opaque octet sequence (RFC 3261 §7.4) and may
    /// contain arbitrary bytes — e.g. a binary ISUP part or an eCall MSD — so
    /// it must not be forced through UTF-8.
    ///
    /// Prefer this over [`Display`](std::fmt::Display) / `to_string()` when
    /// putting a message on the wire: `Display` renders the body with
    /// [`String::from_utf8_lossy`], which is fine for logging but corrupts
    /// non-UTF-8 bytes.
    pub fn to_bytes(&self) -> Vec<u8> {
        let head = format!(
            "{} {} {}\r\n{}\r\n",
            self.method, self.uri, self.version, self.headers
        );
        let mut buf = head.into_bytes();
        buf.extend_from_slice(&self.body);
        buf
    }
}

impl std::convert::TryFrom<Vec<u8>> for Request {
    type Error = Error;
    fn try_from(bytes: Vec<u8>) -> Result<Self, Error> {
        match crate::sip::parser::parse_message(&bytes)? {
            SipMessage::Request(r) => Ok(r),
            SipMessage::Response(_) => {
                Err(Error::Unexpected("expected Request, got Response".into()))
            }
        }
    }
}

impl std::convert::TryFrom<&[u8]> for Request {
    type Error = Error;
    fn try_from(bytes: &[u8]) -> Result<Self, Error> {
        match crate::sip::parser::parse_message(bytes)? {
            SipMessage::Request(r) => Ok(r),
            SipMessage::Response(_) => {
                Err(Error::Unexpected("expected Request, got Response".into()))
            }
        }
    }
}

impl std::convert::TryFrom<&str> for Request {
    type Error = Error;
    fn try_from(s: &str) -> Result<Self, Error> {
        Self::try_from(s.as_bytes())
    }
}

impl std::convert::TryFrom<String> for Request {
    type Error = Error;
    fn try_from(s: String) -> Result<Self, Error> {
        Self::try_from(s.as_bytes())
    }
}

impl std::convert::From<Request> for String {
    fn from(r: Request) -> String {
        r.to_string()
    }
}

impl std::convert::From<Request> for Vec<u8> {
    fn from(r: Request) -> Vec<u8> {
        r.to_bytes()
    }
}

#[derive(Debug, PartialEq, Eq, Clone)]
pub struct Response {
    pub status_code: StatusCode,
    pub version: Version,
    pub headers: Headers,
    pub body: Vec<u8>,
}

impl Response {
    pub fn status_code(&self) -> &StatusCode {
        &self.status_code
    }
    pub fn version(&self) -> &Version {
        &self.version
    }
    pub fn body(&self) -> &Vec<u8> {
        &self.body
    }
    pub fn body_mut(&mut self) -> &mut Vec<u8> {
        &mut self.body
    }
    pub fn reason_phrase(&self) -> Option<&str> {
        self.headers.iter().find_map(|h| {
            if let Header::Reason(r) = h {
                Some(r.value())
            } else {
                None
            }
        })
    }

    /// Get the received address from the Via header (including rport if present)
    pub fn via_received(&self) -> Option<crate::sip::uri::HostWithPort> {
        use crate::sip::HeadersExt;
        self.top_via_header().ok().and_then(|via| {
            via.typed()
                .ok()
                .and_then(|typed_via: crate::sip::typed::Via| {
                    let received = typed_via.params.iter().find_map(|p| {
                        if let crate::sip::uri::Param::Received(r) = p {
                            r.parse().ok().map(crate::sip::uri::Host::IpAddr)
                        } else {
                            None
                        }
                    });
                    let rport = typed_via.params.iter().find_map(|p| {
                        if let crate::sip::uri::Param::Rport(rport) = p {
                            *rport
                        } else {
                            None
                        }
                    });
                    received.map(|host| crate::sip::uri::HostWithPort {
                        host,
                        port: rport.map(crate::sip::transport::Port),
                    })
                })
        })
    }
}

impl HasHeaders for Response {
    fn headers(&self) -> &Headers {
        &self.headers
    }
    fn headers_mut(&mut self) -> &mut Headers {
        &mut self.headers
    }
}

impl HeadersExt for Response {}

impl Default for Response {
    fn default() -> Self {
        Response {
            status_code: StatusCode::OK,
            version: Version::V2,
            headers: Headers::default(),
            body: Vec::new(),
        }
    }
}

impl std::fmt::Display for Response {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(
            f,
            "{} {} {}\r\n{}\r\n{}",
            self.version,
            self.status_code.code(),
            self.status_code.text(),
            self.headers,
            String::from_utf8_lossy(&self.body)
        )
    }
}

impl Response {
    /// Serialize the response to its on-the-wire byte representation.
    ///
    /// See [`Request::to_bytes`]: the body is written verbatim because a SIP
    /// body is an opaque octet sequence (RFC 3261 §7.4), not necessarily UTF-8.
    pub fn to_bytes(&self) -> Vec<u8> {
        let head = format!(
            "{} {} {}\r\n{}\r\n",
            self.version,
            self.status_code.code(),
            self.status_code.text(),
            self.headers
        );
        let mut buf = head.into_bytes();
        buf.extend_from_slice(&self.body);
        buf
    }
}

impl std::convert::TryFrom<Vec<u8>> for Response {
    type Error = Error;
    fn try_from(bytes: Vec<u8>) -> Result<Self, Error> {
        match crate::sip::parser::parse_message(&bytes)? {
            SipMessage::Response(r) => Ok(r),
            SipMessage::Request(_) => {
                Err(Error::Unexpected("expected Response, got Request".into()))
            }
        }
    }
}

impl std::convert::TryFrom<&[u8]> for Response {
    type Error = Error;
    fn try_from(bytes: &[u8]) -> Result<Self, Error> {
        match crate::sip::parser::parse_message(bytes)? {
            SipMessage::Response(r) => Ok(r),
            SipMessage::Request(_) => {
                Err(Error::Unexpected("expected Response, got Request".into()))
            }
        }
    }
}

impl std::convert::TryFrom<&str> for Response {
    type Error = Error;
    fn try_from(s: &str) -> Result<Self, Error> {
        Self::try_from(s.as_bytes())
    }
}

impl std::convert::TryFrom<String> for Response {
    type Error = Error;
    fn try_from(s: String) -> Result<Self, Error> {
        Self::try_from(s.as_bytes())
    }
}

impl std::convert::From<Response> for String {
    fn from(r: Response) -> String {
        r.to_string()
    }
}

impl std::convert::From<Response> for Vec<u8> {
    fn from(r: Response) -> Vec<u8> {
        r.to_bytes()
    }
}

impl std::convert::TryFrom<bytes::Bytes> for SipMessage {
    type Error = Error;
    fn try_from(bytes: bytes::Bytes) -> Result<Self, Error> {
        crate::sip::parser::parse_message(&bytes)
    }
}

#[derive(Debug, PartialEq, Eq, Clone)]
pub enum SipMessage {
    Request(Request),
    Response(Response),
}

impl SipMessage {
    pub fn is_request(&self) -> bool {
        matches!(self, SipMessage::Request(_))
    }
    pub fn is_response(&self) -> bool {
        matches!(self, SipMessage::Response(_))
    }
}

impl HasHeaders for SipMessage {
    fn headers(&self) -> &Headers {
        match self {
            SipMessage::Request(r) => r.headers(),
            SipMessage::Response(r) => r.headers(),
        }
    }
    fn headers_mut(&mut self) -> &mut Headers {
        match self {
            SipMessage::Request(r) => r.headers_mut(),
            SipMessage::Response(r) => r.headers_mut(),
        }
    }
}

impl HeadersExt for SipMessage {}

impl std::fmt::Display for SipMessage {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            SipMessage::Request(r) => write!(f, "{}", r),
            SipMessage::Response(r) => write!(f, "{}", r),
        }
    }
}

impl SipMessage {
    /// Serialize the message to its on-the-wire byte representation.
    ///
    /// See [`Request::to_bytes`]. Transports should use this rather than
    /// `to_string()` so binary bodies are preserved byte-for-byte.
    pub fn to_bytes(&self) -> Vec<u8> {
        match self {
            SipMessage::Request(r) => r.to_bytes(),
            SipMessage::Response(r) => r.to_bytes(),
        }
    }
}

impl std::convert::TryFrom<Vec<u8>> for SipMessage {
    type Error = Error;
    fn try_from(bytes: Vec<u8>) -> Result<Self, Error> {
        crate::sip::parser::parse_message(&bytes)
    }
}

impl std::convert::TryFrom<&[u8]> for SipMessage {
    type Error = Error;
    fn try_from(bytes: &[u8]) -> Result<Self, Error> {
        crate::sip::parser::parse_message(bytes)
    }
}

impl std::convert::TryFrom<&str> for SipMessage {
    type Error = Error;
    fn try_from(s: &str) -> Result<Self, Error> {
        Self::try_from(s.as_bytes())
    }
}

impl std::convert::TryFrom<String> for SipMessage {
    type Error = Error;
    fn try_from(s: String) -> Result<Self, Error> {
        Self::try_from(s.as_bytes())
    }
}

impl std::convert::From<Request> for SipMessage {
    fn from(r: Request) -> SipMessage {
        SipMessage::Request(r)
    }
}

impl std::convert::From<Response> for SipMessage {
    fn from(r: Response) -> SipMessage {
        SipMessage::Response(r)
    }
}

impl std::convert::TryFrom<SipMessage> for Request {
    type Error = Error;
    fn try_from(m: SipMessage) -> Result<Self, Error> {
        match m {
            SipMessage::Request(r) => Ok(r),
            SipMessage::Response(_) => Err(Error::Unexpected("expected Request".into())),
        }
    }
}

impl std::convert::TryFrom<SipMessage> for Response {
    type Error = Error;
    fn try_from(m: SipMessage) -> Result<Self, Error> {
        match m {
            SipMessage::Response(r) => Ok(r),
            SipMessage::Request(_) => Err(Error::Unexpected("expected Response".into())),
        }
    }
}

impl std::convert::From<SipMessage> for String {
    fn from(m: SipMessage) -> String {
        m.to_string()
    }
}

impl std::convert::From<SipMessage> for Vec<u8> {
    fn from(m: SipMessage) -> Vec<u8> {
        m.to_bytes()
    }
}

#[cfg(test)]
mod tests {
    use super::{HasHeaders, HeadersExt, Request, Response, SipMessage};
    use crate::sip::{Header, Method};

    fn invite_request() -> &'static str {
        concat!(
            "INVITE sip:bob@restsend.com SIP/2.0\r\n",
            "Via: SIP/2.0/UDP ua.restsend.com;branch=z9hG4bK776asdhds\r\n",
            "Max-Forwards: 70\r\n",
            "To: Bob <sip:bob@restsend.com>\r\n",
            "From: Alice <sip:alice@restsend.com>;tag=1928301774\r\n",
            "Call-ID: a84b4c76e66710@ua.restsend.com\r\n",
            "CSeq: 314159 INVITE\r\n",
            "Contact: <sip:alice@ua.restsend.com>\r\n",
            "Content-Type: application/sdp\r\n",
            "Content-Length: 0\r\n",
            "\r\n"
        )
    }

    fn register_request() -> &'static str {
        concat!(
            "REGISTER sip:registrar.restsend.com SIP/2.0\r\n",
            "Via: SIP/2.0/UDP ua.restsend.com:5060;branch=z9hG4bKnashds8\r\n",
            "Max-Forwards: 70\r\n",
            "To: Bob <sip:bob@restsend.com>\r\n",
            "From: Bob <sip:bob@restsend.com>;tag=456248\r\n",
            "Call-ID: 843817637684230@998sdasdh09\r\n",
            "CSeq: 1826 REGISTER\r\n",
            "Contact: <sip:bob@192.0.2.4>\r\n",
            "Expires: 7200\r\n",
            "Content-Length: 0\r\n",
            "\r\n"
        )
    }

    fn ok_response() -> &'static str {
        concat!(
            "SIP/2.0 200 OK\r\n",
            "Via: SIP/2.0/UDP proxy1.restsend.com;branch=z9hG4bK4b43c2ff8.1;received=192.0.2.3\r\n",
            "Via: SIP/2.0/UDP proxy2.restsend.com;branch=z9hG4bK77ef4c2312983.1;received=192.0.2.2\r\n",
            "Via: SIP/2.0/UDP ua.restsend.com;branch=z9hG4bK776asdhds;received=192.0.2.1\r\n",
            "To: Bob <sip:bob@restsend.com>;tag=a6c85cf\r\n",
            "From: Alice <sip:alice@restsend.com>;tag=1928301774\r\n",
            "Call-ID: a84b4c76e66710@ua.restsend.com\r\n",
            "CSeq: 314159 INVITE\r\n",
            "Contact: <sip:bob@192.0.2.4>\r\n",
            "Content-Type: application/sdp\r\n",
            "Content-Length: 131\r\n",
            "\r\n",
            "v=0\r\no=bob 2890844527 2890844527 IN IP4 192.0.2.4\r\ns=-\r\nc=IN IP4 192.0.2.4\r\nt=0 0\r\nm=audio 3456 RTP/AVP 0\r\na=rtpmap:0 PCMU/8000"
        )
    }

    #[test]
    fn parse_invite_request() {
        let req: Request = invite_request().try_into().unwrap();
        assert_eq!(req.method, Method::Invite);
        assert_eq!(req.uri.to_string(), "sip:bob@restsend.com");
        assert_eq!(
            req.from_header().unwrap().value(),
            "Alice <sip:alice@restsend.com>;tag=1928301774"
        );
        assert_eq!(
            req.to_header().unwrap().value(),
            "Bob <sip:bob@restsend.com>"
        );
        assert_eq!(
            req.call_id_header().unwrap().value(),
            "a84b4c76e66710@ua.restsend.com"
        );
    }

    #[test]
    fn parse_register_request() {
        let req: Request = register_request().try_into().unwrap();
        assert_eq!(req.method, Method::Register);
        let contacts = req.typed_contact_headers().unwrap();
        assert_eq!(contacts.len(), 1);
        assert_eq!(contacts[0].uri.to_string(), "sip:bob@192.0.2.4");
    }

    #[test]
    fn parse_ok_response() {
        let resp: Response = ok_response().try_into().unwrap();
        assert_eq!(resp.status_code.code(), 200);
        assert_eq!(resp.body, b"v=0\r\no=bob 2890844527 2890844527 IN IP4 192.0.2.4\r\ns=-\r\nc=IN IP4 192.0.2.4\r\nt=0 0\r\nm=audio 3456 RTP/AVP 0\r\na=rtpmap:0 PCMU/8000");
    }

    #[test]
    fn request_binary_body_survives_serialization() {
        // A SIP message body is an opaque octet payload (RFC 3261 §7.4), not
        // necessarily UTF-8 text. e.g. an NG-eCall INVITE carries a binary
        // ASN.1 PER-encoded MSD. Serialization to the wire must preserve every
        // byte, including bytes that are invalid as UTF-8.
        let mut req: Request = invite_request().try_into().unwrap();
        let binary_body: Vec<u8> = vec![0x00, 0x01, 0x80, 0xFF, 0xC0, 0xC1, 0xFE, 0x02, 0x7F, 0x90];
        req.body = binary_body.clone();

        let wire: Vec<u8> = req.into();

        assert!(
            wire.ends_with(&binary_body),
            "binary body corrupted during serialization: got tail {:?}",
            &wire[wire.len().saturating_sub(binary_body.len() + 4)..]
        );
    }

    #[test]
    fn response_binary_body_survives_serialization() {
        let mut resp: Response = ok_response().try_into().unwrap();
        let binary_body: Vec<u8> = vec![0x00, 0x80, 0xFF, 0xC0, 0xC1, 0xFE, 0x02];
        resp.body = binary_body.clone();

        let wire: Vec<u8> = resp.into();

        assert!(
            wire.ends_with(&binary_body),
            "binary body corrupted during serialization"
        );
    }

    #[test]
    fn response_has_multiple_via_headers() {
        let resp: Response = ok_response().try_into().unwrap();
        let vias: Vec<_> = resp
            .headers()
            .iter()
            .filter(|h| matches!(h, Header::Via(_)))
            .collect();
        assert_eq!(vias.len(), 3);
    }

    #[test]
    fn top_via_header_returns_first_value_from_combined_header() {
        let resp: Response = concat!(
            "SIP/2.0 401 Unauthorized\r\n",
            "Via: SIP/2.0/UDP 172.22.22.80:5062;received=172.22.22.80;rport=5062;branch=z9hG4bKfirst,SIP/2.0/TCP 10.0.13.70:5060;branch=z9hG4bKsecond\r\n",
            "From: <sip:001010000000001@ims.example.com>;tag=e80c1d8c\r\n",
            "To: <sip:001010000000001@ims.example.com>;tag=2e518\r\n",
            "Call-ID: 9e353fc94f78064f@10.0.13.70\r\n",
            "CSeq: 1 REGISTER\r\n",
            "Content-Length: 0\r\n",
            "\r\n",
        )
        .try_into()
        .unwrap();

        assert_eq!(
            resp.via_header().unwrap().value(),
            "SIP/2.0/UDP 172.22.22.80:5062;received=172.22.22.80;rport=5062;branch=z9hG4bKfirst,SIP/2.0/TCP 10.0.13.70:5060;branch=z9hG4bKsecond"
        );
        assert_eq!(
            resp.top_via_header().unwrap().value(),
            "SIP/2.0/UDP 172.22.22.80:5062;received=172.22.22.80;rport=5062;branch=z9hG4bKfirst"
        );
    }

    #[test]
    fn headers_push_front() {
        let mut req: Request = invite_request().try_into().unwrap();
        let orig_first = req.headers.iter().next().unwrap().name().to_string();
        req.headers
            .push_front(Header::MaxForwards(crate::sip::MaxForwards::new("10")));
        assert_eq!(req.headers.iter().next().unwrap().name(), "Max-Forwards");
        assert_eq!(req.headers.iter().nth(1).unwrap().name(), orig_first);
    }

    #[test]
    fn new_headers_reason_roundtrip() {
        let msg: SipMessage = concat!(
            "BYE sip:alice@restsend.com SIP/2.0\r\n",
            "Via: SIP/2.0/UDP ua.restsend.com;branch=z9hG4bK776asdhds\r\n",
            "From: <sip:bob@restsend.com>;tag=a6c85cf\r\n",
            "To: <sip:alice@restsend.com>;tag=1928301774\r\n",
            "Call-ID: a84b4c76e66710@ua.restsend.com\r\n",
            "CSeq: 231 BYE\r\n",
            "Reason: SIP ;cause=200 ;text=\"Call completed elsewhere\"\r\n",
            "Content-Length: 0\r\n",
            "\r\n"
        )
        .try_into()
        .unwrap();
        let reason = msg.reason_header().unwrap();
        assert!(reason.value().contains("200"));
        assert!(reason.value().contains("Call completed elsewhere"));
    }

    #[test]
    fn new_headers_refer_to_roundtrip() {
        let msg: SipMessage = concat!(
            "REFER sip:bob@restsend.com SIP/2.0\r\n",
            "Via: SIP/2.0/UDP ua.restsend.com;branch=z9hG4bKkjshdyff\r\n",
            "To: <sip:bob@restsend.com>\r\n",
            "From: <sip:alice@restsend.com>;tag=xyz\r\n",
            "Call-ID: 12345600@ua.restsend.com\r\n",
            "CSeq: 1 REFER\r\n",
            "Refer-To: <sip:carol@restsend.com>\r\n",
            "Content-Length: 0\r\n",
            "\r\n"
        )
        .try_into()
        .unwrap();
        assert!(msg.refer_to_header().is_some());
        assert_eq!(
            msg.refer_to_header().unwrap().value(),
            "<sip:carol@restsend.com>"
        );
    }

    #[test]
    fn new_headers_session_expires_roundtrip() {
        let msg: SipMessage = concat!(
            "INVITE sip:bob@restsend.com SIP/2.0\r\n",
            "Via: SIP/2.0/UDP ua.restsend.com;branch=z9hG4bKtest\r\n",
            "From: <sip:alice@restsend.com>;tag=abc\r\n",
            "To: <sip:bob@restsend.com>\r\n",
            "Call-ID: test@ua.restsend.com\r\n",
            "CSeq: 1 INVITE\r\n",
            "Session-Expires: 1800;refresher=uac\r\n",
            "Min-SE: 90\r\n",
            "Content-Length: 0\r\n",
            "\r\n"
        )
        .try_into()
        .unwrap();
        let se = msg.session_expires_header().unwrap();
        assert!(se.value().contains("1800"));
        let min_se = msg.headers().iter().find_map(|h| {
            if let Header::MinSE(m) = h {
                Some(m)
            } else {
                None
            }
        });
        assert!(min_se.is_some());
        assert_eq!(min_se.unwrap().value(), "90");
    }

    #[test]
    fn new_headers_p_asserted_identity() {
        let msg: SipMessage = concat!(
            "INVITE sip:bob@restsend.com SIP/2.0\r\n",
            "Via: SIP/2.0/UDP ua.restsend.com;branch=z9hG4bKtest\r\n",
            "From: <sip:anonymous@anonymous.invalid>;tag=abc\r\n",
            "To: <sip:bob@restsend.com>\r\n",
            "Call-ID: pai-test@ua.restsend.com\r\n",
            "CSeq: 1 INVITE\r\n",
            "P-Asserted-Identity: <sip:alice@restsend.com>\r\n",
            "Privacy: id\r\n",
            "Content-Length: 0\r\n",
            "\r\n"
        )
        .try_into()
        .unwrap();
        assert!(msg.p_asserted_identity_header().is_some());
        assert_eq!(
            msg.p_asserted_identity_header().unwrap().value(),
            "<sip:alice@restsend.com>"
        );
        assert!(msg.privacy_header().is_some());
    }

    #[test]
    fn new_headers_replaces() {
        let msg: SipMessage = concat!(
            "INVITE sip:bob@restsend.com SIP/2.0\r\n",
            "Via: SIP/2.0/UDP ua.restsend.com;branch=z9hG4bKtest\r\n",
            "From: <sip:alice@restsend.com>;tag=abc\r\n",
            "To: <sip:bob@restsend.com>\r\n",
            "Call-ID: transfer-test@ua.restsend.com\r\n",
            "CSeq: 1 INVITE\r\n",
            "Replaces: original-call-id@ua.restsend.com;to-tag=orig-to;from-tag=orig-from\r\n",
            "Content-Length: 0\r\n",
            "\r\n"
        )
        .try_into()
        .unwrap();
        let replaces = msg.replaces_header().unwrap();
        assert!(replaces.value().contains("original-call-id"));
    }

    #[test]
    fn new_headers_rseq_rack() {
        let prack: SipMessage = concat!(
            "PRACK sip:bob@restsend.com SIP/2.0\r\n",
            "Via: SIP/2.0/UDP ua.restsend.com;branch=z9hG4bKtest\r\n",
            "From: <sip:alice@restsend.com>;tag=abc\r\n",
            "To: <sip:bob@restsend.com>;tag=xyz\r\n",
            "Call-ID: prack-test@ua.restsend.com\r\n",
            "CSeq: 2 PRACK\r\n",
            "RAck: 776656 1 INVITE\r\n",
            "Content-Length: 0\r\n",
            "\r\n"
        )
        .try_into()
        .unwrap();

        let rack = prack.rack_value().unwrap();
        assert_eq!(rack.0, 776656);
        assert_eq!(rack.1, 1);
        assert_eq!(rack.2, Method::Invite);

        let provisional: SipMessage = concat!(
            "SIP/2.0 183 Session Progress\r\n",
            "Via: SIP/2.0/UDP ua.restsend.com;branch=z9hG4bKtest\r\n",
            "From: <sip:alice@restsend.com>;tag=abc\r\n",
            "To: <sip:bob@restsend.com>;tag=xyz\r\n",
            "Call-ID: prack-test@ua.restsend.com\r\n",
            "CSeq: 1 INVITE\r\n",
            "RSeq: 776656\r\n",
            "Content-Length: 0\r\n",
            "\r\n"
        )
        .try_into()
        .unwrap();

        assert_eq!(provisional.rseq_value(), Some(776656));
    }

    #[test]
    fn new_headers_path_header() {
        let msg: SipMessage = concat!(
            "REGISTER sip:registrar.restsend.com SIP/2.0\r\n",
            "Via: SIP/2.0/TCP edge.restsend.com;branch=z9hG4bKtest\r\n",
            "From: <sip:alice@restsend.com>;tag=abc\r\n",
            "To: <sip:alice@restsend.com>\r\n",
            "Call-ID: path-test@edge.restsend.com\r\n",
            "CSeq: 1 REGISTER\r\n",
            "Path: <sip:edge.restsend.com;lr>\r\n",
            "Contact: <sip:alice@192.0.2.5:5060>\r\n",
            "Content-Length: 0\r\n",
            "\r\n"
        )
        .try_into()
        .unwrap();
        let paths = msg.path_headers();
        assert_eq!(paths.len(), 1);
        assert!(paths[0].value().contains("edge.restsend.com"));
    }

    #[test]
    fn header_value_helper_case_insensitive() {
        let req: Request = invite_request().try_into().unwrap();
        let val = req.header_value("content-type");
        assert!(val.is_some());
        assert!(val.unwrap().contains("application/sdp"));
    }

    #[test]
    fn header_contains_token_helper() {
        let msg: SipMessage = concat!(
            "INVITE sip:bob@restsend.com SIP/2.0\r\n",
            "Via: SIP/2.0/UDP ua.restsend.com;branch=z9hG4bKtest\r\n",
            "From: <sip:alice@restsend.com>;tag=abc\r\n",
            "To: <sip:bob@restsend.com>\r\n",
            "Call-ID: test@pc33\r\n",
            "CSeq: 1 INVITE\r\n",
            "Supported: timer, 100rel, replaces\r\n",
            "Content-Length: 0\r\n",
            "\r\n"
        )
        .try_into()
        .unwrap();
        assert!(msg.header_contains_token("supported", "timer"));
        assert!(msg.header_contains_token("supported", "100rel"));
        assert!(!msg.header_contains_token("supported", "gruu"));
        assert!(!msg.header_contains_token("content-type", "application/sdp"));
    }

    #[test]
    fn destination_from_request_without_route() {
        let req: Request = invite_request().try_into().unwrap();
        let dest = req.destination();
        assert_eq!(dest.to_string(), "sip:bob@restsend.com");
    }

    #[test]
    fn destination_from_request_with_route() {
        let req: Request = concat!(
            "INVITE sip:bob@restsend.com SIP/2.0\r\n",
            "Via: SIP/2.0/UDP ua.restsend.com;branch=z9hG4bKtest\r\n",
            "Route: <sip:proxy.restsend.com;lr>\r\n",
            "From: <sip:alice@restsend.com>;tag=abc\r\n",
            "To: <sip:bob@restsend.com>\r\n",
            "Call-ID: routed@pc33\r\n",
            "CSeq: 1 INVITE\r\n",
            "Content-Length: 0\r\n",
            "\r\n"
        )
        .try_into()
        .unwrap();
        let dest = req.destination();
        assert_eq!(dest.to_string(), "sip:proxy.restsend.com;lr");
    }

    #[test]
    fn compact_form_headers_are_parsed() {
        let message: SipMessage = concat!(
            "INVITE sip:bob@restsend.com SIP/2.0\r\n",
            "v: SIP/2.0/UDP restsend.com:5060;branch=z9hG4bK-1\r\n",
            "f: <sip:alice@restsend.com>;tag=123\r\n",
            "t: <sip:bob@restsend.com>\r\n",
            "i: call-id-1\r\n",
            "m: <sip:alice@restsend.com>\r\n",
            "e: gzip\r\n",
            "l: 0\r\n",
            "c: application/sdp\r\n",
            "s: hello\r\n",
            "k: timer\r\n",
            "\r\n"
        )
        .try_into()
        .unwrap();

        assert!(message.via_header().is_ok());
        assert!(message.from_header().is_ok());
        assert!(message.to_header().is_ok());
        assert!(message.call_id_header().is_ok());
        assert!(message.contact_header().is_ok());
        assert!(message
            .headers()
            .iter()
            .any(|header| matches!(header, Header::ContentEncoding(_))));
        assert!(message
            .headers()
            .iter()
            .any(|header| matches!(header, Header::ContentLength(_))));
        assert!(message
            .headers()
            .iter()
            .any(|header| matches!(header, Header::ContentType(_))));
        assert!(message
            .headers()
            .iter()
            .any(|header| matches!(header, Header::Subject(_))));
        assert!(message
            .headers()
            .iter()
            .any(|header| matches!(header, Header::Supported(_))));
    }

    #[test]
    fn compact_form_refer_to_alias_r() {
        let msg: SipMessage = concat!(
            "REFER sip:bob@restsend.com SIP/2.0\r\n",
            "Via: SIP/2.0/UDP ua.restsend.com;branch=z9hG4bKtest\r\n",
            "From: <sip:alice@restsend.com>;tag=abc\r\n",
            "To: <sip:bob@restsend.com>\r\n",
            "Call-ID: refer-compact@host\r\n",
            "CSeq: 1 REFER\r\n",
            "r: <sip:carol@restsend.com>\r\n",
            "Content-Length: 0\r\n",
            "\r\n"
        )
        .try_into()
        .unwrap();
        assert!(msg.refer_to_header().is_some());
    }

    #[test]
    fn compact_form_session_expires_alias_x() {
        let msg: SipMessage = concat!(
            "INVITE sip:bob@restsend.com SIP/2.0\r\n",
            "Via: SIP/2.0/UDP ua.restsend.com;branch=z9hG4bKtest\r\n",
            "From: <sip:alice@restsend.com>;tag=abc\r\n",
            "To: <sip:bob@restsend.com>\r\n",
            "Call-ID: se-compact@host\r\n",
            "CSeq: 1 INVITE\r\n",
            "x: 1800\r\n",
            "Content-Length: 0\r\n",
            "\r\n"
        )
        .try_into()
        .unwrap();
        assert!(msg.session_expires_header().is_some());
        assert_eq!(msg.session_expires_header().unwrap().value(), "1800");
    }
}
