//! Call manager — central registry and orchestration for all calls.
//!
//! Unified model: every call owns N legs and a mixer task.
//! Legs can be SIP (provider/device), WebRTC (browser), or Media (voicemail/IVR).
//! The mixer provides mix-minus audio to all participants.

use crate::call::{
    Call, CallDirection, CallState, LegId, LegInfo, LegKind, LegState, PendingDialogBridge,
};
use crate::config::{extract_inbound_called_number, normalize_routing_identity, AppConfig, ProviderConfig};
use crate::ipc::{emit_event, OutTx};
use crate::leg_io::{
    create_leg_channels, spawn_raw_udp_inbound, spawn_raw_udp_outbound, spawn_sip_inbound,
    spawn_sip_outbound,
};
use crate::mixer::spawn_mixer;
use crate::registrar::Registrar;
use crate::rtp::RtpPortPool;
use crate::sip_leg::{SipLeg, SipLegAction, SipLegConfig};
use crate::tts::TtsEngine;
use sip_proto::dialog::{DialogState, SipDialog};
use sip_proto::helpers::{
    build_sdp, generate_call_id, generate_tag, parse_sdp_endpoint, SdpOptions,
};
use sip_proto::message::{ResponseOptions, SipMessage};
use sip_proto::rewrite::{rewrite_sdp, rewrite_sip_uri};
use std::collections::HashMap;
use std::net::SocketAddr;
use std::path::Path;
use std::sync::Arc;
use tokio::net::UdpSocket;
use tokio::sync::{mpsc, watch, Mutex};

fn emit_inbound_diagnostic(
    out_tx: &OutTx,
    label: &str,
    invite: &SipMessage,
    from_addr: SocketAddr,
    provider_id: &str,
    called_number: &str,
    caller_number: &str,
) {
    emit_event(
        out_tx,
        "sip_unhandled",
        serde_json::json!({
            "method_or_status": format!(
                "INVITE {label} provider={provider_id} called={called_number} caller={caller_number} ruri={} to={} pcalled={}",
                invite.request_uri().unwrap_or(""),
                invite.get_header("To").unwrap_or(""),
                invite.get_header("P-Called-Party-ID").unwrap_or(""),
            ),
            "call_id": invite.call_id(),
            "from_addr": from_addr.ip().to_string(),
            "from_port": from_addr.port(),
        }),
    );
}

/// Result of creating an inbound call — carries both the call id and
/// whether browsers should be notified (flows from the matched inbound
/// route's `ring_browsers` flag).
pub struct InboundCallCreated {
    pub call_id: String,
    pub ring_browsers: bool,
}

/// Emit a `leg_added` event with full leg information.
/// Free function (not a method) to avoid `&self` borrow conflicts when `self.calls` is borrowed.
fn codec_label(leg: &LegInfo) -> String {
    if leg.media_protocol == "t38-udptl" {
        return "T.38".to_string();
    }

    match leg.codec_pt {
        0 => "PCMU".to_string(),
        8 => "PCMA".to_string(),
        9 => "G.722".to_string(),
        111 => "Opus".to_string(),
        _ => format!("PT{}", leg.codec_pt),
    }
}

fn endpoint_media_protocol(endpoint: &sip_proto::Endpoint) -> &'static str {
    if endpoint.is_t38_udptl() {
        "t38-udptl"
    } else {
        "rtp"
    }
}

fn has_to_tag(msg: &SipMessage) -> bool {
    msg.get_header("To")
        .map(|to| to.to_ascii_lowercase().contains(";tag="))
        .unwrap_or(false)
}

fn apply_sdp_to_leg(leg: &mut LegInfo, sdp: &str) {
    if let Some(ep) = parse_sdp_endpoint(sdp) {
        if let Ok(addr) = format!("{}:{}", ep.address, ep.port).parse() {
            leg.remote_media = Some(addr);
        }
        leg.media_protocol = endpoint_media_protocol(&ep);
        if let Some(pt) = ep.codec_pt {
            leg.codec_pt = pt;
        }
    }
}

fn outbound_audio_fax_file(leg: &LegInfo) -> Option<String> {
    match leg.metadata.get("fax_mode").and_then(|value| value.as_str()) {
        Some("outbound-audio") => leg
            .metadata
            .get("fax_file_path")
            .and_then(|value| value.as_str())
            .map(|value| value.to_string()),
        _ => None,
    }
}

fn fax_file_path(leg: &LegInfo) -> Option<String> {
    leg.metadata
        .get("fax_file_path")
        .and_then(|value| value.as_str())
        .map(|value| value.to_string())
}

fn fax_box_id(leg: &LegInfo) -> Option<String> {
    leg.metadata
        .get("fax_box_id")
        .and_then(|value| value.as_str())
        .map(|value| value.to_string())
}

fn fax_caller_number(leg: &LegInfo) -> Option<String> {
    leg.metadata
        .get("caller_number")
        .and_then(|value| value.as_str())
        .map(|value| value.to_string())
}

fn build_t38_sdp(ip: &str, port: u16) -> String {
    build_sdp(&SdpOptions {
        ip,
        port,
        media_kind: sip_proto::SdpMediaKind::Image,
        transport: "udptl",
        media_formats: &["t38"],
        attributes: &[
            "T38FaxVersion:0",
            "T38MaxBitRate:14400",
            "T38FaxFillBitRemoval:0",
            "T38FaxTranscodingMMR:0",
            "T38FaxTranscodingJBIG:0",
            "T38FaxRateManagement:transferredTCF",
            "T38FaxMaxBuffer:2000",
            "T38FaxMaxDatagram:400",
        ],
        ..Default::default()
    })
}

fn response_reason_phrase(msg: &SipMessage) -> &str {
    msg.start_line.splitn(3, ' ').nth(2).unwrap_or("Response")
}

fn build_non_2xx_ack(original_invite: &SipMessage, response: &SipMessage) -> SipMessage {
    let via = original_invite.get_header("Via").unwrap_or("").to_string();
    let from = original_invite.get_header("From").unwrap_or("").to_string();
    let to = response.get_header("To").unwrap_or("").to_string();
    let call_id = original_invite.call_id().to_string();
    let cseq_num: u32 = original_invite
        .get_header("CSeq")
        .and_then(|s| s.split_whitespace().next())
        .and_then(|s| s.parse().ok())
        .unwrap_or(1);
    let ruri = original_invite.request_uri().unwrap_or("sip:unknown").to_string();

    SipMessage::new(
        format!("ACK {ruri} SIP/2.0"),
        vec![
            ("Via".to_string(), via),
            ("From".to_string(), from),
            ("To".to_string(), to),
            ("Call-ID".to_string(), call_id),
            ("CSeq".to_string(), format!("{cseq_num} ACK")),
            ("Max-Forwards".to_string(), "70".to_string()),
            ("Content-Length".to_string(), "0".to_string()),
        ],
        String::new(),
    )
}

fn device_dialog(call: &Call, device_leg_id: &str, config: &AppConfig) -> Option<SipDialog> {
    let device_invite = call.device_invite.as_ref()?;
    let local_tag = call
        .legs
        .get(device_leg_id)?
        .metadata
        .get("dialog_local_tag")?
        .as_str()?;
    let mut dialog = SipDialog::from_uas_invite(
        device_invite,
        local_tag,
        &config.proxy.lan_ip,
        config.proxy.lan_port,
    );
    dialog.state = DialogState::Confirmed;
    dialog.local_cseq = call
        .legs
        .get(device_leg_id)?
        .metadata
        .get("dialog_local_cseq")
        .and_then(|value| value.as_u64())
        .unwrap_or(0) as u32;
    Some(dialog)
}

fn select_audio_fax_codec(codecs: &[u8]) -> Option<u8> {
    if codecs.contains(&codec_lib::PT_PCMU) {
        Some(codec_lib::PT_PCMU)
    } else if codecs.contains(&codec_lib::PT_PCMA) {
        Some(codec_lib::PT_PCMA)
    } else {
        None
    }
}

fn leg_metadata_json(leg: &LegInfo) -> serde_json::Value {
    let metadata: serde_json::Value = if leg.metadata.is_empty() {
        serde_json::json!({})
    } else {
        serde_json::Value::Object(
            leg.metadata
                .iter()
                .map(|(k, v)| (k.clone(), v.clone()))
                .collect(),
        )
    };

    metadata
}

fn leg_event_payload(call_id: &str, leg: &LegInfo) -> serde_json::Value {
    serde_json::json!({
        "call_id": call_id,
        "leg_id": leg.id,
        "kind": leg.kind.as_str(),
        "state": leg.state.as_str(),
        "codec": codec_label(leg),
        "rtpPort": leg.rtp_port,
        "mediaProtocol": leg.media_protocol,
        "remoteMedia": leg.remote_media.map(|a| format!("{}:{}", a.ip(), a.port())),
        "metadata": leg_metadata_json(leg),
    })
}

fn emit_leg_added_event(tx: &OutTx, call_id: &str, leg: &LegInfo) {
    emit_event(
        tx,
        "leg_added",
        leg_event_payload(call_id, leg),
    );
}

fn emit_leg_state_changed_event(tx: &OutTx, call_id: &str, leg: &LegInfo) {
    emit_event(tx, "leg_state_changed", leg_event_payload(call_id, leg));
}

pub struct CallManager {
    /// All active calls, keyed by internal call ID.
    pub calls: HashMap<String, Call>,
    /// Index: SIP Call-ID → (internal call_id, leg_id).
    /// Each SIP leg in a call has its own SIP Call-ID.
    sip_index: HashMap<String, (String, LegId)>,
    /// Call ID counter.
    next_call_num: u64,
    /// Output channel for events.
    out_tx: OutTx,
}

impl CallManager {
    pub fn new(out_tx: OutTx) -> Self {
        Self {
            calls: HashMap::new(),
            sip_index: HashMap::new(),
            next_call_num: 0,
            out_tx,
        }
    }

    fn next_call_id(&mut self) -> String {
        let id = format!(
            "call-{}-{}",
            std::time::SystemTime::now()
                .duration_since(std::time::UNIX_EPOCH)
                .unwrap_or_default()
                .as_millis(),
            self.next_call_num,
        );
        self.next_call_num += 1;
        id
    }

    fn next_leg_id(&mut self) -> String {
        self.next_call_num += 1;
        format!("leg-{}", self.next_call_num)
    }

    /// Check if a SIP Call-ID belongs to any active call.
    pub fn has_call(&self, sip_call_id: &str) -> bool {
        self.sip_index.contains_key(sip_call_id)
    }

    // -----------------------------------------------------------------------
    // SIP message routing
    // -----------------------------------------------------------------------

    /// Route a SIP message to the correct call and leg.
    /// Returns true if the message was handled.
    pub async fn route_sip_message(
        &mut self,
        msg: &SipMessage,
        from_addr: SocketAddr,
        socket: &UdpSocket,
        config: &AppConfig,
    ) -> bool {
        let sip_call_id = msg.call_id().to_string();

        let (call_id, leg_id) = match self.sip_index.get(&sip_call_id) {
            Some((cid, lid)) => (cid.clone(), lid.clone()),
            None => return false,
        };

        // Check if this is a B2BUA leg (has a SipLeg with dialog management).
        let is_b2bua_leg = self
            .calls
            .get(&call_id)
            .and_then(|c| c.legs.get(&leg_id))
            .map(|l| l.sip_leg.is_some())
            .unwrap_or(false);

        if is_b2bua_leg {
            return self
                .route_b2bua_message(&call_id, &leg_id, msg, from_addr, socket, config)
                .await;
        }

        // Passthrough-style routing for inbound/outbound device↔provider calls.
        // The sip_index only stores one leg for shared Call-IDs, so we need to
        // determine which leg the message actually belongs to by comparing from_addr.
        let actual_leg_id = self
            .calls
            .get(&call_id)
            .and_then(|call| {
                call.legs
                    .values()
                    .find(|l| l.signaling_addr == Some(from_addr))
                    .map(|l| l.id.clone())
            })
            .unwrap_or(leg_id);
        self.route_passthrough_message(&call_id, &actual_leg_id, msg, from_addr, socket, config)
            .await
    }

