use crate::sip::{Error, Method, Transport};
use std::convert::TryFrom;
use std::fmt;
use std::net::IpAddr;
use std::str::FromStr;

#[derive(Debug, PartialEq, Eq, Clone, Hash, Default)]
pub enum Scheme {
    #[default]
    Sip,
    Sips,
    Other(String),
}

impl fmt::Display for Scheme {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            Self::Sip => write!(f, "sip"),
            Self::Sips => write!(f, "sips"),
            Self::Other(s) => write!(f, "{}", s),
        }
    }
}

impl FromStr for Scheme {
    type Err = Error;
    fn from_str(s: &str) -> Result<Self, Self::Err> {
        match s.trim() {
            s if s.eq_ignore_ascii_case("sip") => Ok(Self::Sip),
            s if s.eq_ignore_ascii_case("sips") => Ok(Self::Sips),
            s => Ok(Self::Other(s.to_string())),
        }
    }
}

#[derive(Debug, PartialEq, Eq, Clone, Hash, Default)]
pub struct Auth {
    pub user: String,
    pub password: Option<String>,
}

impl fmt::Display for Auth {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match &self.password {
            Some(pw) => write!(
                f,
                "{}:{}",
                percent_encode_user(&self.user),
                percent_encode_password(pw)
            ),
            None => write!(f, "{}", percent_encode_user(&self.user)),
        }
    }
}

fn percent_encode_component(input: &str, allowed: &[u8]) -> String {
    let mut out = String::with_capacity(input.len());
    for byte in input.as_bytes() {
        if byte.is_ascii_alphanumeric() || allowed.contains(byte) {
            out.push(char::from(*byte));
        } else {
            out.push('%');
            out.push_str(&format!("{:02X}", byte));
        }
    }
    out
}

fn percent_encode_user(input: &str) -> String {
    percent_encode_component(input, b"-_.!~*'()&=+$,;?/")
}

fn percent_encode_password(input: &str) -> String {
    percent_encode_component(input, b"-_.!~*'()&=+$,")
}

impl<S: Into<String>> From<S> for Auth {
    fn from(s: S) -> Self {
        let s = s.into();
        if let Some(idx) = s.find(':') {
            Auth {
                user: s[..idx].to_string(),
                password: Some(s[idx + 1..].to_string()),
            }
        } else {
            Auth {
                user: s,
                password: None,
            }
        }
    }
}

#[derive(Debug, PartialEq, Eq, Clone, Hash)]
pub enum Host {
    Domain(Domain),
    IpAddr(IpAddr),
}

impl fmt::Display for Host {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            Self::Domain(d) => write!(f, "{}", d),
            Self::IpAddr(ip @ IpAddr::V6(_)) => write!(f, "[{}]", ip),
            Self::IpAddr(ip) => write!(f, "{}", ip),
        }
    }
}

impl From<Domain> for Host {
    fn from(d: Domain) -> Self {
        Self::Domain(d)
    }
}

impl From<IpAddr> for Host {
    fn from(ip: IpAddr) -> Self {
        Self::IpAddr(ip)
    }
}

impl FromStr for Host {
    type Err = Error;
    fn from_str(s: &str) -> Result<Self, Self::Err> {
        let s = s.trim();
        if let Ok(ip) = s.parse::<IpAddr>() {
            Ok(Host::IpAddr(ip))
        } else {
            Ok(Host::Domain(Domain::from(s)))
        }
    }
}

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

impl TryFrom<Host> for IpAddr {
    type Error = Error;
    fn try_from(h: Host) -> Result<Self, Self::Error> {
        match h {
            Host::IpAddr(ip) => Ok(ip),
            Host::Domain(d) => d.0.parse::<IpAddr>().map_err(Into::into),
        }
    }
}

#[derive(Debug, PartialEq, Eq, Clone, Hash)]
pub struct Domain(pub String);

impl fmt::Display for Domain {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "{}", self.0)
    }
}

