//! SIP URI and SDP body rewriting helpers.
//!
//! Ported from ts/sip/rewrite.ts.

use crate::{Endpoint, SdpMediaKind};

/// Replaces the host:port in every `sip:` / `sips:` URI found in `value`.
pub fn rewrite_sip_uri(value: &str, host: &str, port: u16) -> String {
    let mut result = String::with_capacity(value.len());
    let mut i = 0;
    let bytes = value.as_bytes();

    while i < bytes.len() {
        // Look for "sip:" or "sips:"
        let scheme_len = if i + 4 <= bytes.len()
            && (bytes[i..].starts_with(b"sip:") || bytes[i..].starts_with(b"SIP:"))
        {
            4
        } else if i + 5 <= bytes.len()
            && (bytes[i..].starts_with(b"sips:") || bytes[i..].starts_with(b"SIPS:"))
        {
            5
        } else {
            result.push(value[i..].chars().next().unwrap());
            i += value[i..].chars().next().unwrap().len_utf8();
            continue;
        };

        let scheme = &value[i..i + scheme_len];
        let rest = &value[i + scheme_len..];

        // Check for userpart (contains '@')
        let (userpart, host_start) = if let Some(at) = rest.find('@') {
            // Make sure @ comes before any delimiters
            let delim = rest.find(|c: char| c == '>' || c == ';' || c == ',' || c.is_whitespace());
            if delim.is_none() || at < delim.unwrap() {
                (&rest[..=at], at + 1)
            } else {
                ("", 0)
            }
        } else {
            ("", 0)
        };

        // Find the end of the host:port portion
        let host_rest = &rest[host_start..];
        let end = host_rest
            .find(|c: char| c == '>' || c == ';' || c == ',' || c.is_whitespace())
            .unwrap_or(host_rest.len());

        result.push_str(scheme);
        result.push_str(userpart);
        result.push_str(&format!("{host}:{port}"));
        i += scheme_len + host_start + end;
    }

    result
}

/// Rewrites the connection address (`c=`) and first supported media port
/// (`m=audio`, `m=image`, `m=application`) in an SDP body. Returns the
/// rewritten body together with the original endpoint that was replaced (if any).
pub fn rewrite_sdp(body: &str, ip: &str, port: u16) -> (String, Option<Endpoint>) {
    let mut orig_addr: Option<String> = None;
    let mut orig_media: Option<(SdpMediaKind, u16, String)> = None;

    let lines: Vec<String> = body
        .replace("\r\n", "\n")
        .split('\n')
        .map(|line| {
            if let Some(rest) = line.strip_prefix("c=IN IP4 ") {
                orig_addr = Some(rest.trim().to_string());
                format!("c=IN IP4 {ip}")
            } else if line.starts_with("m=audio ")
                || line.starts_with("m=image ")
                || line.starts_with("m=application ")
            {
                let parts: Vec<&str> = line.split(' ').collect();
                if parts.len() >= 2 {
                    let media_kind = parts[0]
                        .strip_prefix("m=")
                        .map(SdpMediaKind::from_sdp_token)
                        .unwrap_or(SdpMediaKind::Unknown);
                    if orig_media.is_none() {
                        orig_media = parts[1].parse().ok().map(|orig_port| {
                            (
                                media_kind,
                                orig_port,
                                parts.get(2).copied().unwrap_or("").to_string(),
                            )
                        });
                    }
                    let mut rebuilt = parts[0].to_string();
                    rebuilt.push(' ');
                    rebuilt.push_str(&port.to_string());
                    for part in &parts[2..] {
                        rebuilt.push(' ');
                        rebuilt.push_str(part);
                    }
                    return rebuilt;
                }
                line.to_string()
            } else {
                line.to_string()
            }
        })
        .collect();

    let original = match (orig_addr, orig_media) {
        (Some(a), Some((media_kind, p, transport))) => Some(Endpoint {
            address: a,
            port: p,
            codec_pt: None,
            media_kind,
            transport,
        }),
        _ => None,
    };

    (lines.join("\r\n"), original)
}

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

    #[test]
    fn test_rewrite_sip_uri() {
        let input = "<sip:user@10.0.0.1:5060>";
        let result = rewrite_sip_uri(input, "192.168.1.1", 5070);
        assert_eq!(result, "<sip:user@192.168.1.1:5070>");
    }

    #[test]
    fn test_rewrite_sip_uri_no_port() {
        let input = "sip:user@10.0.0.1";
        let result = rewrite_sip_uri(input, "192.168.1.1", 5070);
        assert_eq!(result, "sip:user@192.168.1.1:5070");
    }

    #[test]
    fn test_rewrite_sdp() {
        let sdp = "v=0\r\nc=IN IP4 10.0.0.1\r\nm=audio 5060 RTP/AVP 0 9\r\na=sendrecv\r\n";
        let (rewritten, orig) = rewrite_sdp(sdp, "192.168.1.1", 20000);
        assert!(rewritten.contains("c=IN IP4 192.168.1.1"));
        assert!(rewritten.contains("m=audio 20000 RTP/AVP 0 9"));
        let ep = orig.unwrap();
        assert_eq!(ep.address, "10.0.0.1");
        assert_eq!(ep.port, 5060);
        assert_eq!(ep.transport, "RTP/AVP");
    }

    #[test]
    fn test_rewrite_t38_sdp() {
        let sdp = "v=0\r\nc=IN IP4 10.0.0.1\r\nm=image 5060 udptl t38\r\na=T38FaxVersion:0\r\n";
        let (rewritten, orig) = rewrite_sdp(sdp, "192.168.1.1", 4000);
        assert!(rewritten.contains("c=IN IP4 192.168.1.1"));
        assert!(rewritten.contains("m=image 4000 udptl t38"));
        let ep = orig.unwrap();
        assert_eq!(ep.address, "10.0.0.1");
        assert_eq!(ep.port, 5060);
        assert_eq!(ep.media_kind, SdpMediaKind::Image);
        assert_eq!(ep.transport, "udptl");
    }
}