    async fn handle_local_fax_offer(
        &mut self,
        call_id: &str,
        leg_id: &str,
        msg: &SipMessage,
        socket: &UdpSocket,
        config: &AppConfig,
    ) -> bool {
        let method = msg.method().unwrap_or("");
        if method != "INVITE" && method != "UPDATE" {
            return false;
        }

        let offer = match msg.has_sdp_body().then(|| parse_sdp_endpoint(&msg.body)).flatten() {
            Some(offer) => offer,
            None => {
                let resp = SipMessage::create_response(488, "Not Acceptable Here", msg, None);
                let target = self
                    .calls
                    .get(call_id)
                    .and_then(|call| call.legs.get(leg_id))
                    .and_then(|leg| leg.signaling_addr);
                if let Some(target) = target {
                    let _ = socket.send_to(&resp.serialize(), target).await;
                }
                return true;
            }
        };

        let (
            rtp_socket,
            rtp_port,
            codec_pt,
            public_ip,
            current_media_protocol,
            fax_file_path,
            fax_box_id,
            caller_number,
            signaling_addr,
        ) = match self.calls.get(call_id).and_then(|call| call.legs.get(leg_id)) {
            Some(leg) => (
                leg.rtp_socket.clone(),
                leg.rtp_port,
                leg.codec_pt,
                leg.public_ip.clone(),
                leg.media_protocol.to_string(),
                fax_file_path(leg),
                fax_box_id(leg),
                fax_caller_number(leg),
                leg.signaling_addr,
            ),
            None => return false,
        };
        if fax_file_path.is_none() && fax_box_id.is_none() {
            return false;
        }
        let Some(target) = signaling_addr else {
            return false;
        };

        let advertise_ip = public_ip.unwrap_or_else(|| config.proxy.lan_ip.clone());

        if offer.is_t38_udptl() {
            let response = SipMessage::create_response(
                200,
                "OK",
                msg,
                Some(ResponseOptions {
                    contact: Some(format!("<sip:{}:{}>", config.proxy.lan_ip, config.proxy.lan_port)),
                    body: Some(build_t38_sdp(&advertise_ip, rtp_port)),
                    content_type: Some("application/sdp".to_string()),
                    ..Default::default()
                }),
            );
            let _ = socket.send_to(&response.serialize(), target).await;

            if let Some(rtp_socket) = rtp_socket {
                let remote = match format!("{}:{}", offer.address, offer.port).parse() {
                    Ok(remote) => remote,
                    Err(_) => return true,
                };
                let (cancel_tx, cancel_rx) = watch::channel(false);

                if let Some(file_path) = fax_file_path {
                    if let (Some(fax_box_id), Some(caller_number)) = (fax_box_id, caller_number) {
                        crate::fax_engine::spawn_inbound_t38_fax(
                            call_id.to_string(),
                            leg_id.to_string(),
                            fax_box_id,
                            caller_number,
                            file_path,
                            rtp_socket,
                            remote,
                            self.out_tx.clone(),
                            cancel_rx,
                        );
                    } else {
                        crate::fax_engine::spawn_outbound_t38_fax(
                            call_id.to_string(),
                            leg_id.to_string(),
                            file_path,
                            rtp_socket,
                            remote,
                            self.out_tx.clone(),
                            cancel_rx,
                        );
                    }
                }

                if let Some(call) = self.calls.get_mut(call_id) {
                    if let Some(leg) = call.legs.get_mut(leg_id) {
                        leg.media_protocol = "t38-udptl";
                        leg.remote_media = Some(remote);
                        leg.media_io_active = true;
                    }
                    call.install_media_bridge("fax-t38", vec![cancel_tx]);
                }
                if let Some(call) = self.calls.get(call_id) {
                    if let Some(leg) = call.legs.get(leg_id) {
                        emit_leg_state_changed_event(&self.out_tx, call_id, leg);
                    }
                }
            }

            return true;
        }

        if offer.is_audio_rtp() {
            if current_media_protocol == "t38-udptl" {
                let resp = SipMessage::create_response(488, "Not Acceptable Here", msg, None);
                let _ = socket.send_to(&resp.serialize(), target).await;
                return true;
            }

            let response = SipMessage::create_response(
                200,
                "OK",
                msg,
                Some(ResponseOptions {
                    contact: Some(format!("<sip:{}:{}>", config.proxy.lan_ip, config.proxy.lan_port)),
                    body: Some(build_sdp(&SdpOptions {
                        ip: &advertise_ip,
                        port: rtp_port,
                        payload_types: &[codec_pt],
                        ..Default::default()
                    })),
                    content_type: Some("application/sdp".to_string()),
                    ..Default::default()
                }),
            );
            let _ = socket.send_to(&response.serialize(), target).await;

            if let Ok(remote) = format!("{}:{}", offer.address, offer.port).parse() {
                if let Some(call) = self.calls.get_mut(call_id) {
                    if let Some(leg) = call.legs.get_mut(leg_id) {
                        leg.remote_media = Some(remote);
                        leg.media_protocol = "rtp";
                    }
                }
                if let Some(call) = self.calls.get(call_id) {
                    if let Some(leg) = call.legs.get(leg_id) {
                        emit_leg_state_changed_event(&self.out_tx, call_id, leg);
                    }
                }
            }
            return true;
        }

        let resp = SipMessage::create_response(488, "Not Acceptable Here", msg, None);
        let _ = socket.send_to(&resp.serialize(), target).await;
        true
    }

    async fn handle_device_bridge_offer(
        &mut self,
        call_id: &str,
        source_leg_id: &str,
        msg: &SipMessage,
        socket: &UdpSocket,
        config: &AppConfig,
    ) -> bool {
        let method = msg.method().unwrap_or("");
        if method != "INVITE" && method != "UPDATE" {
            return false;
        }

        let _offer = match msg.has_sdp_body().then(|| parse_sdp_endpoint(&msg.body)).flatten() {
            Some(offer) => offer,
            None => {
                let not_acceptable = SipMessage::create_response(488, "Not Acceptable Here", msg, None);
                let target = self
                    .calls
                    .get(call_id)
                    .and_then(|call| call.legs.get(source_leg_id))
                    .and_then(|leg| leg.signaling_addr);
                if let Some(target) = target {
                    let _ = socket.send_to(&not_acceptable.serialize(), target).await;
                }
                return true;
            }
        };

        let (device_leg_id, device_addr, device_rtp_port, provider_target) = match self.calls.get(call_id) {
            Some(call) => {
                if call.pending_dialog_bridge.is_some() {
                    let pending = SipMessage::create_response(491, "Request Pending", msg, None);
                    if let Some(target) = call.legs.get(source_leg_id).and_then(|leg| leg.signaling_addr) {
                        let _ = socket.send_to(&pending.serialize(), target).await;
                    }
                    return true;
                }

                let Some(device_leg) = call
                    .legs
                    .values()
                    .find(|leg| leg.id != source_leg_id && leg.kind == LegKind::SipDevice)
                else {
                    let unsupported = SipMessage::create_response(488, "Not Acceptable Here", msg, None);
                    if let Some(target) = call.legs.get(source_leg_id).and_then(|leg| leg.signaling_addr) {
                        let _ = socket.send_to(&unsupported.serialize(), target).await;
                    }
                    return true;
                };

                match (
                    device_leg.id.clone(),
                    device_leg.signaling_addr,
                    device_leg.rtp_port,
                    call.legs.get(source_leg_id).and_then(|leg| leg.signaling_addr),
                ) {
                    (device_leg_id, Some(device_addr), device_rtp_port, Some(provider_target)) => {
                        (device_leg_id, device_addr, device_rtp_port, provider_target)
                    }
                    _ => return false,
                }
            }
            None => return false,
        };

        let mut dialog = match self.calls.get(call_id).and_then(|call| device_dialog(call, &device_leg_id, config)) {
            Some(dialog) => dialog,
            None => {
                let not_acceptable = SipMessage::create_response(488, "Not Acceptable Here", msg, None);
                let _ = socket.send_to(&not_acceptable.serialize(), provider_target).await;
                return true;
            }
        };

        let rewritten_body = if msg.has_sdp_body() {
            Some(rewrite_sdp(&msg.body, &config.proxy.lan_ip, device_rtp_port).0)
        } else {
            None
        };
        let target_request = dialog.create_request(
            method,
            rewritten_body.as_deref(),
            rewritten_body.as_ref().map(|_| "application/sdp"),
            None,
        );

        if let Some(call) = self.calls.get_mut(call_id) {
            if let Some(device_leg) = call.legs.get_mut(&device_leg_id) {
                device_leg.metadata.insert(
                    "dialog_local_cseq".to_string(),
                    serde_json::json!(dialog.local_cseq),
                );
            }
            call.pending_dialog_bridge = Some(PendingDialogBridge {
                source_leg_id: source_leg_id.to_string(),
                target_leg_id: device_leg_id.clone(),
                source_request: msg.clone(),
                target_request: target_request.clone(),
                method: method.to_string(),
            });
        }

        let _ = socket.send_to(&target_request.serialize(), device_addr).await;
        true
    }

    async fn handle_provider_bridge_offer(
        &mut self,
        call_id: &str,
        source_leg_id: &str,
        msg: &SipMessage,
        socket: &UdpSocket,
        config: &AppConfig,
    ) -> bool {
        let method = msg.method().unwrap_or("");
        if method != "INVITE" && method != "UPDATE" {
            return false;
        }

        let Some(source_target) = self
            .calls
            .get(call_id)
            .and_then(|call| call.legs.get(source_leg_id))
            .and_then(|leg| leg.signaling_addr)
        else {
            return false;
        };

        let offer = match msg.has_sdp_body().then(|| parse_sdp_endpoint(&msg.body)).flatten() {
            Some(offer) => offer,
            None => {
                let not_acceptable = SipMessage::create_response(488, "Not Acceptable Here", msg, None);
                let _ = socket.send_to(&not_acceptable.serialize(), source_target).await;
                return true;
            }
        };

        if !offer.is_audio_rtp() && !offer.is_t38_udptl() {
            let not_acceptable = SipMessage::create_response(488, "Not Acceptable Here", msg, None);
            let _ = socket.send_to(&not_acceptable.serialize(), source_target).await;
            return true;
        }

        let (provider_leg_id, provider_rtp_port, provider_target, rewritten_body, target_request) = {
            let call = match self.calls.get_mut(call_id) {
                Some(call) => call,
                None => return false,
            };

            if call.pending_dialog_bridge.is_some() {
                let pending = SipMessage::create_response(491, "Request Pending", msg, None);
                let _ = socket.send_to(&pending.serialize(), source_target).await;
                return true;
            }

            let Some(provider_leg) = call
                .legs
                .values_mut()
                .find(|leg| leg.id != source_leg_id && leg.kind == LegKind::SipProvider)
            else {
                let unsupported = SipMessage::create_response(488, "Not Acceptable Here", msg, None);
                let _ = socket.send_to(&unsupported.serialize(), source_target).await;
                return true;
            };

            let advertise_ip = provider_leg
                .public_ip
                .clone()
                .unwrap_or_else(|| config.proxy.lan_ip.clone());
            let rewritten_body = rewrite_sdp(&msg.body, &advertise_ip, provider_leg.rtp_port).0;
            let provider_target = provider_leg
                .sip_leg
                .as_mut()
                .map(|sip_leg| {
                    let target_request = sip_leg.dialog.as_mut().map(|dialog| {
                        dialog.create_request(
                            method,
                            Some(&rewritten_body),
                            Some("application/sdp"),
                            None,
                        )
                    });
                    (sip_leg.config.sip_target, target_request)
                });

            let Some((provider_target, Some(target_request))) = provider_target else {
                return false;
            };

            (
                provider_leg.id.clone(),
                provider_leg.rtp_port,
                provider_target,
                rewritten_body,
                target_request,
            )
        };

        let trying = SipMessage::create_response(100, "Trying", msg, None);
        let _ = socket.send_to(&trying.serialize(), source_target).await;

        if let Some(call) = self.calls.get_mut(call_id) {
            if let Some(device_leg) = call.legs.get_mut(source_leg_id) {
                device_leg.metadata.insert(
                    "dialog_local_cseq".to_string(),
                    serde_json::json!(msg.get_header("CSeq")
                        .and_then(|value| value.split_whitespace().next())
                        .and_then(|value| value.parse::<u32>().ok())
                        .unwrap_or(0)),
                );
            }
            call.pending_dialog_bridge = Some(PendingDialogBridge {
                source_leg_id: source_leg_id.to_string(),
                target_leg_id: provider_leg_id,
                source_request: msg.clone(),
                target_request: target_request.clone(),
                method: method.to_string(),
            });
        }

        let _ = provider_rtp_port;
        let _ = rewritten_body;
        let _ = socket.send_to(&target_request.serialize(), provider_target).await;
        true
    }

    async fn handle_pending_dialog_bridge_response(
        &mut self,
        call_id: &str,
        leg_id: &str,
        msg: &SipMessage,
        socket: &UdpSocket,
        config: &AppConfig,
    ) -> bool {
        if !msg.is_response() {
            return false;
        }

        let pending = match self
            .calls
            .get(call_id)
            .and_then(|call| call.pending_dialog_bridge.clone())
        {
            Some(pending) if pending.target_leg_id == leg_id => pending,
            _ => return false,
        };

        let source_leg = match self
            .calls
            .get(call_id)
            .and_then(|call| call.legs.get(&pending.source_leg_id))
        {
            Some(leg) => leg,
            None => return false,
        };
        let Some(source_target) = source_leg.signaling_addr else {
            return false;
        };

        let code = msg.status_code().unwrap_or(500);
        let body = if msg.has_sdp_body() {
            let advertise_ip = match source_leg.kind {
                LegKind::SipProvider => source_leg
                    .public_ip
                    .clone()
                    .unwrap_or_else(|| config.proxy.lan_ip.clone()),
                _ => config.proxy.lan_ip.clone(),
            };
            Some(rewrite_sdp(&msg.body, &advertise_ip, source_leg.rtp_port).0)
        } else {
            None
        };
        let response = SipMessage::create_response(
            code,
            response_reason_phrase(msg),
            &pending.source_request,
            Some(ResponseOptions {
                body,
                content_type: msg
                    .has_sdp_body()
                    .then(|| "application/sdp".to_string()),
                ..Default::default()
            }),
        );
        let _ = socket.send_to(&response.serialize(), source_target).await;

        if pending.method == "INVITE" {
            if (200..300).contains(&code) {
                let device_ack: Option<(Vec<u8>, SocketAddr)> = self
                    .calls
                    .get(call_id)
                    .and_then(|call| {
                        let target_leg = call.legs.get(leg_id)?;
                        if target_leg.kind != LegKind::SipDevice {
                            return None;
                        }
                        let mut dialog = device_dialog(call, leg_id, config)?;
                        dialog.process_response(msg);
                        Some((dialog.create_ack().serialize(), target_leg.signaling_addr?))
                    });
                if let Some((ack, target)) = device_ack {
                    let _ = socket.send_to(&ack, target).await;
                }

                let provider_ack: Option<(Vec<u8>, SocketAddr)> = self
                    .calls
                    .get_mut(call_id)
                    .and_then(|call| {
                        let target_leg = call.legs.get_mut(leg_id)?;
                        if target_leg.kind != LegKind::SipProvider {
                            return None;
                        }
                        let sip_leg = target_leg.sip_leg.as_mut()?;
                        let dialog = sip_leg.dialog.as_mut()?;
                        dialog.process_response(msg);
                        Some((dialog.create_ack().serialize(), sip_leg.config.sip_target))
                    });
                if let Some((ack, target)) = provider_ack {
                    let _ = socket.send_to(&ack, target).await;
                }
            } else if code >= 300 {
                let ack = build_non_2xx_ack(&pending.target_request, msg);
                if let Some(target) = self
                    .calls
                    .get(call_id)
                    .and_then(|call| call.legs.get(leg_id))
                    .and_then(|leg| {
                        leg.signaling_addr.or_else(|| {
                            leg.sip_leg
                                .as_ref()
                                .map(|sip_leg| sip_leg.config.sip_target)
                        })
                    })
                {
                    let _ = socket.send_to(&ack.serialize(), target).await;
                }
            }
        }

        if (200..300).contains(&code) && msg.has_sdp_body() {
            if let Some(call) = self.calls.get_mut(call_id) {
                if pending.source_request.has_sdp_body() {
                    if let Some(source_leg) = call.legs.get_mut(&pending.source_leg_id) {
                        apply_sdp_to_leg(source_leg, &pending.source_request.body);
                    }
                }
                if let Some(target_leg) = call.legs.get_mut(leg_id) {
                    apply_sdp_to_leg(target_leg, &msg.body);
                }
                call.pending_dialog_bridge = None;
            }
            self.reset_passthrough_media_bridge(call_id).await;
            self.maybe_wire_passthrough_legs(call_id).await;
        } else if code >= 200 {
            if let Some(call) = self.calls.get_mut(call_id) {
                call.pending_dialog_bridge = None;
            }
        }

        true
    }