impl<S: Into<String>> From<S> for Domain {
    fn from(s: S) -> Self {
        Self(s.into())
    }
}

impl From<Domain> for HostWithPort {
    fn from(d: Domain) -> Self {
        HostWithPort {
            host: Host::Domain(d),
            port: None,
        }
    }
}

pub use crate::sip::transport::Port;
#[derive(Debug, PartialEq, Eq, Clone, Hash)]
pub struct HostWithPort {
    pub host: Host,
    pub port: Option<Port>,
}

impl Default for HostWithPort {
    fn default() -> Self {
        Self {
            host: Host::Domain(Domain::from("localhost")),
            port: None,
        }
    }
}

impl fmt::Display for HostWithPort {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match &self.port {
            Some(port) => write!(f, "{}:{}", self.host, port),
            None => write!(f, "{}", self.host),
        }
    }
}

impl From<std::net::SocketAddr> for HostWithPort {
    fn from(sa: std::net::SocketAddr) -> Self {
        Self {
            host: Host::IpAddr(sa.ip()),
            port: Some(Port(sa.port())),
        }
    }
}

impl From<IpAddr> for HostWithPort {
    fn from(ip: IpAddr) -> Self {
        Self {
            host: Host::IpAddr(ip),
            port: None,
        }
    }
}

impl TryFrom<HostWithPort> for std::net::SocketAddr {
    type Error = Error;
    fn try_from(h: HostWithPort) -> Result<Self, Self::Error> {
        let port = h.port.map(|p| p.0).unwrap_or(5060);
        match h.host {
            Host::IpAddr(ip) => Ok(std::net::SocketAddr::new(ip, port)),
            Host::Domain(d) => {
                let ip: IpAddr = d.0.parse()?;
                Ok(std::net::SocketAddr::new(ip, port))
            }
        }
    }
}

impl TryFrom<&str> for HostWithPort {
    type Error = Error;
    fn try_from(s: &str) -> Result<Self, Self::Error> {
        parse_host_with_port(s.trim())
    }
}

impl TryFrom<String> for HostWithPort {
    type Error = Error;
    fn try_from(s: String) -> Result<Self, Self::Error> {
        parse_host_with_port(s.trim())
    }
}

fn parse_host_with_port(s: &str) -> Result<HostWithPort, Error> {
    if s.starts_with('[') {
        if let Some(close) = s.find(']') {
            let ip_str = &s[1..close];
            let ip: IpAddr = ip_str
                .parse()
                .map_err(|_| Error::ParseError(format!("invalid IPv6: {}", ip_str)))?;
            let port = if s.len() > close + 1 && s.as_bytes()[close + 1] == b':' {
                Some(Port(s[close + 2..].parse()?))
            } else {
                None
            };
            return Ok(HostWithPort {
                host: Host::IpAddr(ip),
                port,
            });
        }
    }

    if let Some(colon_pos) = s.rfind(':') {
        let after = &s[colon_pos + 1..];
        if after.chars().all(|c| c.is_ascii_digit()) {
            let port: u16 = after
                .parse()
                .map_err(|_| Error::ParseError(format!("invalid port: {}", after)))?;
            let host_str = &s[..colon_pos];
            let host: Host = host_str.parse()?;
            return Ok(HostWithPort {
                host,
                port: Some(Port(port)),
            });
        }
    }

    let host: Host = s.parse()?;
    Ok(HostWithPort { host, port: None })
}

#[derive(Debug, PartialEq, Eq, Clone, Hash)]
pub enum Param {
    Transport(Transport),
    User(User),
    Method(Method),
    Ttl(Ttl),
    Maddr(Maddr),
    Lr,
    Ob,
    Rport(Option<u16>),
    Branch(Branch),
    Received(Received),
    Tag(Tag),
    Expires(Expires),
    Q(Q),
    Other(OtherParam, Option<OtherParamValue>),
}

impl fmt::Display for Param {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            Self::Transport(t) => write!(f, ";transport={}", t),
            Self::User(u) => write!(f, ";user={}", u),
            Self::Method(m) => write!(f, ";method={}", m),
            Self::Ttl(t) => write!(f, ";ttl={}", t),
            Self::Maddr(m) => write!(f, ";maddr={}", m),
            Self::Lr => write!(f, ";lr"),
            Self::Ob => write!(f, ";ob"),
            Self::Rport(None) => write!(f, ";rport"),
            Self::Rport(Some(p)) => write!(f, ";rport={}", p),
            Self::Branch(b) => write!(f, ";branch={}", b),
            Self::Received(r) => write!(f, ";received={}", r),
            Self::Tag(t) => write!(f, ";tag={}", t),
            Self::Expires(e) => write!(f, ";expires={}", e),
            Self::Q(q) => write!(f, ";q={}", q),
            Self::Other(name, Some(val)) => write!(f, ";{}={}", name, val),
            Self::Other(name, None) => write!(f, ";{}", name),
        }
    }
}

impl TryFrom<(&str, Option<&str>)> for Param {
    type Error = Error;
    fn try_from((name, value): (&str, Option<&str>)) -> Result<Self, Self::Error> {
        match (name, value) {
            (n, Some(v)) if n.eq_ignore_ascii_case("transport") => Ok(Param::Transport(v.parse()?)),
            (n, Some(v)) if n.eq_ignore_ascii_case("user") => Ok(Param::User(User::new(v))),
            (n, Some(v)) if n.eq_ignore_ascii_case("method") => Ok(Param::Method(v.parse()?)),
            (n, Some(v)) if n.eq_ignore_ascii_case("ttl") => Ok(Param::Ttl(Ttl::new(v))),
            (n, Some(v)) if n.eq_ignore_ascii_case("maddr") => Ok(Param::Maddr(Maddr::new(v))),
            (n, Some(v)) if n.eq_ignore_ascii_case("branch") => Ok(Param::Branch(Branch::new(v))),
            (n, Some(v)) if n.eq_ignore_ascii_case("received") => {
                Ok(Param::Received(Received::new(v)))
            }
            (n, Some(v)) if n.eq_ignore_ascii_case("tag") => Ok(Param::Tag(Tag::new(v))),
            (n, Some(v)) if n.eq_ignore_ascii_case("expires") => {
                Ok(Param::Expires(Expires::new(v)))
            }
            (n, Some(v)) if n.eq_ignore_ascii_case("q") => Ok(Param::Q(Q::new(v))),
            (n, None) if n.eq_ignore_ascii_case("lr") => Ok(Param::Lr),
            (n, None) if n.eq_ignore_ascii_case("ob") => Ok(Param::Ob),
            (n, None) if n.eq_ignore_ascii_case("rport") => Ok(Param::Rport(None)),
            (n, Some(v)) if n.eq_ignore_ascii_case("rport") => {
                let port = v
                    .parse::<u16>()
                    .map_err(|_| Error::ParseError(format!("invalid rport: {}", v)))?;
                Ok(Param::Rport(Some(port)))
            }
            (n, v) => Ok(Param::Other(
                OtherParam::new(n),
                v.map(OtherParamValue::new),
            )),
        }
    }
}

impl FromStr for Param {
    type Err = Error;
    fn from_str(s: &str) -> Result<Self, Self::Err> {
        let s = s.trim_start_matches(';').trim();
        if let Some(eq) = s.find('=') {
            let name = &s[..eq];
            let value = &s[eq + 1..];
            Param::try_from((name, Some(value)))
        } else {
            Param::try_from((s, None))
        }
    }
}

macro_rules! string_newtype {
    ($name:ident) => {
        #[derive(Debug, PartialEq, Eq, Clone, Default, Hash)]
        pub struct $name(pub String);

        impl $name {
            pub fn new(s: impl Into<String>) -> Self {
                Self(s.into())
            }
            pub fn value(&self) -> &str {
                &self.0
            }
        }

        impl fmt::Display for $name {
            fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
                write!(f, "{}", self.0)
            }
        }

        impl From<String> for $name {
            fn from(s: String) -> Self {
                Self(s)
            }
        }
        impl From<&str> for $name {
            fn from(s: &str) -> Self {
                Self(s.to_string())
            }
        }
        impl From<$name> for String {
            fn from(s: $name) -> String {
                s.0
            }
        }
        impl std::ops::Deref for $name {
            type Target = str;
            fn deref(&self) -> &str {
                &self.0
            }
        }
    };
}