    /// Route a message to a B2BUA leg (has SipLeg dialog management).
    async fn route_b2bua_message(
        &mut self,
        call_id: &str,
        leg_id: &str,
        msg: &SipMessage,
        from_addr: SocketAddr,
        socket: &UdpSocket,
        config: &AppConfig,
    ) -> bool {
        if self
            .handle_pending_dialog_bridge_response(call_id, leg_id, msg, socket, config)
            .await
        {
            return true;
        }

        // Process the SipLeg action first, extracting all needed data.
        let (action, target, codecs, rtp_socket_clone) = {
            let call = match self.calls.get_mut(call_id) {
                Some(c) => c,
                None => return false,
            };
            let leg = match call.legs.get_mut(leg_id) {
                Some(l) => l,
                None => return false,
            };
            let sip_leg = match &mut leg.sip_leg {
                Some(sl) => sl,
                None => return false,
            };
            let action = sip_leg.handle_message(msg);
            let target = sip_leg.config.sip_target;
            let codecs = sip_leg.config.codecs.clone();
            let rtp_socket_clone = leg.rtp_socket.clone();
            (action, target, codecs, rtp_socket_clone)
        };
        // Mutable borrow on call/leg is now released.

        let mut sip_pt = codecs.first().copied().unwrap_or(9);
        let mut media_protocol = "rtp";

        // If the message has SDP (e.g., 200 OK answer), use the negotiated codec
        // instead of the offered one.
        if msg.has_sdp_body() {
            if let Some(ep) = parse_sdp_endpoint(&msg.body) {
                media_protocol = endpoint_media_protocol(&ep);
                if let Some(pt) = ep.codec_pt {
                    sip_pt = pt;
                }
            }
        }

        match action {
            SipLegAction::None => {}
            SipLegAction::InDialogRequest(_method) => {
                if self
                    .handle_local_fax_offer(call_id, leg_id, msg, socket, config)
                    .await
                {
                    return true;
                }

                if self
                    .handle_device_bridge_offer(call_id, leg_id, msg, socket, config)
                    .await
                {
                    return true;
                }

                let response = if msg
                    .has_sdp_body()
                    .then(|| parse_sdp_endpoint(&msg.body))
                    .flatten()
                    .is_some_and(|offer| offer.is_t38_udptl())
                {
                    SipMessage::create_response(488, "Not Acceptable Here", msg, None)
                } else {
                    SipMessage::create_response(501, "Not Implemented", msg, None)
                };
                let _ = socket.send_to(&response.serialize(), target).await;
                return true;
            }
            SipLegAction::Send(buf) => {
                let _ = socket.send_to(&buf, target).await;
            }
            SipLegAction::StateChange(crate::sip_leg::LegState::Ringing) => {
                if let Some(call) = self.calls.get_mut(call_id) {
                    if let Some(leg) = call.legs.get_mut(leg_id) {
                        leg.state = LegState::Ringing;
                    }
                    // Forward 180 Ringing to device if this is a device-originated call.
                    if let Some(device_invite) = &call.device_invite {
                        let device_leg = call.legs.values().find(|l| l.kind == LegKind::SipDevice);
                        if let Some(dev) = device_leg {
                            if let Some(dev_addr) = dev.signaling_addr {
                                let ringing = SipMessage::create_response(
                                    180,
                                    "Ringing",
                                    device_invite,
                                    None,
                                );
                                let _ = socket.send_to(&ringing.serialize(), dev_addr).await;
                            }
                        }
                    }
                }
                emit_event(
                    &self.out_tx,
                    "call_ringing",
                    serde_json::json!({ "call_id": call_id }),
                );
                if let Some(call) = self.calls.get(call_id) {
                    if let Some(leg) = call.legs.get(leg_id) {
                        emit_leg_state_changed_event(&self.out_tx, call_id, leg);
                    }
                }
            }
            SipLegAction::ConnectedWithAck(ack_buf) => {
                let _ = socket.send_to(&ack_buf, target).await;

                // Update leg state and get remote media.
                let (remote, outbound_fax_file) = {
                    let call = self.calls.get_mut(call_id).unwrap();
                    let leg = call.legs.get_mut(leg_id).unwrap();
                    let sip_leg = leg.sip_leg.as_ref().unwrap();
                    let remote = sip_leg.remote_media;
                    let outbound_fax_file = outbound_audio_fax_file(leg);
                    leg.state = LegState::Connected;
                    leg.codec_pt = sip_pt;
                    leg.media_protocol = media_protocol;
                    leg.media_io_active = false;
                    leg.remote_media = remote;
                    call.state = CallState::Connected;
                    (remote, outbound_fax_file)
                };

                // Wire the provider leg to the mixer if remote media is known.
                if let Some(fax_file) = outbound_fax_file {
                    if let (Some(remote_addr), Some(rtp_socket)) = (remote, rtp_socket_clone) {
                        let (cancel_tx, cancel_rx) = watch::channel(false);
                        if media_protocol == "t38-udptl" {
                            crate::fax_engine::spawn_outbound_t38_fax(
                                call_id.to_string(),
                                leg_id.to_string(),
                                fax_file,
                                rtp_socket,
                                remote_addr,
                                self.out_tx.clone(),
                                cancel_rx,
                            );
                        } else {
                            crate::fax_engine::spawn_outbound_audio_fax(
                                call_id.to_string(),
                                leg_id.to_string(),
                                fax_file,
                                rtp_socket,
                                remote_addr,
                                sip_pt,
                                self.out_tx.clone(),
                                cancel_rx,
                            );
                        }
                        if let Some(call) = self.calls.get_mut(call_id) {
                            if let Some(leg) = call.legs.get_mut(leg_id) {
                                leg.media_io_active = true;
                            }
                            call.install_media_bridge(
                                if media_protocol == "t38-udptl" {
                                    "fax-t38"
                                } else {
                                    "fax-audio"
                                },
                                vec![cancel_tx],
                            );
                        }
                    }
                } else if media_protocol == "rtp" {
                    if let (Some(remote_addr), Some(rtp_socket)) = (remote, rtp_socket_clone) {
                        let channels = create_leg_channels();
                        spawn_sip_inbound(rtp_socket.clone(), channels.inbound_tx);
                        spawn_sip_outbound(rtp_socket, remote_addr, channels.outbound_rx);
                        if let Some(call) = self.calls.get(call_id) {
                            call.add_leg_to_mixer(
                                leg_id,
                                sip_pt,
                                channels.inbound_rx,
                                channels.outbound_tx,
                            )
                            .await;
                        }
                    }
                }

                // For device-originated calls: send 200 OK to device and wire device leg.
                if let Some(call) = self.calls.get(call_id) {
                    if let Some(device_invite) = call.device_invite.clone() {
                        let device_leg_info: Option<(
                            SocketAddr,
                            u16,
                            Arc<UdpSocket>,
                            Option<SocketAddr>,
                            String,
                        )> = call
                            .legs
                            .values()
                            .find(|l| l.kind == LegKind::SipDevice)
                            .and_then(|dev| {
                                Some((
                                    dev.signaling_addr?,
                                    dev.rtp_port,
                                    dev.rtp_socket.clone()?,
                                    dev.remote_media,
                                    dev.id.clone(),
                                ))
                            });

                        if let Some((
                            dev_addr,
                            dev_rtp_port,
                            dev_rtp_socket,
                            dev_remote,
                            dev_leg_id,
                            )) = device_leg_info
                        {
                            // Use the device's preferred codec from its INVITE SDP,
                            // not the provider's negotiated codec.
                            let dev_ep = device_invite
                                .has_sdp_body()
                                .then(|| parse_sdp_endpoint(&device_invite.body))
                                .flatten();
                            let dev_pt = dev_ep
                                .as_ref()
                                .and_then(|ep| ep.codec_pt)
                                .unwrap_or(sip_pt);
                            let dev_media_protocol = dev_ep
                                .as_ref()
                                .map(endpoint_media_protocol)
                                .unwrap_or("rtp");

                            // Build SDP pointing device to our device_rtp port.
                            // Use LAN IP for the device (it's on the local network).
                            let call_ref = self.calls.get(call_id).unwrap();
                            let prov_leg = call_ref
                                .legs
                                .values()
                                .find(|l| l.kind == LegKind::SipProvider);
                            let lan_ip_str = prov_leg
                                .and_then(|l| l.sip_leg.as_ref())
                                .map(|sl| sl.config.lan_ip.clone())
                                .unwrap_or_else(|| "0.0.0.0".to_string());

                            let sdp = build_sdp(&SdpOptions {
                                ip: &lan_ip_str,
                                port: dev_rtp_port,
                                ..Default::default()
                            });

                            let device_local_tag = self
                                .calls
                                .get(call_id)
                                .and_then(|call| call.legs.get(&dev_leg_id))
                                .and_then(|leg| leg.metadata.get("dialog_local_tag"))
                                .and_then(|value| value.as_str())
                                .map(|value| value.to_string())
                                .unwrap_or_else(generate_tag);

                            let ok = SipMessage::create_response(
                                200,
                                "OK",
                                &device_invite,
                                Some(ResponseOptions {
                                    to_tag: Some(device_local_tag.clone()),
                                    contact: Some(format!("<sip:{}:{}>", lan_ip_str, 5060)),
                                    body: Some(sdp),
                                    content_type: Some("application/sdp".to_string()),
                                    extra_headers: None,
                                }),
                            );
                            let _ = socket.send_to(&ok.serialize(), dev_addr).await;

                            // Update device leg state.
                            if let Some(call) = self.calls.get_mut(call_id) {
                                if let Some(dev_leg) = call.legs.get_mut(&dev_leg_id) {
                                    dev_leg.state = LegState::Connected;
                                    dev_leg.codec_pt = dev_pt;
                                    dev_leg.media_protocol = dev_media_protocol;
                                    dev_leg.media_io_active = false;
                                    dev_leg.metadata.insert(
                                        "dialog_local_tag".to_string(),
                                        serde_json::json!(device_local_tag),
                                    );
                                    dev_leg.metadata.insert(
                                        "dialog_local_cseq".to_string(),
                                        serde_json::json!(0),
                                    );
                                }
                            }
                            if let Some(call) = self.calls.get(call_id) {
                                if let Some(dev_leg) = call.legs.get(&dev_leg_id) {
                                    emit_leg_state_changed_event(&self.out_tx, call_id, dev_leg);
                                }
                            }

                            // Wire device leg to mixer.
                            if dev_media_protocol == "rtp" {
                                if let Some(dev_remote_addr) = dev_remote {
                                    let channels = create_leg_channels();
                                    spawn_sip_inbound(dev_rtp_socket.clone(), channels.inbound_tx);
                                    spawn_sip_outbound(
                                        dev_rtp_socket,
                                        dev_remote_addr,
                                        channels.outbound_rx,
                                    );
                                    if let Some(call) = self.calls.get(call_id) {
                                        call.add_leg_to_mixer(
                                            &dev_leg_id,
                                            dev_pt,
                                            channels.inbound_rx,
                                            channels.outbound_tx,
                                        )
                                        .await;
                                    }
                                }
                            }
                        }
                    }
                }

                emit_event(
                    &self.out_tx,
                    "call_answered",
                    serde_json::json!({
                        "call_id": call_id,
                        "provider_media_addr": remote.map(|a| a.ip().to_string()),
                        "provider_media_port": remote.map(|a| a.port()),
                        "media_protocol": media_protocol,
                        "sip_pt": sip_pt,
                    }),
                );
                if let Some(call) = self.calls.get(call_id) {
                    if let Some(leg) = call.legs.get(leg_id) {
                        emit_leg_state_changed_event(&self.out_tx, call_id, leg);
                    }
                }
            }
            SipLegAction::Terminated(reason) => {
                let duration = self
                    .calls
                    .get(call_id)
                    .map(|c| c.duration_secs())
                    .unwrap_or(0);

                // Notify device if this is a device-originated outbound call.
                if let Some(call) = self.calls.get(call_id) {
                    if let Some(device_invite) = &call.device_invite {
                        let device_leg = call.legs.values().find(|l| l.kind == LegKind::SipDevice);
                        if let Some(dev) = device_leg {
                            if let Some(dev_addr) = dev.signaling_addr {
                                // Map reason to SIP response code.
                                let code: u16 = if reason.starts_with("rejected_") {
                                    reason
                                        .strip_prefix("rejected_")
                                        .and_then(|s| s.parse().ok())
                                        .unwrap_or(503)
                                } else if reason == "bye" {
                                    // Provider sent BYE — send BYE to device too.
                                    // (200 OK already connected; just let terminate_call handle it)
                                    0
                                } else {
                                    503
                                };
                                if code > 0 && dev.state != LegState::Connected {
                                    let resp = SipMessage::create_response(
                                        code,
                                        "Service Unavailable",
                                        device_invite,
                                        None,
                                    );
                                    let _ = socket.send_to(&resp.serialize(), dev_addr).await;
                                }
                            }
                        }
                    }
                }

                if let Some(call) = self.calls.get_mut(call_id) {
                    if let Some(leg) = call.legs.get_mut(leg_id) {
                        leg.state = LegState::Terminated;
                    }
                }
                if let Some(call) = self.calls.get(call_id) {
                    if let Some(leg) = call.legs.get(leg_id) {
                        emit_leg_state_changed_event(&self.out_tx, call_id, leg);
                    }
                }
                emit_event(
                    &self.out_tx,
                    "call_ended",
                    serde_json::json!({ "call_id": call_id, "reason": reason, "duration": duration }),
                );
                self.terminate_call(call_id).await;
                return true;
            }
            SipLegAction::SendAndTerminate(buf, reason) => {
                let _ = socket.send_to(&buf, from_addr).await;
                let duration = self
                    .calls
                    .get(call_id)
                    .map(|c| c.duration_secs())
                    .unwrap_or(0);
                emit_event(
                    &self.out_tx,
                    "call_ended",
                    serde_json::json!({ "call_id": call_id, "reason": reason, "duration": duration }),
                );
                self.terminate_call(call_id).await;
                return true;
            }
            SipLegAction::AuthRetry {
                ack_407,
                invite_with_auth,
            } => {
                if let Some(ack) = ack_407 {
                    let _ = socket.send_to(&ack, target).await;
                }
                let _ = socket.send_to(&invite_with_auth, target).await;
            }
            _ => {}
        }

        true
    }