string_newtype!(Branch);
string_newtype!(Received);
string_newtype!(Tag);
string_newtype!(Expires);
string_newtype!(Q);
string_newtype!(User);
string_newtype!(Ttl);
string_newtype!(Maddr);
string_newtype!(OtherParam);
string_newtype!(OtherParamValue);

impl Received {
    pub fn parse(&self) -> Result<IpAddr, std::net::AddrParseError> {
        self.0.parse()
    }
}

impl From<Tag> for Param {
    fn from(t: Tag) -> Self {
        Param::Tag(t)
    }
}

impl From<Branch> for Param {
    fn from(b: Branch) -> Self {
        Param::Branch(b)
    }
}

impl From<Received> for Param {
    fn from(r: Received) -> Self {
        Param::Received(r)
    }
}

#[derive(Debug, PartialEq, Eq, Clone, Default, Hash)]
pub struct Uri {
    pub scheme: Option<Scheme>,
    pub auth: Option<Auth>,
    pub host_with_port: HostWithPort,
    pub params: Vec<Param>,
    pub headers: Vec<(String, String)>,
}

impl fmt::Display for Uri {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        if let Some(scheme) = &self.scheme {
            write!(f, "{}:", scheme)?;
        }
        if let Some(auth) = &self.auth {
            write!(f, "{}@", auth)?;
        }
        write!(f, "{}", self.host_with_port)?;
        for param in &self.params {
            write!(f, "{}", param)?;
        }
        if !self.headers.is_empty() {
            write!(f, "?")?;
            let parts: Vec<_> = self
                .headers
                .iter()
                .map(|(k, v)| format!("{}={}", k, v))
                .collect();
            write!(f, "{}", parts.join("&"))?;
        }
        Ok(())
    }
}

impl Uri {
    pub fn user(&self) -> Option<&str> {
        self.auth.as_ref().map(|a| a.user.as_str())
    }
    pub fn host(&self) -> &Host {
        &self.host_with_port.host
    }
}

impl TryFrom<&str> for Uri {
    type Error = Error;
    fn try_from(s: &str) -> Result<Self, Self::Error> {
        parse_uri(s.trim())
    }
}

impl From<HostWithPort> for Uri {
    fn from(hwp: HostWithPort) -> Self {
        Uri {
            scheme: Some(Scheme::Sip),
            auth: None,
            host_with_port: hwp,
            params: Vec::new(),
            headers: Vec::new(),
        }
    }
}

impl TryFrom<String> for Uri {
    type Error = Error;
    fn try_from(s: String) -> Result<Self, Self::Error> {
        parse_uri(s.trim())
    }
}

impl FromStr for Uri {
    type Err = Error;
    fn from_str(s: &str) -> Result<Self, Self::Err> {
        parse_uri(s.trim())
    }
}