    /// Route a passthrough-style message (inbound/outbound device↔provider).
    /// In the new model, both sides still go through the mixer, but SIP signaling
    /// is forwarded between the two endpoints with SDP rewriting.
    async fn route_passthrough_message(
        &mut self,
        call_id: &str,
        this_leg_id: &str,
        msg: &SipMessage,
        from_addr: SocketAddr,
        socket: &UdpSocket,
        config: &AppConfig,
    ) -> bool {
        if self
            .handle_pending_dialog_bridge_response(call_id, this_leg_id, msg, socket, config)
            .await
        {
            return true;
        }

        // Single-leg system calls (voicemail/IVR) have no counterpart SIP leg.
        // Handle teardown requests before we try to resolve a forward target.
        if msg.is_request() {
            let method = msg.method().unwrap_or("");
            let has_other_live_leg = self
                .calls
                .get(call_id)
                .map(|call| {
                    call.legs
                        .values()
                        .any(|l| l.id != this_leg_id && l.state != LegState::Terminated)
                })
                .unwrap_or(false);

            if !has_other_live_leg {
                if method == "ACK" {
                    return true;
                }

                if method == "INFO" {
                    let ok = SipMessage::create_response(200, "OK", msg, None);
                    let _ = socket.send_to(&ok.serialize(), from_addr).await;
                    return true;
                }

                if method == "INVITE" || method == "UPDATE" {
                    if self
                        .handle_local_fax_offer(call_id, this_leg_id, msg, socket, config)
                        .await
                    {
                        return true;
                    }
                }

                if method == "BYE" || method == "CANCEL" {
                    let ok = SipMessage::create_response(200, "OK", msg, None);
                    let _ = socket.send_to(&ok.serialize(), from_addr).await;

                    let (duration, leg_payload) = {
                        let call = match self.calls.get_mut(call_id) {
                            Some(c) => c,
                            None => return false,
                        };
                        let duration = call.duration_secs();
                        if let Some(leg) = call.legs.get_mut(this_leg_id) {
                            leg.state = LegState::Terminated;
                        }
                        let leg_payload = call
                            .legs
                            .get(this_leg_id)
                            .map(|leg| leg_event_payload(call_id, leg));
                        (duration, leg_payload)
                    };

                    if let Some(payload) = leg_payload {
                        emit_event(&self.out_tx, "leg_state_changed", payload);
                    }
                    emit_event(
                        &self.out_tx,
                        "call_ended",
                        serde_json::json!({
                            "call_id": call_id,
                            "reason": if method == "BYE" { "bye" } else { "cancel" },
                            "duration": duration,
                        }),
                    );
                    self.terminate_call(call_id).await;
                    return true;
                }
            }
        }

        let call = match self.calls.get_mut(call_id) {
            Some(c) => c,
            None => return false,
        };

        // Find the "other" leg — the one we forward to.
        let this_leg = call.legs.get(this_leg_id);
        let this_kind = this_leg.map(|l| l.kind).unwrap_or(LegKind::SipProvider);

        // Find the counterpart leg.
        let other_leg = call
            .legs
            .values()
            .find(|l| l.id != this_leg_id && l.state != LegState::Terminated);
        let (other_addr, other_rtp_port, other_leg_id, other_kind, other_public_ip) =
            match other_leg {
                Some(l) => (
                    l.signaling_addr,
                    l.rtp_port,
                    l.id.clone(),
                    l.kind,
                    l.public_ip.clone(),
                ),
                None => return false,
            };
        let forward_to = match other_addr {
            Some(a) => a,
            None => return false,
        };

        let lan_ip = config.proxy.lan_ip.clone();
        let lan_port = config.proxy.lan_port;

        // Pick the IP to advertise to the destination leg. Provider legs face
        // the public internet and need `public_ip`; every other leg kind is
        // on-LAN (or proxy-internal) and takes `lan_ip`. This rule is applied
        // both to the SDP `c=` line and the Record-Route header below.
        let advertise_ip: String = match other_kind {
            LegKind::SipProvider => other_public_ip.unwrap_or_else(|| lan_ip.clone()),
            _ => lan_ip.clone(),
        };

        // Check if the other leg is a B2BUA leg (has SipLeg for proper dialog mgmt).
        let other_has_sip_leg = call
            .legs
            .get(&other_leg_id)
            .map(|l| l.sip_leg.is_some())
            .unwrap_or(false);

        if msg.is_request() {
            let method = msg.method().unwrap_or("");

            // ACK: In hybrid B2BUA mode, the device's ACK for our 200 OK
            // is absorbed silently (provider's 200 was already ACKed by SipLeg).
            if method == "ACK" {
                if other_has_sip_leg {
                    return true; // Absorb — provider ACK handled by SipLeg.
                }
                // Pure passthrough: forward ACK normally.
                let _ = socket.send_to(&msg.serialize(), forward_to).await;
                return true;
            }

            // Initial INVITE retransmit: the call already exists, re-send 100 Trying.
            // Mid-dialog re-INVITEs carry a To-tag and must be forwarded.
            if method == "INVITE" && !has_to_tag(msg) {
                let trying = SipMessage::create_response(100, "Trying", msg, None);
                let _ = socket.send_to(&trying.serialize(), from_addr).await;
                return true;
            }

            if (method == "INVITE" || method == "UPDATE") && other_has_sip_leg {
                if self
                    .handle_provider_bridge_offer(call_id, this_leg_id, msg, socket, config)
                    .await
                {
                    return true;
                }

                let not_impl = SipMessage::create_response(501, "Not Implemented", msg, None);
                let _ = socket.send_to(&not_impl.serialize(), from_addr).await;
                return true;
            }

            if method == "BYE" {
                let ok = SipMessage::create_response(200, "OK", msg, None);
                let _ = socket.send_to(&ok.serialize(), from_addr).await;

                // If other leg has SipLeg, use build_hangup for proper dialog teardown.
                if other_has_sip_leg {
                    if let Some(other) = call.legs.get_mut(&other_leg_id) {
                        if let Some(sip_leg) = &mut other.sip_leg {
                            if let Some(hangup_buf) = sip_leg.build_hangup() {
                                let _ =
                                    socket.send_to(&hangup_buf, sip_leg.config.sip_target).await;
                            }
                        }
                    }
                } else {
                    let _ = socket.send_to(&msg.serialize(), forward_to).await;
                }

                let duration = call.duration_secs();
                emit_event(
                    &self.out_tx,
                    "call_ended",
                    serde_json::json!({
                        "call_id": call_id,
                        "reason": "bye",
                        "duration": duration,
                    }),
                );
                self.terminate_call(call_id).await;
                return true;
            }

            if method == "CANCEL" {
                let ok = SipMessage::create_response(200, "OK", msg, None);
                let _ = socket.send_to(&ok.serialize(), from_addr).await;

                // If other leg has SipLeg, use build_hangup (produces CANCEL for early dialog).
                if other_has_sip_leg {
                    if let Some(other) = call.legs.get_mut(&other_leg_id) {
                        if let Some(sip_leg) = &mut other.sip_leg {
                            if let Some(hangup_buf) = sip_leg.build_hangup() {
                                let _ =
                                    socket.send_to(&hangup_buf, sip_leg.config.sip_target).await;
                            }
                        }
                    }
                } else {
                    let _ = socket.send_to(&msg.serialize(), forward_to).await;
                }

                let duration = call.duration_secs();
                emit_event(
                    &self.out_tx,
                    "call_ended",
                    serde_json::json!({ "call_id": call_id, "reason": "cancel", "duration": duration }),
                );
                self.terminate_call(call_id).await;
                return true;
            }

            if method == "INFO" {
                let ok = SipMessage::create_response(200, "OK", msg, None);
                let _ = socket.send_to(&ok.serialize(), from_addr).await;
                return true;
            }

            // Forward other requests with SDP rewriting.
            let mut fwd = msg.clone();
            // Rewrite SDP so the destination leg sends RTP to our proxy port
            // at an address that is routable from its vantage point
            // (public IP for provider legs, LAN IP for everything else).
            if fwd.has_sdp_body() {
                if let Some(leg) = call.legs.get_mut(this_leg_id) {
                    apply_sdp_to_leg(leg, &msg.body);
                }
                let (new_body, _) = rewrite_sdp(&fwd.body, &advertise_ip, other_rtp_port);
                fwd.body = new_body;
                fwd.update_content_length();
            }
            if this_kind == LegKind::SipProvider {
                // From provider → forward to device: rewrite request URI.
                if let Some(ruri) = fwd.request_uri().map(|s| s.to_string()) {
                    let new_ruri =
                        rewrite_sip_uri(&ruri, &forward_to.ip().to_string(), forward_to.port());
                    fwd.set_request_uri(&new_ruri);
                }
            }
            if fwd.is_dialog_establishing() {
                // Record-Route must also be routable from the destination leg.
                fwd.prepend_header(
                    "Record-Route",
                    &format!("<sip:{advertise_ip}:{lan_port};lr>"),
                );
            }
            let _ = socket.send_to(&fwd.serialize(), forward_to).await;
            if fwd.has_sdp_body() {
                if let Some(leg) = call.legs.get(this_leg_id) {
                    emit_leg_state_changed_event(&self.out_tx, call_id, leg);
                }
            }
            return true;
        }

        // --- Responses ---
        if msg.is_response() {
            let code = msg.status_code().unwrap_or(0);
            let cseq_method = msg.cseq_method().unwrap_or("").to_uppercase();

            let mut fwd = msg.clone();
            // Rewrite SDP so the forward-to side sends RTP to the correct
            // leg port at a routable address (see `advertise_ip` above).
            if fwd.has_sdp_body() {
                let (new_body, _) = rewrite_sdp(&fwd.body, &advertise_ip, other_rtp_port);
                fwd.body = new_body;
                fwd.update_content_length();
            }

            // State transitions on INVITE / UPDATE responses.
            if cseq_method == "INVITE" || cseq_method == "UPDATE" {
                if code == 180 || code == 183 {
                    if call.state == CallState::SettingUp {
                        call.state = CallState::Ringing;
                        emit_event(
                            &self.out_tx,
                            "call_ringing",
                            serde_json::json!({ "call_id": call_id }),
                        );
                    }
                    if let Some(leg) = call.legs.get_mut(this_leg_id) {
                        leg.state = LegState::Ringing;
                    }
                    if let Some(leg) = call.legs.get(this_leg_id) {
                        emit_leg_state_changed_event(&self.out_tx, call_id, leg);
                    }
                } else if code >= 200 && code < 300 {
                    let mut needs_wiring = false;
                    if let Some(leg) = call.legs.get_mut(this_leg_id) {
                        if cseq_method == "INVITE" {
                            leg.state = LegState::Connected;
                        }
                        // Learn remote media and negotiated media protocol from SDP answer.
                        if msg.has_sdp_body() {
                            apply_sdp_to_leg(leg, &msg.body);
                        }
                        needs_wiring = true;
                    }

                    if let Some(leg) = call.legs.get(this_leg_id) {
                        emit_leg_state_changed_event(&self.out_tx, call_id, leg);
                    }

                    if cseq_method == "INVITE" && call.state != CallState::Connected {
                        call.state = CallState::Connected;
                        let answered_media_protocol = call
                            .legs
                            .get(this_leg_id)
                            .map(|leg| leg.media_protocol)
                            .unwrap_or("rtp");
                        emit_event(
                            &self.out_tx,
                            "call_answered",
                            serde_json::json!({ "call_id": call_id, "media_protocol": answered_media_protocol }),
                        );
                    }

                    // Forward the response before wiring (drop call borrow).
                    let _ = socket.send_to(&fwd.serialize(), forward_to).await;

                    // Rebuild passthrough media after in-dialog SDP changes.
                    if needs_wiring {
                        self.reset_passthrough_media_bridge(call_id).await;
                        self.maybe_wire_passthrough_legs(call_id).await;
                    }
                    return true;
                } else if code >= 300 {
                    let duration = call.duration_secs();
                    emit_event(
                        &self.out_tx,
                        "call_ended",
                        serde_json::json!({ "call_id": call_id, "reason": format!("rejected_{code}"), "duration": duration }),
                    );
                    // Don't terminate yet — let the forward happen first.
                }
            }

            let _ = socket.send_to(&fwd.serialize(), forward_to).await;
            return true;
        }

        false
    }

    async fn reset_passthrough_media_bridge(&mut self, call_id: &str) {
        let active_leg_ids: Vec<String> = match self.calls.get(call_id) {
            Some(call) => call
                .legs
                .values()
                .filter(|leg| leg.media_io_active)
                .map(|leg| leg.id.clone())
                .collect(),
            None => return,
        };

        if let Some(call) = self.calls.get(call_id) {
            for leg_id in &active_leg_ids {
                call.remove_leg_from_mixer(leg_id).await;
            }
        }

        if let Some(call) = self.calls.get_mut(call_id) {
            call.clear_media_bridge();
            for leg in call.legs.values_mut() {
                leg.media_io_active = false;
            }
        }
    }