pub fn parse_uri(s: &str) -> Result<Uri, Error> {
    let s = s.trim();
    let s = s.trim_start_matches('<');
    let s = s.trim_end_matches('>');
    let s = s.trim();

    let (main, hdrs_str) = if let Some(q) = s.find('?') {
        (&s[..q], Some(&s[q + 1..]))
    } else {
        (s, None)
    };

    let (host_part, params_str) = split_at_first_semicolon(main);

    let (scheme, user_host) = if let Some(colon) = host_part.find(':') {
        let potential_scheme = &host_part[..colon];
        if potential_scheme.chars().all(|c| c.is_ascii_alphabetic()) {
            (
                Some(potential_scheme.parse::<Scheme>()?),
                &host_part[colon + 1..],
            )
        } else {
            (None, host_part)
        }
    } else {
        (None, host_part)
    };

    let (auth, host_str) = if let Some(at) = user_host.rfind('@') {
        (Some(Auth::from(&user_host[..at])), &user_host[at + 1..])
    } else {
        (None, user_host)
    };

    let host_with_port = parse_host_with_port(host_str)?;

    let params = parse_params(params_str.unwrap_or(""))?;

    let headers = if let Some(h) = hdrs_str {
        h.split('&')
            .filter_map(|kv| {
                let mut parts = kv.splitn(2, '=');
                let k = parts.next()?.to_string();
                let v = parts.next().unwrap_or("").to_string();
                Some((k, v))
            })
            .collect()
    } else {
        vec![]
    };

    Ok(Uri {
        scheme,
        auth,
        host_with_port,
        params,
        headers,
    })
}

fn split_at_first_semicolon(s: &str) -> (&str, Option<&str>) {
    if let Some(pos) = s.find(';') {
        (&s[..pos], Some(&s[pos + 1..]))
    } else {
        (s, None)
    }
}

pub fn parse_params(s: &str) -> Result<Vec<Param>, Error> {
    if s.is_empty() {
        return Ok(vec![]);
    }
    let mut params = Vec::new();
    for part in s.split(';') {
        let part = part.trim();
        if part.is_empty() {
            continue;
        }
        let param = part.parse::<Param>()?;
        params.push(param);
    }
    Ok(params)
}

pub type UriWithParams = Uri;
pub type UriWithParamsList = Vec<Uri>;

pub trait ParamsExt {
    fn params(&self) -> &[Param];
    fn params_mut(&mut self) -> &mut Vec<Param>;

    fn tag(&self) -> Option<&str> {
        self.params().iter().find_map(|p| {
            if let Param::Tag(t) = p {
                Some(t.value())
            } else {
                None
            }
        })
    }
    fn branch(&self) -> Option<&str> {
        self.params().iter().find_map(|p| {
            if let Param::Branch(b) = p {
                Some(b.value())
            } else {
                None
            }
        })
    }
    fn transport(&self) -> Option<&Transport> {
        self.params().iter().find_map(|p| {
            if let Param::Transport(t) = p {
                Some(t)
            } else {
                None
            }
        })
    }
    fn received(&self) -> Option<&str> {
        self.params().iter().find_map(|p| {
            if let Param::Received(r) = p {
                Some(r.value())
            } else {
                None
            }
        })
    }
    fn rport(&self) -> Option<Option<u16>> {
        self.params().iter().find_map(|p| {
            if let Param::Rport(r) = p {
                Some(*r)
            } else {
                None
            }
        })
    }
    fn has_lr(&self) -> bool {
        self.params().iter().any(|p| {
            matches!(p, Param::Lr)
                || matches!(p, Param::Other(name, Some(value))
                    if name.value().eq_ignore_ascii_case("lr")
                        && value.value().eq_ignore_ascii_case("on"))
        })
    }
    fn expires(&self) -> Option<&str> {
        self.params().iter().find_map(|p| {
            if let Param::Expires(e) = p {
                Some(e.value())
            } else {
                None
            }
        })
    }
    fn q(&self) -> Option<&str> {
        self.params().iter().find_map(|p| {
            if let Param::Q(q) = p {
                Some(q.value())
            } else {
                None
            }
        })
    }
    fn other_param(&self, name: &str) -> Option<&str> {
        self.params().iter().find_map(|p| {
            if let Param::Other(n, v) = p {
                if n.value().eq_ignore_ascii_case(name) {
                    return Some(v.as_ref().map(|v| v.value()).unwrap_or(""));
                }
            }
            None
        })
    }

    fn set_tag(&mut self, tag: impl Into<String>) {
        let new = Param::Tag(Tag::new(tag));
        let params = self.params_mut();
        if let Some(p) = params.iter_mut().find(|p| matches!(p, Param::Tag(_))) {
            *p = new;
        } else {
            params.push(new);
        }
    }
    fn set_branch(&mut self, branch: impl Into<String>) {
        let new = Param::Branch(Branch::new(branch));
        let params = self.params_mut();
        if let Some(p) = params.iter_mut().find(|p| matches!(p, Param::Branch(_))) {
            *p = new;
        } else {
            params.push(new);
        }
    }
    fn set_transport(&mut self, t: Transport) {
        let new = Param::Transport(t);
        let params = self.params_mut();
        if let Some(p) = params.iter_mut().find(|p| matches!(p, Param::Transport(_))) {
            *p = new;
        } else {
            params.push(new);
        }
    }
    fn set_rport(&mut self, port: Option<u16>) {
        let new = Param::Rport(port);
        let params = self.params_mut();
        if let Some(p) = params.iter_mut().find(|p| matches!(p, Param::Rport(_))) {
            *p = new;
        } else {
            params.push(new);
        }
    }
    fn set_expires(&mut self, value: impl Into<String>) {
        let new = Param::Expires(Expires::new(value));
        let params = self.params_mut();
        if let Some(p) = params.iter_mut().find(|p| matches!(p, Param::Expires(_))) {
            *p = new;
        } else {
            params.push(new);
        }
    }
    fn set_other_param(&mut self, name: &str, value: Option<&str>) {
        let new = Param::Other(OtherParam::new(name), value.map(OtherParamValue::new));
        let params = self.params_mut();
        if let Some(p) = params.iter_mut().find(|p| {
            if let Param::Other(n, _) = p {
                n.value().eq_ignore_ascii_case(name)
            } else {
                false
            }
        }) {
            *p = new;
        } else {
            params.push(new);
        }
    }

    fn remove_tag(&mut self) {
        self.params_mut().retain(|p| !matches!(p, Param::Tag(_)));
    }
    fn remove_branch(&mut self) {
        self.params_mut().retain(|p| !matches!(p, Param::Branch(_)));
    }
    fn remove_transport(&mut self) {
        self.params_mut()
            .retain(|p| !matches!(p, Param::Transport(_)));
    }
    fn remove_rport(&mut self) {
        self.params_mut().retain(|p| !matches!(p, Param::Rport(_)));
    }
    fn remove_lr(&mut self) {
        self.params_mut().retain(|p| !matches!(p, Param::Lr));
    }
    fn remove_param(&mut self, name: &str) {
        self.params_mut().retain(|p| {
            if let Param::Other(n, _) = p {
                !n.value().eq_ignore_ascii_case(name)
            } else {
                true
            }
        });
    }

    fn pop_tag(&mut self) -> Option<String> {
        let params = self.params_mut();
        let pos = params.iter().position(|p| matches!(p, Param::Tag(_)))?;
        if let Param::Tag(t) = params.remove(pos) {
            Some(t.0)
        } else {
            None
        }
    }
    fn pop_branch(&mut self) -> Option<String> {
        let params = self.params_mut();
        let pos = params.iter().position(|p| matches!(p, Param::Branch(_)))?;
        if let Param::Branch(b) = params.remove(pos) {
            Some(b.0)
        } else {
            None
        }
    }
}

impl ParamsExt for Vec<Param> {
    fn params(&self) -> &[Param] {
        self.as_slice()
    }
    fn params_mut(&mut self) -> &mut Vec<Param> {
        self
    }
}

impl ParamsExt for Uri {
    fn params(&self) -> &[Param] {
        &self.params
    }
    fn params_mut(&mut self) -> &mut Vec<Param> {
        &mut self.params
    }
}

#[cfg(test)]
mod tests {
    use super::{parse_uri, Host, Param, ParamsExt, Scheme};