    /// Wire passthrough legs either into the audio mixer (`rtp`) or into a raw
    /// UDP relay (`t38-udptl`) once both sides have compatible media.
    async fn maybe_wire_passthrough_legs(&mut self, call_id: &str) {
        let ready_legs: Vec<(String, &'static str, u8, Arc<UdpSocket>, SocketAddr)> =
            match self.calls.get(call_id) {
                Some(call) => call
                    .legs
                    .values()
                    .filter(|leg| leg.state == LegState::Connected || leg.state == LegState::Ringing)
                    .filter_map(|leg| {
                        Some((
                            leg.id.clone(),
                            leg.media_protocol,
                            leg.codec_pt,
                            leg.rtp_socket.clone()?,
                            leg.remote_media?,
                        ))
                    })
                    .collect(),
                None => return,
            };

        if ready_legs.len() < 2 {
            return;
        }

        let media_protocol = ready_legs[0].1;
        if ready_legs.iter().any(|(_, protocol, _, _, _)| *protocol != media_protocol) {
            return;
        }

        if let Some(call) = self.calls.get(call_id) {
            if call.media_bridge_mode.as_deref() == Some(media_protocol)
                && ready_legs.iter().all(|(leg_id, _, _, _, _)| {
                    call.legs
                        .get(leg_id)
                        .map(|leg| leg.media_io_active)
                        .unwrap_or(false)
                })
            {
                return;
            }
        }

        self.reset_passthrough_media_bridge(call_id).await;

        if media_protocol == "rtp" {
            if let Some(call) = self.calls.get(call_id) {
                for (leg_id, _protocol, codec_pt, rtp_socket, remote) in &ready_legs {
                    let channels = create_leg_channels();
                    spawn_sip_inbound(rtp_socket.clone(), channels.inbound_tx);
                    spawn_sip_outbound(rtp_socket.clone(), *remote, channels.outbound_rx);
                    call.add_leg_to_mixer(leg_id, *codec_pt, channels.inbound_rx, channels.outbound_tx)
                        .await;
                }
            }

            if let Some(call) = self.calls.get_mut(call_id) {
                for (leg_id, ..) in &ready_legs {
                    if let Some(leg) = call.legs.get_mut(leg_id) {
                        leg.media_io_active = true;
                    }
                }
                call.note_mixer_bridge("rtp");
            }
            return;
        }

        if media_protocol == "t38-udptl" && ready_legs.len() == 2 {
            let (a_id, _, _a_codec, a_socket, a_remote) = &ready_legs[0];
            let (b_id, _, _b_codec, b_socket, b_remote) = &ready_legs[1];

            let (a_to_b_tx, a_to_b_rx) = mpsc::channel::<Vec<u8>>(64);
            let (b_to_a_tx, b_to_a_rx) = mpsc::channel::<Vec<u8>>(64);

            let (cancel_a_tx, cancel_a_rx) = watch::channel(false);
            let (cancel_b_tx, cancel_b_rx) = watch::channel(false);
            let cancel_txs = vec![cancel_a_tx.clone(), cancel_b_tx.clone()];

            spawn_raw_udp_inbound(a_socket.clone(), a_to_b_tx, cancel_a_rx.clone());
            spawn_raw_udp_outbound(b_socket.clone(), *b_remote, a_to_b_rx, cancel_a_rx);
            spawn_raw_udp_inbound(b_socket.clone(), b_to_a_tx, cancel_b_rx.clone());
            spawn_raw_udp_outbound(a_socket.clone(), *a_remote, b_to_a_rx, cancel_b_rx);

            if let Some(call) = self.calls.get_mut(call_id) {
                if let Some(leg) = call.legs.get_mut(a_id) {
                    leg.media_io_active = true;
                }
                if let Some(leg) = call.legs.get_mut(b_id) {
                    leg.media_io_active = true;
                }
                call.install_media_bridge("t38-udptl", cancel_txs);
            }
        }
    }

    // -----------------------------------------------------------------------
    // Call creation
    // -----------------------------------------------------------------------

    /// Create an inbound call (provider → device).
    pub async fn create_inbound_call(
        &mut self,
        invite: &SipMessage,
        from_addr: SocketAddr,
        provider_id: &str,
        provider_config: &ProviderConfig,
        config: &AppConfig,
        registrar: &Registrar,
        rtp_pool: &mut RtpPortPool,
        socket: &UdpSocket,
        public_ip: Option<&str>,
        tts_engine: Arc<Mutex<TtsEngine>>,
    ) -> Option<InboundCallCreated> {
        let call_id = self.next_call_id();
        let lan_ip = &config.proxy.lan_ip;
        let lan_port = config.proxy.lan_port;
        let sip_call_id = invite.call_id().to_string();

        // Extract caller/callee info.
        let from_header = invite.get_header("From").unwrap_or("");
        let caller_number = normalize_routing_identity(from_header);
        let called_number = extract_inbound_called_number(invite);

        // Resolve via the configured inbound routing table. The matched route
        // is the source of truth for which external numbers this provider is
        // allowed to deliver to us.
        //
        // TODO: Multi-target inbound fork is not yet implemented.
        //   - `route.device_ids` beyond the first registered target are ignored.
        //   - `ring_browsers` is informational only — browsers see a toast but
        //     do not race the SIP device. First-to-answer-wins requires a
        //     multi-leg fork + per-leg CANCEL, which is not built yet.
        let route = match config.resolve_inbound_route(provider_id, &called_number, &caller_number)
        {
            Some(route) => route,
            None => {
                emit_inbound_diagnostic(
                    &self.out_tx,
                    "route_miss",
                    invite,
                    from_addr,
                    provider_id,
                    &called_number,
                    &caller_number,
                );
                let resp = SipMessage::create_response(404, "Not Found", invite, None);
                let _ = socket.send_to(&resp.serialize(), from_addr).await;
                return None;
            }
        };
        let ring_browsers = route.ring_browsers;

        // IVR routing: if the route targets an IVR menu, go there directly.
        if let Some(ref ivr_menu_id) = route.ivr_menu_id {
            if let Some(ivr) = &config.ivr {
                if ivr.enabled {
                    if let Some(menu) = ivr.menus.iter().find(|m| m.id == *ivr_menu_id) {
                        let call_id = self
                            .route_to_ivr(
                                &call_id,
                                invite,
                                from_addr,
                                &caller_number,
                                provider_id,
                                provider_config,
                                config,
                                rtp_pool,
                                socket,
                                public_ip,
                                menu,
                                &tts_engine,
                            )
                            .await?;
                        return Some(InboundCallCreated {
                            call_id,
                            ring_browsers,
                        });
                    }
                }
            }
        }

        // Explicit no-target inbound routes do not fall back to random devices.
        // They either go to the configured voicemail box or play the unrouted
        // greeting via the default voicemail flow.
        if !route.ring_all_devices && route.device_ids.is_empty() && !route.ring_browsers {
            if let Some(ref fax_box_id) = route.fax_box {
                let call_id = self
                    .route_to_fax(
                        &call_id,
                        invite,
                        from_addr,
                        &caller_number,
                        provider_id,
                        provider_config,
                        config,
                        rtp_pool,
                        socket,
                        public_ip,
                        fax_box_id,
                    )
                    .await?;
                return Some(InboundCallCreated {
                    call_id,
                    ring_browsers,
                });
            }

            let greeting_wav = if route.voicemail_box.is_some() {
                resolve_greeting_wav(config, route.voicemail_box.as_deref(), &tts_engine).await
            } else {
                resolve_unrouted_greeting_wav(&tts_engine).await
            };
            let call_id = self
                .route_to_voicemail(
                    &call_id,
                    invite,
                    from_addr,
                    &caller_number,
                    provider_id,
                    provider_config,
                    config,
                    rtp_pool,
                    socket,
                    public_ip,
                    route.voicemail_box.clone(),
                    greeting_wav,
                )
                .await?;
            return Some(InboundCallCreated {
                call_id,
                ring_browsers,
            });
        }

        // Device targeting is explicit: omitted targets ring any registered
        // device, an empty target list rings nobody, and a populated list rings
        // only those device IDs.
        let device_addr = if route.ring_all_devices {
            self.resolve_first_device(config, registrar)
        } else {
            route
                .device_ids
                .iter()
                .find_map(|id| registrar.get_device_contact(id))
        };

        let device_addr = match device_addr {
            Some(addr) => addr,
            None => {
                if let Some(ref fax_box_id) = route.fax_box {
                    let call_id = self
                        .route_to_fax(
                            &call_id,
                            invite,
                            from_addr,
                            &caller_number,
                            provider_id,
                            provider_config,
                            config,
                            rtp_pool,
                            socket,
                            public_ip,
                            fax_box_id,
                        )
                        .await?;
                    return Some(InboundCallCreated {
                        call_id,
                        ring_browsers,
                    });
                }

                // No device registered → voicemail.
                // Resolve greeting WAV on-demand (may trigger TTS generation).
                let greeting_wav =
                    resolve_greeting_wav(config, route.voicemail_box.as_deref(), &tts_engine).await;
                let call_id = self
                    .route_to_voicemail(
                        &call_id,
                        invite,
                        from_addr,
                        &caller_number,
                        provider_id,
                        provider_config,
                        config,
                        rtp_pool,
                        socket,
                        public_ip,
                        route.voicemail_box.clone(),
                        greeting_wav,
                    )
                    .await?;
                return Some(InboundCallCreated {
                    call_id,
                    ring_browsers,
                });
            }
        };

        // Allocate RTP ports for both legs.
        let provider_rtp = match rtp_pool.allocate().await {
            Some(a) => a,
            None => {
                emit_inbound_diagnostic(
                    &self.out_tx,
                    "provider_rtp_unavailable",
                    invite,
                    from_addr,
                    provider_id,
                    &called_number,
                    &caller_number,
                );
                let resp = SipMessage::create_response(503, "Service Unavailable", invite, None);
                let _ = socket.send_to(&resp.serialize(), from_addr).await;
                return None;
            }
        };
        let device_rtp = match rtp_pool.allocate().await {
            Some(a) => a,
            None => {
                emit_inbound_diagnostic(
                    &self.out_tx,
                    "device_rtp_unavailable",
                    invite,
                    from_addr,
                    provider_id,
                    &called_number,
                    &caller_number,
                );
                let resp = SipMessage::create_response(503, "Service Unavailable", invite, None);
                let _ = socket.send_to(&resp.serialize(), from_addr).await;
                return None;
            }
        };

        // Create the call with a mixer.
        let (mixer_cmd_tx, mixer_task) = spawn_mixer(call_id.clone(), self.out_tx.clone());
        let mut call = Call::new(
            call_id.clone(),
            CallDirection::Inbound,
            provider_id.to_string(),
            mixer_cmd_tx,
            mixer_task,
        );
        call.caller_number = Some(caller_number);
        call.callee_number = Some(called_number);
        call.state = CallState::Ringing;

        let mut codec_pt = provider_config.codecs.first().copied().unwrap_or(9);
        let mut media_protocol = "rtp";

        // Provider leg — extract media and negotiated codec from SDP.
        let mut provider_media: Option<SocketAddr> = None;
        if invite.has_sdp_body() {
            if let Some(ep) = parse_sdp_endpoint(&invite.body) {
                if let Ok(addr) = format!("{}:{}", ep.address, ep.port).parse() {
                    provider_media = Some(addr);
                }
                media_protocol = endpoint_media_protocol(&ep);
                // Use the codec from the provider's SDP offer (what they actually want to use).
                if let Some(pt) = ep.codec_pt {
                    codec_pt = pt;
                }
            }
        }

        let provider_leg_id = format!("{call_id}-prov");
        call.legs.insert(
            provider_leg_id.clone(),
            LegInfo {
                id: provider_leg_id.clone(),
                kind: LegKind::SipProvider,
                state: LegState::Connected, // Provider already connected (sent us the INVITE).
                codec_pt,
                media_protocol,
                media_io_active: false,
                sip_leg: None,
                sip_call_id: Some(sip_call_id.clone()),
                webrtc_session_id: None,
                rtp_socket: Some(provider_rtp.socket.clone()),
                rtp_port: provider_rtp.port,
                public_ip: public_ip.map(|s| s.to_string()),
                remote_media: provider_media,
                signaling_addr: Some(from_addr),
                metadata: HashMap::new(),
            },
        );

        // Device leg.
        let device_leg_id = format!("{call_id}-dev");
        call.legs.insert(
            device_leg_id.clone(),
            LegInfo {
                id: device_leg_id.clone(),
                kind: LegKind::SipDevice,
                state: LegState::Inviting,
                codec_pt,
                media_protocol,
                media_io_active: false,
                sip_leg: None,
                sip_call_id: Some(sip_call_id.clone()), // Same SIP Call-ID for passthrough.
                webrtc_session_id: None,
                rtp_socket: Some(device_rtp.socket.clone()),
                rtp_port: device_rtp.port,
                public_ip: None,
                remote_media: None, // Learned from device's 200 OK.
                signaling_addr: Some(device_addr),
                metadata: HashMap::new(),
            },
        );

        // Register SIP Call-ID → both legs (provider leg handles provider messages).
        // For passthrough, both legs share the same SIP Call-ID.
        // We route based on source address in route_passthrough_message.
        self.sip_index.insert(
            sip_call_id.clone(),
            (call_id.clone(), provider_leg_id.clone()),
        );

        // Rewrite and forward INVITE to device.
        let mut fwd_invite = invite.clone();
        fwd_invite.set_request_uri(&rewrite_sip_uri(
            fwd_invite.request_uri().unwrap_or(""),
            &device_addr.ip().to_string(),
            device_addr.port(),
        ));
        fwd_invite.prepend_header("Record-Route", &format!("<sip:{lan_ip}:{lan_port};lr>"));

        // Rewrite SDP: tell the device to send RTP to the device leg's port.
        if fwd_invite.has_sdp_body() {
            let (new_body, _) = rewrite_sdp(&fwd_invite.body, lan_ip, device_rtp.port);
            fwd_invite.body = new_body;
            fwd_invite.update_content_length();
        }

        let _ = socket.send_to(&fwd_invite.serialize(), device_addr).await;

        // Store the call.
        self.calls.insert(call_id.clone(), call);

        // Emit leg_added for both initial legs.
        if let Some(call) = self.calls.get(&call_id) {
            if let Some(leg) = call.legs.get(&provider_leg_id) {
                emit_leg_added_event(&self.out_tx, &call_id, leg);
            }
            if let Some(leg) = call.legs.get(&device_leg_id) {
                emit_leg_added_event(&self.out_tx, &call_id, leg);
            }
        }

        Some(InboundCallCreated {
            call_id,
            ring_browsers,
        })
    }

    /// Initiate an outbound B2BUA call from the dashboard.
    /// Creates a Call with a single SipLeg (provider). WebRTC leg added later via webrtc_link.
    pub async fn make_outbound_call(
        &mut self,
        number: &str,
        provider_config: &ProviderConfig,
        config: &AppConfig,
        rtp_pool: &mut RtpPortPool,
        socket: &UdpSocket,
        public_ip: Option<&str>,
        registered_aor: &str,
    ) -> Option<String> {
        let call_id = self.next_call_id();
        let lan_ip = &config.proxy.lan_ip;
        let lan_port = config.proxy.lan_port;

        let provider_dest: SocketAddr = match provider_config.outbound_proxy.to_socket_addr() {
            Some(a) => a,
            None => return None,
        };

        let rtp_alloc = match rtp_pool.allocate().await {
            Some(a) => a,
            None => return None,
        };

        let sip_call_id = generate_call_id(None);

        // Create SipLeg for provider.
        let leg_config = SipLegConfig {
            lan_ip: lan_ip.clone(),
            lan_port,
            public_ip: public_ip.map(|s| s.to_string()),
            sip_target: provider_dest,
            username: Some(provider_config.username.clone()),
            password: Some(provider_config.password.clone()),
            registered_aor: Some(registered_aor.to_string()),
            codecs: provider_config.codecs.clone(),
            rtp_port: rtp_alloc.port,
        };

        let leg_id = format!("{call_id}-prov");
        let mut sip_leg = SipLeg::new(leg_id.clone(), leg_config);

        // Send INVITE.
        let to_uri = format!("sip:{number}@{}", provider_config.domain);
        sip_leg
            .send_invite(registered_aor, &to_uri, &sip_call_id, socket)
            .await;

        // Create call with mixer.
        let (mixer_cmd_tx, mixer_task) = spawn_mixer(call_id.clone(), self.out_tx.clone());
        let mut call = Call::new(
            call_id.clone(),
            CallDirection::Outbound,
            provider_config.id.clone(),
            mixer_cmd_tx,
            mixer_task,
        );
        call.callee_number = Some(number.to_string());

        let codec_pt = provider_config.codecs.first().copied().unwrap_or(9);

        call.legs.insert(
            leg_id.clone(),
            LegInfo {
                id: leg_id.clone(),
                kind: LegKind::SipProvider,
                state: LegState::Inviting,
                codec_pt,
                media_protocol: "rtp",
                media_io_active: false,
                sip_leg: Some(sip_leg),
                sip_call_id: Some(sip_call_id.clone()),
                webrtc_session_id: None,
                rtp_socket: Some(rtp_alloc.socket.clone()),
                rtp_port: rtp_alloc.port,
                public_ip: public_ip.map(|s| s.to_string()),
                remote_media: None,
                signaling_addr: Some(provider_dest),
                metadata: HashMap::new(),
            },
        );

        // Register for SIP routing.
        self.sip_index
            .insert(sip_call_id, (call_id.clone(), leg_id));

        self.calls.insert(call_id.clone(), call);

        // Emit leg_added for the provider leg.
        if let Some(call) = self.calls.get(&call_id) {
            for leg in call.legs.values() {
                emit_leg_added_event(&self.out_tx, &call_id, leg);
            }
        }

        Some(call_id)
    }

    /// Create a device-originated outbound call (device → provider) using hybrid B2BUA.
    ///
    /// The device side is a simple passthrough leg (no SipLeg needed).
    /// The provider side uses a full SipLeg for proper dialog management,
    /// 407 auth, correct From URI, and public IP in SDP.
    pub async fn create_device_outbound_call(
        &mut self,
        invite: &SipMessage,
        from_addr: SocketAddr,
        provider_config: &ProviderConfig,
        config: &AppConfig,
        rtp_pool: &mut RtpPortPool,
        socket: &UdpSocket,
        public_ip: Option<&str>,
        registered_aor: &str,
    ) -> Option<String> {
        let call_id = self.next_call_id();
        let lan_ip = &config.proxy.lan_ip;
        let lan_port = config.proxy.lan_port;
        let device_sip_call_id = invite.call_id().to_string();

        // Extract just the user part from the request URI (e.g., "sip:16196000@10.0.0.1" → "16196000").
        // extract_uri is for header values with angle brackets, not bare request URIs.
        let dialed_number = invite
            .request_uri()
            .map(|uri| {
                let stripped = uri
                    .strip_prefix("sip:")
                    .or_else(|| uri.strip_prefix("sips:"))
                    .unwrap_or(uri);
                stripped.split('@').next().unwrap_or(stripped).to_string()
            })
            .unwrap_or_default();

        let provider_dest: SocketAddr = match provider_config.outbound_proxy.to_socket_addr() {
            Some(a) => a,
            None => return None,
        };

        // Send 100 Trying to device immediately to stop retransmissions.
        let trying = SipMessage::create_response(100, "Trying", invite, None);
        let _ = socket.send_to(&trying.serialize(), from_addr).await;

        // Allocate RTP ports for both legs.
        let device_rtp = match rtp_pool.allocate().await {
            Some(a) => a,
            None => return None,
        };
        let provider_rtp = match rtp_pool.allocate().await {
            Some(a) => a,
            None => return None,
        };

        let codec_pt = provider_config.codecs.first().copied().unwrap_or(9);
        let mut device_media_protocol = "rtp";

        // Create call with mixer.
        let (mixer_cmd_tx, mixer_task) = spawn_mixer(call_id.clone(), self.out_tx.clone());
        let mut call = Call::new(
            call_id.clone(),
            CallDirection::Outbound,
            provider_config.id.clone(),
            mixer_cmd_tx,
            mixer_task,
        );
        call.callee_number = Some(dialed_number.clone());
        call.device_invite = Some(invite.clone());

        // --- Device leg (passthrough, no SipLeg) ---
        let device_leg_id = format!("{call_id}-dev");
        let mut device_media: Option<SocketAddr> = None;
        if invite.has_sdp_body() {
            if let Some(ep) = parse_sdp_endpoint(&invite.body) {
                if let Ok(addr) = format!("{}:{}", ep.address, ep.port).parse() {
                    device_media = Some(addr);
                }
                device_media_protocol = endpoint_media_protocol(&ep);
            }
        }

        call.legs.insert(
            device_leg_id.clone(),
            LegInfo {
                id: device_leg_id.clone(),
                kind: LegKind::SipDevice,
                state: LegState::Inviting, // Not connected yet — waiting for provider answer
                codec_pt,
                media_protocol: device_media_protocol,
                media_io_active: false,
                sip_leg: None,
                sip_call_id: Some(device_sip_call_id.clone()),
                webrtc_session_id: None,
                public_ip: None,
                rtp_socket: Some(device_rtp.socket.clone()),
                rtp_port: device_rtp.port,
                remote_media: device_media,
                signaling_addr: Some(from_addr),
                metadata: HashMap::new(),
            },
        );

        // Register device's SIP Call-ID → device leg.
        self.sip_index
            .insert(device_sip_call_id, (call_id.clone(), device_leg_id));

        // --- Provider leg (B2BUA with SipLeg) ---
        let provider_leg_id = format!("{call_id}-prov");
        let provider_sip_call_id = generate_call_id(None);

        let leg_config = SipLegConfig {
            lan_ip: lan_ip.clone(),
            lan_port,
            public_ip: public_ip.map(|s| s.to_string()),
            sip_target: provider_dest,
            username: Some(provider_config.username.clone()),
            password: Some(provider_config.password.clone()),
            registered_aor: Some(registered_aor.to_string()),
            codecs: provider_config.codecs.clone(),
            rtp_port: provider_rtp.port,
        };

        let mut sip_leg = SipLeg::new(provider_leg_id.clone(), leg_config);

        // Build proper To URI and send INVITE.
        let to_uri = format!("sip:{}@{}", dialed_number, provider_config.domain);
        let initial_sdp = if device_media_protocol == "t38-udptl" {
            let advertise_ip = public_ip.unwrap_or(lan_ip.as_str());
            build_t38_sdp(advertise_ip, provider_rtp.port)
        } else {
            let advertise_ip = public_ip.unwrap_or(lan_ip.as_str());
            build_sdp(&SdpOptions {
                ip: advertise_ip,
                port: provider_rtp.port,
                payload_types: &provider_config.codecs,
                ..Default::default()
            })
        };
        sip_leg
            .send_invite_with_sdp(
                registered_aor,
                &to_uri,
                &provider_sip_call_id,
                socket,
                initial_sdp,
            )
            .await;

        call.legs.insert(
            provider_leg_id.clone(),
            LegInfo {
                id: provider_leg_id.clone(),
                kind: LegKind::SipProvider,
                state: LegState::Inviting,
                codec_pt,
                media_protocol: if device_media_protocol == "t38-udptl" {
                    "t38-udptl"
                } else {
                    "rtp"
                },
                media_io_active: false,
                sip_leg: Some(sip_leg),
                sip_call_id: Some(provider_sip_call_id.clone()),
                webrtc_session_id: None,
                rtp_socket: Some(provider_rtp.socket.clone()),
                rtp_port: provider_rtp.port,
                public_ip: public_ip.map(|s| s.to_string()),
                remote_media: None,
                signaling_addr: Some(provider_dest),
                metadata: HashMap::new(),
            },
        );

        // Register provider's SIP Call-ID → provider leg.
        self.sip_index
            .insert(provider_sip_call_id, (call_id.clone(), provider_leg_id));

        self.calls.insert(call_id.clone(), call);

        // Emit leg_added for both initial legs (device + provider).
        if let Some(call) = self.calls.get(&call_id) {
            for leg in call.legs.values() {
                emit_leg_added_event(&self.out_tx, &call_id, leg);
            }
        }

        Some(call_id)
    }

    // -----------------------------------------------------------------------
    // Leg management (mid-call add/remove)
    // -----------------------------------------------------------------------

    /// Add a SIP leg to an existing call (e.g., add external participant).
    pub async fn add_external_leg(
        &mut self,
        call_id: &str,
        number: &str,
        provider_config: &ProviderConfig,
        config: &AppConfig,
        rtp_pool: &mut RtpPortPool,
        socket: &UdpSocket,
        public_ip: Option<&str>,
        registered_aor: &str,
    ) -> Option<String> {
        self.calls.get(call_id)?; // existence check; the call is re-fetched via get_mut below
        let lan_ip = &config.proxy.lan_ip;
        let lan_port = config.proxy.lan_port;

        let provider_dest: SocketAddr = provider_config.outbound_proxy.to_socket_addr()?;
        let rtp_alloc = rtp_pool.allocate().await?;
        let sip_call_id = generate_call_id(None);
        let leg_id = self.next_leg_id();

        let leg_config = SipLegConfig {
            lan_ip: lan_ip.clone(),
            lan_port,
            public_ip: public_ip.map(|s| s.to_string()),
            sip_target: provider_dest,
            username: Some(provider_config.username.clone()),
            password: Some(provider_config.password.clone()),
            registered_aor: Some(registered_aor.to_string()),
            codecs: provider_config.codecs.clone(),
            rtp_port: rtp_alloc.port,
        };

        let mut sip_leg = SipLeg::new(leg_id.clone(), leg_config);
        let to_uri = format!("sip:{number}@{}", provider_config.domain);
        sip_leg
            .send_invite(registered_aor, &to_uri, &sip_call_id, socket)
            .await;

        let codec_pt = provider_config.codecs.first().copied().unwrap_or(9);

        let leg_info = LegInfo {
            id: leg_id.clone(),
            kind: LegKind::SipProvider,
            state: LegState::Inviting,
            codec_pt,
            media_protocol: "rtp",
            media_io_active: false,
            sip_leg: Some(sip_leg),
            sip_call_id: Some(sip_call_id.clone()),
            webrtc_session_id: None,
            rtp_socket: Some(rtp_alloc.socket.clone()),
            rtp_port: rtp_alloc.port,
            public_ip: public_ip.map(|s| s.to_string()),
            remote_media: None,
            signaling_addr: Some(provider_dest),
            metadata: HashMap::new(),
        };

        self.sip_index
            .insert(sip_call_id, (call_id.to_string(), leg_id.clone()));

        let call = self.calls.get_mut(call_id).unwrap();
        call.legs.insert(leg_id.clone(), leg_info);

        if let Some(call) = self.calls.get(call_id) {
            if let Some(leg) = call.legs.get(&leg_id) {
                emit_leg_added_event(&self.out_tx, call_id, leg);
            }
        }

        Some(leg_id)
    }

    /// Add a local SIP device to an existing call (mid-call INVITE to desk phone).
    pub async fn add_device_leg(
        &mut self,
        call_id: &str,
        device_id: &str,
        registrar: &Registrar,
        config: &AppConfig,
        rtp_pool: &mut RtpPortPool,
        socket: &UdpSocket,
    ) -> Option<String> {
        let device_addr = registrar.get_device_contact(device_id)?;
        self.calls.get(call_id)?; // existence check; the call is re-fetched via get_mut below
        let lan_ip = &config.proxy.lan_ip;
        let lan_port = config.proxy.lan_port;

        let rtp_alloc = rtp_pool.allocate().await?;
        let sip_call_id = generate_call_id(None);
        let leg_id = self.next_leg_id();

        // Use G.722 by default for local devices (most SIP phones support it).
        let codec_pt: u8 = 9;

        // Build a B2BUA SipLeg targeting the device.
        let leg_config = SipLegConfig {
            lan_ip: lan_ip.clone(),
            lan_port,
            public_ip: None, // local device — no public IP needed
            sip_target: device_addr,
            username: None,
            password: None,
            registered_aor: None,
            codecs: vec![codec_pt, 0], // G.722, PCMU fallback
            rtp_port: rtp_alloc.port,
        };

        let mut sip_leg = SipLeg::new(leg_id.clone(), leg_config);
        let to_uri = format!(
            "sip:{}@{}:{}",
            device_id,
            device_addr.ip(),
            device_addr.port()
        );
        let from_uri = format!("sip:sipproxy@{lan_ip}:{lan_port}");
        sip_leg
            .send_invite(&from_uri, &to_uri, &sip_call_id, socket)
            .await;

        let leg_info = LegInfo {
            id: leg_id.clone(),
            kind: LegKind::SipDevice,
            state: LegState::Inviting,
            codec_pt,
            media_protocol: "rtp",
            media_io_active: false,
            sip_leg: Some(sip_leg),
            sip_call_id: Some(sip_call_id.clone()),
            webrtc_session_id: None,
            rtp_socket: Some(rtp_alloc.socket.clone()),
            rtp_port: rtp_alloc.port,
            public_ip: None,
            remote_media: None,
            signaling_addr: Some(device_addr),
            metadata: HashMap::new(),
        };

        self.sip_index
            .insert(sip_call_id, (call_id.to_string(), leg_id.clone()));

        let call = self.calls.get_mut(call_id).unwrap();
        call.legs.insert(leg_id.clone(), leg_info);

        if let Some(call) = self.calls.get(call_id) {
            if let Some(leg) = call.legs.get(&leg_id) {
                emit_leg_added_event(&self.out_tx, call_id, leg);
            }
        }

        Some(leg_id)
    }

    /// Remove a leg from a call.
    pub async fn remove_leg(&mut self, call_id: &str, leg_id: &str, socket: &UdpSocket) -> bool {
        let call = match self.calls.get_mut(call_id) {
            Some(c) => c,
            None => return false,
        };

        // Remove from mixer.
        call.remove_leg_from_mixer(leg_id).await;

        // Send BYE if it's a SIP leg.
        if let Some(leg) = call.legs.get_mut(leg_id) {
            if let Some(sip_leg) = &mut leg.sip_leg {
                if let Some(hangup_bytes) = sip_leg.build_hangup() {
                    let _ = socket
                        .send_to(&hangup_bytes, sip_leg.config.sip_target)
                        .await;
                }
            }
            leg.state = LegState::Terminated;

            // Clean up SIP index.
            if let Some(sip_cid) = &leg.sip_call_id {
                self.sip_index.remove(sip_cid);
            }
        }

        emit_event(
            &self.out_tx,
            "leg_removed",
            serde_json::json!({ "call_id": call_id, "leg_id": leg_id }),
        );

        // If fewer than 2 active legs remain, end the call.
        let active_legs = call
            .legs
            .values()
            .filter(|l| l.state != LegState::Terminated)
            .count();
        if active_legs <= 1 {
            let duration = call.duration_secs();
            emit_event(
                &self.out_tx,
                "call_ended",
                serde_json::json!({ "call_id": call_id, "reason": "last_leg", "duration": duration }),
            );
            self.terminate_call(call_id).await;
        }

        true
    }

    /// Transfer a leg from one call to another.
    /// The leg stays connected (same RTP socket, same SIP dialog) but moves
    /// between mixers so it hears the new call's participants.
    pub async fn transfer_leg(
        &mut self,
        source_call_id: &str,
        leg_id: &str,
        target_call_id: &str,
    ) -> bool {
        // Validate both calls exist and the leg is in the source call.
        if !self.calls.contains_key(source_call_id) || !self.calls.contains_key(target_call_id) {
            return false;
        }

        // Remove from source mixer (drops old channels → old I/O tasks exit).
        let source_call = self.calls.get(source_call_id).unwrap();
        source_call.remove_leg_from_mixer(leg_id).await;

        // Take the LegInfo out of the source call.
        let source_call = self.calls.get_mut(source_call_id).unwrap();
        let leg_info = match source_call.legs.remove(leg_id) {
            Some(l) => l,
            None => return false,
        };

        // Emit leg_removed for source call.
        emit_event(
            &self.out_tx,
            "leg_removed",
            serde_json::json!({ "call_id": source_call_id, "leg_id": leg_id }),
        );

        // Update SIP index to point to the target call.
        if let Some(sip_cid) = &leg_info.sip_call_id {
            self.sip_index.insert(
                sip_cid.clone(),
                (target_call_id.to_string(), leg_id.to_string()),
            );
        }

        // Create new channels and I/O tasks for the target mixer.
        let channels = create_leg_channels();
        if let Some(rtp_socket) = &leg_info.rtp_socket {
            spawn_sip_inbound(rtp_socket.clone(), channels.inbound_tx);
            if let Some(remote_media) = leg_info.remote_media {
                spawn_sip_outbound(rtp_socket.clone(), remote_media, channels.outbound_rx);
            }
        }

        // Add to target mixer.
        let target_call = self.calls.get(target_call_id).unwrap();
        target_call
            .add_leg_to_mixer(
                leg_id,
                leg_info.codec_pt,
                channels.inbound_rx,
                channels.outbound_tx,
            )
            .await;

        // Insert leg into target call.
        let target_call = self.calls.get_mut(target_call_id).unwrap();
        target_call.legs.insert(leg_id.to_string(), leg_info);

        // Emit leg_added for target call.
        if let Some(target) = self.calls.get(target_call_id) {
            if let Some(leg) = target.legs.get(leg_id) {
                emit_leg_added_event(&self.out_tx, target_call_id, leg);
            }
        }

        // Check if source call has too few legs remaining.
        let source_call = self.calls.get(source_call_id).unwrap();
        let active_legs = source_call
            .legs
            .values()
            .filter(|l| l.state != LegState::Terminated)
            .count();
        if active_legs <= 1 {
            let duration = source_call.duration_secs();
            emit_event(
                &self.out_tx,
                "call_ended",
                serde_json::json!({
                    "call_id": source_call_id,
                    "reason": "leg_transferred",
                    "duration": duration,
                }),
            );
            self.terminate_call(source_call_id).await;
        }

        true
    }

    /// Replace a leg: terminate the old leg, then dial a new one into the same call.
    /// Returns the new leg ID on success.
    pub async fn replace_leg(
        &mut self,
        call_id: &str,
        old_leg_id: &str,
        number: &str,
        provider_config: &ProviderConfig,
        config: &AppConfig,
        rtp_pool: &mut RtpPortPool,
        socket: &UdpSocket,
        public_ip: Option<&str>,
        registered_aor: &str,
    ) -> Option<String> {
        // Terminate the old leg.
        self.remove_leg(call_id, old_leg_id, socket).await;

        // If the call was terminated because it had too few legs, bail.
        if !self.calls.contains_key(call_id) {
            return None;
        }

        // Dial the replacement.
        self.add_external_leg(
            call_id,
            number,
            provider_config,
            config,
            rtp_pool,
            socket,
            public_ip,
            registered_aor,
        )
        .await
    }

    // -----------------------------------------------------------------------
    // Hangup + cleanup
    // -----------------------------------------------------------------------

    /// Hangup a call by internal call ID.
    pub async fn hangup(&mut self, call_id: &str, socket: &UdpSocket) -> bool {
        let call = match self.calls.get_mut(call_id) {
            Some(c) => c,
            None => return false,
        };

        if call.state == CallState::Terminated {
            return false;
        }

        let duration = call.duration_secs();

        // Send BYE to all SIP legs.
        for leg in call.legs.values_mut() {
            if leg.state == LegState::Terminated {
                continue;
            }
            if let Some(sip_leg) = &mut leg.sip_leg {
                if let Some(hangup_bytes) = sip_leg.build_hangup() {
                    let _ = socket
                        .send_to(&hangup_bytes, sip_leg.config.sip_target)
                        .await;
                }
            } else if let Some(addr) = leg.signaling_addr {
                // Passthrough leg — send a simple BYE.
                if let Some(sip_cid) = &leg.sip_call_id {
                    let bye = format!(
                        "BYE sip:hangup SIP/2.0\r\n\
                         Via: SIP/2.0/UDP 0.0.0.0:0;branch=z9hG4bK-hangup\r\n\
                         Call-ID: {sip_cid}\r\n\
                         CSeq: 99 BYE\r\n\
                         Max-Forwards: 70\r\n\
                         Content-Length: 0\r\n\r\n"
                    );
                    let _ = socket.send_to(bye.as_bytes(), addr).await;
                }
            }
            leg.state = LegState::Terminated;
            emit_leg_state_changed_event(&self.out_tx, call_id, leg);
        }

        emit_event(
            &self.out_tx,
            "call_ended",
            serde_json::json!({ "call_id": call_id, "reason": "hangup_command", "duration": duration }),
        );

        self.terminate_call(call_id).await;
        true
    }

    /// Clean up a terminated call: shutdown mixer, remove from indexes.
    async fn terminate_call(&mut self, call_id: &str) {
        if let Some(mut call) = self.calls.remove(call_id) {
            call.state = CallState::Terminated;
            call.shutdown_mixer().await;

            // Remove all SIP index entries for this call.
            self.sip_index.retain(|_, (cid, _)| cid != call_id);
        }
    }

    // -----------------------------------------------------------------------
    // Fax routing
    // -----------------------------------------------------------------------

    #[allow(clippy::too_many_arguments)]
    async fn route_to_fax(
        &mut self,
        call_id: &str,
        invite: &SipMessage,
        from_addr: SocketAddr,
        caller_number: &str,
        provider_id: &str,
        provider_config: &ProviderConfig,
        config: &AppConfig,
        rtp_pool: &mut RtpPortPool,
        socket: &UdpSocket,
        public_ip: Option<&str>,
        fax_box_id: &str,
    ) -> Option<String> {
        let fax_box = match config
            .faxboxes
            .iter()
            .find(|fax_box| fax_box.id == fax_box_id && fax_box.enabled)
        {
            Some(fax_box) => fax_box,
            None => {
                emit_inbound_diagnostic(
                    &self.out_tx,
                    "fax_box_missing",
                    invite,
                    from_addr,
                    provider_id,
                    &extract_inbound_called_number(invite),
                    caller_number,
                );
                let resp = SipMessage::create_response(404, "Not Found", invite, None);
                let _ = socket.send_to(&resp.serialize(), from_addr).await;
                return None;
            }
        };

        let provider_offer = invite
            .has_sdp_body()
            .then(|| parse_sdp_endpoint(&invite.body))
            .flatten();
        let provider_media_protocol = provider_offer
            .as_ref()
            .map(endpoint_media_protocol)
            .unwrap_or("rtp");

        let codec_pt = if provider_media_protocol == "rtp" {
            match select_audio_fax_codec(&provider_config.codecs) {
                Some(codec) => codec,
                None => {
                    emit_inbound_diagnostic(
                        &self.out_tx,
                        "fax_codec_unavailable",
                        invite,
                        from_addr,
                        provider_id,
                        &extract_inbound_called_number(invite),
                        caller_number,
                    );
                    let resp =
                        SipMessage::create_response(488, "Not Acceptable Here", invite, None);
                    let _ = socket.send_to(&resp.serialize(), from_addr).await;
                    return None;
                }
            }
        } else {
            codec_lib::PT_PCMU
        };

        if provider_media_protocol != "rtp" && provider_media_protocol != "t38-udptl" {
            emit_inbound_diagnostic(
                &self.out_tx,
                "fax_transport_unsupported",
                invite,
                from_addr,
                provider_id,
                &extract_inbound_called_number(invite),
                caller_number,
            );
            let resp = SipMessage::create_response(488, "Not Acceptable Here", invite, None);
            let _ = socket.send_to(&resp.serialize(), from_addr).await;
            return None;
        }

        let rtp_alloc = match rtp_pool.allocate().await {
            Some(a) => a,
            None => {
                emit_inbound_diagnostic(
                    &self.out_tx,
                    "fax_rtp_unavailable",
                    invite,
                    from_addr,
                    provider_id,
                    &extract_inbound_called_number(invite),
                    caller_number,
                );
                let resp = SipMessage::create_response(503, "Service Unavailable", invite, None);
                let _ = socket.send_to(&resp.serialize(), from_addr).await;
                return None;
            }
        };

        let lan_ip = &config.proxy.lan_ip;
        let pub_ip = public_ip.unwrap_or(lan_ip.as_str());
        let sdp = if provider_media_protocol == "t38-udptl" {
            build_t38_sdp(pub_ip, rtp_alloc.port)
        } else {
            sip_proto::helpers::build_sdp(&sip_proto::helpers::SdpOptions {
                ip: pub_ip,
                port: rtp_alloc.port,
                payload_types: &[codec_pt],
                ..Default::default()
            })
        };

        let response = SipMessage::create_response(
            200,
            "OK",
            invite,
            Some(sip_proto::message::ResponseOptions {
                to_tag: Some(sip_proto::helpers::generate_tag()),
                contact: Some(format!("<sip:{}:{}>", lan_ip, config.proxy.lan_port)),
                body: Some(sdp),
                content_type: Some("application/sdp".to_string()),
                ..Default::default()
            }),
        );
        let _ = socket.send_to(&response.serialize(), from_addr).await;

        let provider_media = if let Some(ep) = &provider_offer {
            format!("{}:{}", ep.address, ep.port).parse().ok()
        } else {
            Some(from_addr)
        }
        .unwrap_or(from_addr);

        let timestamp = std::time::SystemTime::now()
            .duration_since(std::time::UNIX_EPOCH)
            .unwrap_or_default()
            .as_millis();
        let fax_dir = format!(".nogit/fax/inboxes/{}", fax_box.id);
        let fax_path = format!("{fax_dir}/fax-{timestamp}.tif");

        let (mixer_cmd_tx, mixer_task) = spawn_mixer(call_id.to_string(), self.out_tx.clone());
        let mut call = Call::new(
            call_id.to_string(),
            CallDirection::Inbound,
            provider_id.to_string(),
            mixer_cmd_tx,
            mixer_task,
        );
        call.state = CallState::Connected;
        call.caller_number = Some(caller_number.to_string());

        let provider_leg_id = format!("{call_id}-prov");
        let mut metadata = HashMap::new();
        metadata.insert("fax_box_id".to_string(), serde_json::json!(fax_box.id));
        metadata.insert("caller_number".to_string(), serde_json::json!(caller_number));
        metadata.insert("fax_file_path".to_string(), serde_json::json!(fax_path));
        metadata.insert(
            "fax_mode".to_string(),
            serde_json::json!(if provider_media_protocol == "t38-udptl" { "inbound-t38" } else { "inbound-audio" }),
        );
        call.legs.insert(
            provider_leg_id.clone(),
            LegInfo {
                id: provider_leg_id.clone(),
                kind: LegKind::SipProvider,
                state: LegState::Connected,
                codec_pt,
                media_protocol: if provider_media_protocol == "t38-udptl" { "t38-udptl" } else { "rtp" },
                media_io_active: true,
                sip_leg: None,
                sip_call_id: Some(invite.call_id().to_string()),
                webrtc_session_id: None,
                rtp_socket: Some(rtp_alloc.socket.clone()),
                rtp_port: rtp_alloc.port,
                public_ip: public_ip.map(|s| s.to_string()),
                remote_media: Some(provider_media),
                signaling_addr: Some(from_addr),
                metadata,
            },
        );

        let (cancel_tx, cancel_rx) = watch::channel(false);
        if provider_media_protocol == "t38-udptl" {
            crate::fax_engine::spawn_inbound_t38_fax(
                call_id.to_string(),
                provider_leg_id.clone(),
                fax_box.id.clone(),
                caller_number.to_string(),
                fax_path,
                rtp_alloc.socket.clone(),
                provider_media,
                self.out_tx.clone(),
                cancel_rx,
            );
            call.install_media_bridge("fax-t38", vec![cancel_tx]);
        } else {
            crate::fax_engine::spawn_inbound_audio_fax(
                call_id.to_string(),
                provider_leg_id.clone(),
                fax_box.id.clone(),
                caller_number.to_string(),
                fax_path,
                rtp_alloc.socket.clone(),
                provider_media,
                codec_pt,
                self.out_tx.clone(),
                cancel_rx,
            );
            call.install_media_bridge("fax-audio", vec![cancel_tx]);
        }

        self.sip_index.insert(
            invite.call_id().to_string(),
            (call_id.to_string(), provider_leg_id.clone()),
        );
        self.calls.insert(call_id.to_string(), call);

        if let Some(call) = self.calls.get(call_id) {
            for leg in call.legs.values() {
                emit_leg_added_event(&self.out_tx, call_id, leg);
            }
        }

        emit_event(
            &self.out_tx,
            "call_answered",
            serde_json::json!({
                "call_id": call_id,
                "provider_media_addr": Some(provider_media.ip().to_string()),
                "provider_media_port": Some(provider_media.port()),
                "media_protocol": provider_media_protocol,
                "sip_pt": codec_pt,
            }),
        );

        Some(call_id.to_string())
    }

    // -----------------------------------------------------------------------
    // Voicemail
    // -----------------------------------------------------------------------

    async fn route_to_voicemail(
        &mut self,
        call_id: &str,
        invite: &SipMessage,
        from_addr: SocketAddr,
        caller_number: &str,
        provider_id: &str,
        provider_config: &ProviderConfig,
        config: &AppConfig,
        rtp_pool: &mut RtpPortPool,
        socket: &UdpSocket,
        public_ip: Option<&str>,
        voicebox_id: Option<String>,
        greeting_wav: Option<String>,
    ) -> Option<String> {
        let lan_ip = &config.proxy.lan_ip;
        let pub_ip = public_ip.unwrap_or(lan_ip.as_str());

        let rtp_alloc = match rtp_pool.allocate().await {
            Some(a) => a,
            None => {
                let called_number = extract_inbound_called_number(invite);
                emit_inbound_diagnostic(
                    &self.out_tx,
                    "voicemail_rtp_unavailable",
                    invite,
                    from_addr,
                    provider_id,
                    &called_number,
                    caller_number,
                );
                let resp = SipMessage::create_response(503, "Service Unavailable", invite, None);
                let _ = socket.send_to(&resp.serialize(), from_addr).await;
                return None;
            }
        };

        let codec_pt = provider_config.codecs.first().copied().unwrap_or(9);
        let provider_media_protocol = if invite.has_sdp_body() {
            parse_sdp_endpoint(&invite.body)
                .map(|ep| endpoint_media_protocol(&ep))
                .unwrap_or("rtp")
        } else {
            "rtp"
        };

        let sdp = sip_proto::helpers::build_sdp(&sip_proto::helpers::SdpOptions {
            ip: pub_ip,
            port: rtp_alloc.port,
            payload_types: &provider_config.codecs,
            ..Default::default()
        });

        let response = SipMessage::create_response(
            200,
            "OK",
            invite,
            Some(sip_proto::message::ResponseOptions {
                to_tag: Some(sip_proto::helpers::generate_tag()),
                contact: Some(format!("<sip:{}:{}>", lan_ip, config.proxy.lan_port)),
                body: Some(sdp),
                content_type: Some("application/sdp".to_string()),
                ..Default::default()
            }),
        );
        let _ = socket.send_to(&response.serialize(), from_addr).await;

        let provider_media = if invite.has_sdp_body() {
            parse_sdp_endpoint(&invite.body)
                .and_then(|ep| format!("{}:{}", ep.address, ep.port).parse().ok())
        } else {
            Some(from_addr)
        };
        let provider_media = provider_media.unwrap_or(from_addr);

        // Create a minimal call for BYE routing.
        let (mixer_cmd_tx, mixer_task) = spawn_mixer(call_id.to_string(), self.out_tx.clone());
        let mut call = Call::new(
            call_id.to_string(),
            CallDirection::Inbound,
            provider_id.to_string(),
            mixer_cmd_tx,
            mixer_task,
        );
        call.state = CallState::Voicemail;
        call.caller_number = Some(caller_number.to_string());

        let provider_leg_id = format!("{call_id}-prov");
        call.legs.insert(
            provider_leg_id.clone(),
            LegInfo {
                id: provider_leg_id.clone(),
                kind: LegKind::SipProvider,
                state: LegState::Connected,
                codec_pt,
                media_protocol: provider_media_protocol,
                media_io_active: false,
                sip_leg: None,
                sip_call_id: Some(invite.call_id().to_string()),
                webrtc_session_id: None,
                rtp_socket: Some(rtp_alloc.socket.clone()),
                rtp_port: rtp_alloc.port,
                public_ip: public_ip.map(|s| s.to_string()),
                remote_media: Some(provider_media),
                signaling_addr: Some(from_addr),
                metadata: HashMap::new(),
            },
        );

        self.sip_index.insert(
            invite.call_id().to_string(),
            (call_id.to_string(), provider_leg_id),
        );
        self.calls.insert(call_id.to_string(), call);

        // Emit leg_added for the provider leg.
        if let Some(call) = self.calls.get(call_id) {
            for leg in call.legs.values() {
                emit_leg_added_event(&self.out_tx, call_id, leg);
            }
        }

        emit_event(
            &self.out_tx,
            "call_answered",
            serde_json::json!({
                "call_id": call_id,
                "provider_media_addr": Some(provider_media.ip().to_string()),
                "provider_media_port": Some(provider_media.port()),
                "media_protocol": provider_media_protocol,
                "sip_pt": codec_pt,
            }),
        );

        // Build recording path.
        let timestamp = std::time::SystemTime::now()
            .duration_since(std::time::UNIX_EPOCH)
            .unwrap_or_default()
            .as_millis();
        let recording_dir = format!(
            ".nogit/voicemail/{}",
            voicebox_id.as_deref().unwrap_or("default")
        );
        let recording_path = format!("{recording_dir}/msg-{timestamp}.wav");
        let out_tx = self.out_tx.clone();
        let call_id_owned = call_id.to_string();
        let caller_owned = caller_number.to_string();
        let voicebox_id_owned = voicebox_id.clone();
        let rtp_socket = rtp_alloc.socket;
        tokio::spawn(async move {
            crate::voicemail::run_voicemail_session(
                rtp_socket,
                provider_media,
                codec_pt,
                voicebox_id_owned,
                greeting_wav,
                recording_path,
                120_000,
                call_id_owned,
                caller_owned,
                out_tx,
            )
            .await;
        });

        Some(call_id.to_string())
    }

    // -----------------------------------------------------------------------
    // IVR routing
    // -----------------------------------------------------------------------

    #[allow(clippy::too_many_arguments)]
    async fn route_to_ivr(
        &mut self,
        call_id: &str,
        invite: &SipMessage,
        from_addr: SocketAddr,
        caller_number: &str,
        provider_id: &str,
        provider_config: &ProviderConfig,
        config: &AppConfig,
        rtp_pool: &mut RtpPortPool,
        socket: &UdpSocket,
        public_ip: Option<&str>,
        menu: &crate::config::IvrMenuConfig,
        tts_engine: &Arc<Mutex<TtsEngine>>,
    ) -> Option<String> {
        let lan_ip = &config.proxy.lan_ip;

        let rtp_alloc = match rtp_pool.allocate().await {
            Some(a) => a,
            None => {
                let called_number = extract_inbound_called_number(invite);
                emit_inbound_diagnostic(
                    &self.out_tx,
                    "ivr_rtp_unavailable",
                    invite,
                    from_addr,
                    provider_id,
                    &called_number,
                    caller_number,
                );
                let resp = SipMessage::create_response(503, "Service Unavailable", invite, None);
                let _ = socket.send_to(&resp.serialize(), from_addr).await;
                return None;
            }
        };

        let codec_pt = provider_config.codecs.first().copied().unwrap_or(9);
        let provider_media_protocol = if invite.has_sdp_body() {
            parse_sdp_endpoint(&invite.body)
                .map(|ep| endpoint_media_protocol(&ep))
                .unwrap_or("rtp")
        } else {
            "rtp"
        };
        let pub_ip = public_ip.unwrap_or(lan_ip.as_str());

        let sdp = sip_proto::helpers::build_sdp(&sip_proto::helpers::SdpOptions {
            ip: pub_ip,
            port: rtp_alloc.port,
            payload_types: &provider_config.codecs,
            ..Default::default()
        });

        let response = SipMessage::create_response(
            200,
            "OK",
            invite,
            Some(sip_proto::message::ResponseOptions {
                to_tag: Some(sip_proto::helpers::generate_tag()),
                contact: Some(format!("<sip:{}:{}>", lan_ip, config.proxy.lan_port)),
                body: Some(sdp),
                content_type: Some("application/sdp".to_string()),
                ..Default::default()
            }),
        );
        let _ = socket.send_to(&response.serialize(), from_addr).await;

        let provider_media = if invite.has_sdp_body() {
            parse_sdp_endpoint(&invite.body)
                .and_then(|ep| format!("{}:{}", ep.address, ep.port).parse().ok())
        } else {
            Some(from_addr)
        };
        let provider_media = provider_media.unwrap_or(from_addr);

        // Create call with IVR state.
        let (mixer_cmd_tx, mixer_task) = spawn_mixer(call_id.to_string(), self.out_tx.clone());
        let mut call = Call::new(
            call_id.to_string(),
            CallDirection::Inbound,
            provider_id.to_string(),
            mixer_cmd_tx.clone(),
            mixer_task,
        );
        call.state = CallState::Ivr;
        call.caller_number = Some(caller_number.to_string());

        let provider_leg_id = format!("{call_id}-prov");
        call.legs.insert(
            provider_leg_id.clone(),
            LegInfo {
                id: provider_leg_id.clone(),
                kind: LegKind::SipProvider,
                state: LegState::Connected,
                codec_pt,
                media_protocol: provider_media_protocol,
                media_io_active: false,
                sip_leg: None,
                sip_call_id: Some(invite.call_id().to_string()),
                webrtc_session_id: None,
                rtp_socket: Some(rtp_alloc.socket.clone()),
                rtp_port: rtp_alloc.port,
                public_ip: public_ip.map(|s| s.to_string()),
                remote_media: Some(provider_media),
                signaling_addr: Some(from_addr),
                metadata: HashMap::new(),
            },
        );

        self.sip_index.insert(
            invite.call_id().to_string(),
            (call_id.to_string(), provider_leg_id.clone()),
        );
        self.calls.insert(call_id.to_string(), call);

        // Emit leg_added for the provider leg.
        if let Some(call) = self.calls.get(call_id) {
            for leg in call.legs.values() {
                emit_leg_added_event(&self.out_tx, call_id, leg);
            }
        }

        emit_event(
            &self.out_tx,
            "call_answered",
            serde_json::json!({
                "call_id": call_id,
                "provider_media_addr": Some(provider_media.ip().to_string()),
                "provider_media_port": Some(provider_media.port()),
                "media_protocol": provider_media_protocol,
                "sip_pt": codec_pt,
            }),
        );

        // Generate the IVR prompt as a live chunked TTS stream so playback can
        // start after the first chunk instead of waiting for a full WAV render.
        let voice = menu.prompt_voice.as_deref().unwrap_or("af_bella");
        let live_prompt = {
            let mut tts = tts_engine.lock().await;
            match tts
                .start_live_prompt(crate::tts::TtsPromptRequest {
                    model_path: crate::tts::DEFAULT_MODEL_PATH.to_string(),
                    voices_path: crate::tts::DEFAULT_VOICES_PATH.to_string(),
                    voice_name: voice.to_string(),
                    text: menu.prompt_text.clone(),
                })
                .await
            {
                Ok(prompt) => Some(prompt),
                Err(e) => {
                    eprintln!("[ivr] live TTS setup failed: {e}");
                    None
                }
            }
        };

        // Load prompt and run interaction via the mixer.
        let out_tx = self.out_tx.clone();
        let call_id_owned = call_id.to_string();
        let expected_digits: Vec<char> = menu
            .entries
            .iter()
            .filter_map(|e| e.digit.chars().next())
            .collect();
        let timeout_ms = menu.timeout_sec.unwrap_or(5) * 1000;

        tokio::spawn(async move {
            if let Some(prompt) = live_prompt {
                let (result_tx, result_rx) = tokio::sync::oneshot::channel();
                let _ = mixer_cmd_tx
                    .send(crate::mixer::MixerCommand::StartInteraction {
                        leg_id: provider_leg_id.clone(),
                        prompt_pcm_frames: prompt.initial_frames,
                        prompt_stream_rx: Some(prompt.stream_rx),
                        prompt_cancel_tx: Some(prompt.cancel_tx),
                        expected_digits: expected_digits.clone(),
                        timeout_ms,
                        result_tx,
                    })
                    .await;

                // Wait for digit or timeout.
                let safety = tokio::time::Duration::from_millis(timeout_ms as u64 + 30000);
                let result = match tokio::time::timeout(safety, result_rx).await {
                    Ok(Ok(r)) => r,
                    Ok(Err(_)) => crate::mixer::InteractionResult::Cancelled,
                    Err(_) => crate::mixer::InteractionResult::Timeout,
                };

                match &result {
                    crate::mixer::InteractionResult::Digit(d) => {
                        eprintln!("[ivr] caller pressed '{d}' on call {call_id_owned}");
                        emit_event(
                            &out_tx,
                            "ivr_digit",
                            serde_json::json!({
                                "call_id": call_id_owned,
                                "digit": d.to_string(),
                            }),
                        );
                    }
                    crate::mixer::InteractionResult::Timeout => {
                        eprintln!("[ivr] timeout on call {call_id_owned}");
                        emit_event(
                            &out_tx,
                            "ivr_timeout",
                            serde_json::json!({ "call_id": call_id_owned }),
                        );
                    }
                    crate::mixer::InteractionResult::Cancelled => {
                        eprintln!("[ivr] cancelled on call {call_id_owned}");
                    }
                }
            } else {
                eprintln!("[ivr] no prompt available for call {call_id_owned}, ending");
                emit_event(
                    &out_tx,
                    "ivr_error",
                    serde_json::json!({
                        "call_id": call_id_owned,
                        "error": "no prompt available",
                    }),
                );
            }
        });

        Some(call_id.to_string())
    }

    // -----------------------------------------------------------------------
    // Internal helpers
    // -----------------------------------------------------------------------

    fn resolve_first_device(
        &self,
        config: &AppConfig,
        registrar: &Registrar,
    ) -> Option<SocketAddr> {
        for device in &config.devices {
            if let Some(addr) = registrar.get_device_contact(&device.id) {
                return Some(addr);
            }
        }
        None
    }
}

/// Resolve the greeting WAV for a voicemail box.
///
/// Priority:
/// 1. Pre-recorded WAV from voicebox config (`greetingWavPath`)
/// 2. On-demand TTS generation from greeting text (cached via `cacheable: true`)
/// 3. Legacy hardcoded paths (`.nogit/voicemail/default/greeting.wav`, etc.)
/// 4. None — voicemail session plays beep only
async fn resolve_greeting_wav(
    config: &AppConfig,
    voicebox_id: Option<&str>,
    tts_engine: &Arc<Mutex<TtsEngine>>,
) -> Option<String> {
    // 1. Look up voicebox config.
    let vb = voicebox_id.and_then(|id| config.voiceboxes.iter().find(|v| v.id == id && v.enabled));

    if let Some(vb) = vb {
        // 2. Pre-recorded WAV takes priority.
        if let Some(ref wav) = vb.greeting_wav_path {
            if Path::new(wav).exists() {
                return Some(wav.clone());
            }
        }
        // 3. TTS on-demand with caching.
        let text = vb.greeting_text.as_deref().unwrap_or(
            "The person you are trying to reach is not available. Please leave a message after the tone.",
        );
        let voice = vb.greeting_voice.as_deref().unwrap_or("af_bella");
        let output = format!(".nogit/tts/voicemail-greeting-{}.wav", vb.id);

        let params = serde_json::json!({
            "model": ".nogit/tts/kokoro-v1.0.onnx",
            "voices": ".nogit/tts/voices.bin",
            "voice": voice,
            "text": text,
            "output": &output,
            "cacheable": true,
        });
        let mut tts = tts_engine.lock().await;
        if tts.generate(&params).await.is_ok() {
            return Some(output);
        }
    }

    // 4. Fallback: legacy hardcoded paths.
    for path in &[
        ".nogit/voicemail/default/greeting.wav",
        ".nogit/voicemail/greeting.wav",
    ] {
        if Path::new(path).exists() {
            return Some(path.to_string());
        }
    }
    None
}

async fn resolve_unrouted_greeting_wav(tts_engine: &Arc<Mutex<TtsEngine>>) -> Option<String> {
    let output = ".nogit/tts/unrouted-number.wav";
    let params = serde_json::json!({
        "model": crate::tts::DEFAULT_MODEL_PATH,
        "voices": crate::tts::DEFAULT_VOICES_PATH,
        "voice": "af_bella",
        "text": "This number is currently not being routed by siprouter.",
        "output": output,
        "cacheable": true,
    });

    let mut tts = tts_engine.lock().await;
    if tts.generate(&params).await.is_ok() {
        Some(output.to_string())
    } else {
        None
    }
}