    #[test]
    fn uri_param_value_keeps_colons_and_following_params() {
        let uri = parse_uri(
            "sip:82.202.218.130;lr=on;ftag=d4nwJ0jF;du=sip:95.143.188.49:5060;did=893.d6d1",
        )
        .unwrap();
        assert_eq!(
            uri.to_string(),
            "sip:82.202.218.130;lr=on;ftag=d4nwJ0jF;du=sip:95.143.188.49:5060;did=893.d6d1"
        );
        assert!(uri.params.iter().any(|param| matches!(param, Param::Other(name, Some(value)) if name.value().eq_ignore_ascii_case("du") && value.value() == "sip:95.143.188.49:5060")));
        assert!(uri.params.iter().any(|param| matches!(param, Param::Other(name, Some(value)) if name.value().eq_ignore_ascii_case("did") && value.value() == "893.d6d1")));
    }

    #[test]
    fn uri_preserves_username_with_period() {
        let uri = parse_uri("sip:alice.smith@restsend.com").unwrap();
        assert_eq!(uri.auth.unwrap().user, "alice.smith");
    }

    #[test]
    fn uri_display_percent_encodes_user_and_password() {
        let uri = parse_uri("sip:al ice:pa:ss@restsend.com").unwrap();
        assert_eq!(uri.to_string(), "sip:al%20ice:pa%3Ass@restsend.com");
    }

    #[test]
    fn user_param_is_parsed_as_uri_param() {
        let uri = parse_uri("sip:alice@restsend.com;user=phone").unwrap();
        assert!(matches!(uri.params.first(), Some(Param::User(value)) if value.value() == "phone"));
    }

    #[test]
    fn uri_rport_without_value() {
        let uri = parse_uri("sip:alice@restsend.com;rport").unwrap();
        assert!(matches!(uri.params.first(), Some(Param::Rport(None))));
        assert_eq!(uri.to_string(), "sip:alice@restsend.com;rport");
    }

    #[test]
    fn uri_rport_with_value() {
        let uri = parse_uri("sip:alice@restsend.com;rport=51372").unwrap();
        assert!(matches!(
            uri.params.first(),
            Some(Param::Rport(Some(51372)))
        ));
        assert_eq!(uri.to_string(), "sip:alice@restsend.com;rport=51372");
    }

    #[test]
    fn uri_rport_with_other_params() {
        let uri = parse_uri("sip:alice@ua.restsend.com;transport=tcp;rport").unwrap();
        assert!(uri.params.iter().any(|p| matches!(p, Param::Rport(None))));
        assert!(uri.params.iter().any(|p| matches!(p, Param::Transport(_))));
    }

    #[test]
    fn uri_branch_param() {
        let uri = parse_uri("sip:proxy.restsend.com;branch=z9hG4bK776asdhds").unwrap();
        assert!(
            matches!(uri.params.first(), Some(Param::Branch(b)) if b.value() == "z9hG4bK776asdhds")
        );
    }

    #[test]
    fn uri_lr_flag_param() {
        let uri = parse_uri("sip:proxy.restsend.com;lr").unwrap();
        assert!(matches!(uri.params.first(), Some(Param::Lr)));
        assert_eq!(uri.to_string(), "sip:proxy.restsend.com;lr");
    }

    #[test]
    fn uri_lr_on_is_treated_as_loose_route() {
        let uri = parse_uri("sip:proxy.restsend.com;lr=on").unwrap();
        assert!(uri.has_lr());
        assert_eq!(uri.to_string(), "sip:proxy.restsend.com;lr=on");
    }

    #[test]
    fn uri_sips_scheme() {
        let uri = parse_uri("sips:alice@restsend.com").unwrap();
        assert_eq!(uri.scheme, Some(Scheme::Sips));
        assert_eq!(uri.to_string(), "sips:alice@restsend.com");
    }

    #[test]
    fn uri_ipv4_host() {
        let uri = parse_uri("sip:bob@192.0.2.4").unwrap();
        assert!(matches!(uri.host_with_port.host, Host::IpAddr(_)));
        assert_eq!(uri.to_string(), "sip:bob@192.0.2.4");
    }

    #[test]
    fn uri_ipv4_host_with_port() {
        let uri = parse_uri("sip:alice@192.0.2.1:5060").unwrap();
        assert!(uri.host_with_port.port.is_some());
        assert_eq!(uri.to_string(), "sip:alice@192.0.2.1:5060");
    }

    #[test]
    fn uri_ipv6_host() {
        let uri = parse_uri("sip:alice@[2001:db8::1]").unwrap();
        assert!(matches!(uri.host_with_port.host, Host::IpAddr(_)));
        assert_eq!(uri.to_string(), "sip:alice@[2001:db8::1]");
    }

    #[test]
    fn uri_ipv6_host_with_port() {
        let uri = parse_uri("sip:alice@[2001:db8::1]:5060").unwrap();
        assert!(uri.host_with_port.port.is_some());
        assert_eq!(uri.to_string(), "sip:alice@[2001:db8::1]:5060");
    }

    #[test]
    fn uri_transport_tcp_param() {
        use crate::sip::Transport;
        let uri = parse_uri("sip:alice@restsend.com;transport=tcp").unwrap();
        assert!(matches!(
            uri.params.first(),
            Some(Param::Transport(Transport::Tcp))
        ));
        assert_eq!(uri.to_string(), "sip:alice@restsend.com;transport=TCP");
    }

    #[test]
    fn uri_transport_tls_param() {
        use crate::sip::Transport;
        let uri = parse_uri("sip:alice@restsend.com;transport=tls").unwrap();
        assert!(matches!(
            uri.params.first(),
            Some(Param::Transport(Transport::Tls))
        ));
    }

    #[test]
    fn uri_roundtrip_complex() {
        let s = "sip:alice@restsend.com;transport=tcp;tag=1928301774";
        let uri = parse_uri(s).unwrap();
        assert_eq!(
            uri.to_string(),
            "sip:alice@restsend.com;transport=TCP;tag=1928301774"
        );
    }

    #[test]
    fn uri_host_only_no_user() {
        let uri = parse_uri("sip:restsend.com").unwrap();
        assert!(uri.auth.is_none());
        assert_eq!(uri.host_with_port.host, Host::Domain("restsend.com".into()));
    }

    #[test]
    fn uri_with_headers() {
        let uri = parse_uri("sip:alice@restsend.com?subject=project&priority=urgent").unwrap();
        assert_eq!(uri.headers.len(), 2);
        assert_eq!(uri.headers[0], ("subject".into(), "project".into()));
        assert_eq!(uri.headers[1], ("priority".into(), "urgent".into()));
    }

    #[test]
    fn uri_maddr_param() {
        let uri = parse_uri("sip:alice@restsend.com;maddr=239.255.255.1").unwrap();
        assert!(
            matches!(uri.params.first(), Some(Param::Maddr(m)) if m.value() == "239.255.255.1")
        );
    }

    #[test]
    fn uri_ttl_param() {
        let uri = parse_uri("sip:alice@restsend.com;ttl=15").unwrap();
        assert!(matches!(uri.params.first(), Some(Param::Ttl(t)) if t.value() == "15"));
    }

    #[test]
    fn uri_tag_param() {
        let uri = parse_uri("sip:alice@restsend.com;tag=1928301774").unwrap();
        assert!(matches!(uri.params.first(), Some(Param::Tag(t)) if t.value() == "1928301774"));
    }

    #[test]
    fn uri_multiple_params() {
        let uri = parse_uri("sip:alice@restsend.com;transport=tcp;tag=1928301774;lr").unwrap();
        assert_eq!(uri.params.len(), 3);
    }

    #[test]
    fn uri_password_auth() {
        let uri = parse_uri("sip:alice:secret@restsend.com").unwrap();
        let auth = uri.auth.unwrap();
        assert_eq!(auth.user, "alice");
        assert_eq!(auth.password, Some("secret".into()));
    }

    #[test]
    fn uri_anonymous() {
        let uri = parse_uri("sip:anonymous@anonymous.invalid").unwrap();
        assert_eq!(uri.auth.unwrap().user, "anonymous");
    }
}
