//! Legacy `VoiceManager` facade backed by the native gateway transport.

use crate::*;
use base64::Engine as _;
use libremetaverse_structured_data::{OSD, OSDFormat, OSDParser};
use libremetaverse_types::UUID;
use libremetaverse_types::compat::{CancellationToken, Subscription};
use roxmltree::{Document, Node};
use std::collections::HashMap;
use std::sync::{
    Arc, Condvar, Mutex, Weak,
    atomic::{AtomicBool, AtomicU64, Ordering},
};
use std::time::Duration;

fn lock<T>(mutex: &Mutex<T>) -> std::sync::MutexGuard<'_, T> {
    mutex
        .lock()
        .unwrap_or_else(std::sync::PoisonError::into_inner)
}

#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
enum ManagerEvent {
    AccountChannelGetList,
    AccountLogout,
    AuxAudioProperties,
    CaptureDevices,
    ConnectorCreated,
    ConnectorShutdown,
    Login,
    LoginState,
    NewSession,
    ParcelVoiceInfo,
    ParticipantProperties,
    ParticipantState,
    ProvisionAccount,
    RenderDevices,
    SessionConnected,
    SessionCreated,
    SessionState,
    SessionTerminated,
}

#[derive(Clone, Copy)]
enum CapabilityKind {
    Provision,
    Parcel,
}

type Erased = Arc<dyn Fn(&dyn std::any::Any) + Send + Sync>;

#[derive(Default)]
struct ManagerEvents {
    next_id: AtomicU64,
    handlers: Arc<Mutex<HashMap<ManagerEvent, HashMap<u64, Erased>>>>,
}

impl ManagerEvents {
    fn subscribe<T>(&self, key: ManagerEvent, handler: Option<T>) -> Subscription
    where
        T: Clone + Send + Sync + 'static,
    {
        let Some(handler) = handler else {
            return Subscription::detached();
        };
        let id = self.next_id.fetch_add(1, Ordering::Relaxed);
        let erased: Erased = Arc::new(move |value| {
            if let Some(invoke) = value.downcast_ref::<Box<dyn Fn(&T) + Send + Sync>>() {
                invoke(&handler);
            }
        });
        lock(&self.handlers)
            .entry(key)
            .or_default()
            .insert(id, erased);
        let handlers = Arc::downgrade(&self.handlers);
        Subscription::new(move || remove(&handlers, key, id))
    }

    fn emit<T, F>(&self, key: ManagerEvent, invoke: F)
    where
        T: Clone + Send + Sync + 'static,
        F: Fn(&T) + Send + Sync + 'static,
    {
        let handlers: Vec<_> = lock(&self.handlers)
            .get(&key)
            .into_iter()
            .flat_map(|entries| entries.values().cloned())
            .collect();
        let invoke: Box<dyn Fn(&T) + Send + Sync> = Box::new(invoke);
        for handler in handlers {
            handler(&invoke);
        }
    }
}

fn remove(
    handlers: &Weak<Mutex<HashMap<ManagerEvent, HashMap<u64, Erased>>>>,
    key: ManagerEvent,
    id: u64,
) {
    if let Some(handlers) = handlers.upgrade() {
        let mut handlers = lock(&handlers);
        if let Some(event) = handlers.get_mut(&key) {
            event.remove(&id);
        }
    }
}

#[derive(Default)]
struct ManagerState {
    capture_devices: Vec<String>,
    render_devices: Vec<String>,
    channel_map: HashMap<String, String>,
    connector_handle: String,
    account_handle: String,
    status_code: i32,
    tuning_sound_file: String,
}

#[derive(Default)]
struct ResponseSignal {
    completed: Mutex<bool>,
    wake: Condvar,
}

impl ResponseSignal {
    fn notify(&self) {
        *lock(&self.completed) = true;
        self.wake.notify_all();
    }

    fn wait(&self, timeout: Duration) -> bool {
        let completed = lock(&self.completed);
        let (completed, _) = self
            .wake
            .wait_timeout_while(completed, timeout, |value| !*value)
            .unwrap_or_else(std::sync::PoisonError::into_inner);
        *completed
    }
}

struct VoiceManagerInner {
    client: libremetaverse::GridClient,
    gateway: VoiceGateway,
    state: Mutex<ManagerState>,
    events: ManagerEvents,
    signals: Mutex<HashMap<i32, Arc<ResponseSignal>>>,
    subscriptions: Mutex<Vec<Subscription>>,
    disposed: AtomicBool,
}

pub struct VoiceManager {
    pub blocking_timeout: i32,
    pub voice_server: String,
    inner: Arc<VoiceManagerInner>,
}

macro_rules! subscription {
    ($name:ident, $key:ident, $type:ty) => {
        #[must_use]
        pub fn $name(&self, handler: Option<$type>) -> Subscription {
            self.inner.events.subscribe(ManagerEvent::$key, handler)
        }
    };
}

impl VoiceManager {
    pub const DAEMON_ARGS: &'static str = " -p tcp -h -c -ll ";
    pub const DAEMON_LOG_LEVEL: i32 = 1;
    pub const DAEMON_PORT: i32 = 44_124;
    pub const REQUEST_TERMINATOR: &'static str = "\n\n\n";
    pub const VOICE_DEBUG_SERVER: &'static str = "bhd.vivox.com";
    pub const VOICE_MAJOR_VERSION: i32 = 1;
    pub const VOICE_RELEASE_SERVER: &'static str = "bhr.vivox.com";

    pub fn new(client: libremetaverse::GridClient) -> Result<Self, Error> {
        let gateway = VoiceGateway::new(client.clone())?;
        let inner = Arc::new(VoiceManagerInner {
            client,
            gateway,
            state: Mutex::new(ManagerState::default()),
            events: ManagerEvents::default(),
            signals: Mutex::new(HashMap::new()),
            subscriptions: Mutex::new(Vec::new()),
            disposed: AtomicBool::new(false),
        });
        let weak = Arc::downgrade(&inner);
        let subscription = inner.gateway.subscribe_control_line(Arc::new(move |line| {
            if let Some(inner) = weak.upgrade() {
                let facade = VoiceManager {
                    blocking_timeout: 30_000,
                    voice_server: VoiceManager::VOICE_RELEASE_SERVER.into(),
                    inner,
                };
                let _ = facade.process_line(&line);
            }
        }));
        lock(&inner.subscriptions).push(subscription);
        Ok(Self {
            blocking_timeout: 30_000,
            voice_server: Self::VOICE_RELEASE_SERVER.into(),
            inner,
        })
    }

    subscription!(
        subscribe_on_account_channel_get_list,
        AccountChannelGetList,
        VoiceManagerBasicActionCallback
    );
    subscription!(
        subscribe_on_account_logout,
        AccountLogout,
        VoiceManagerBasicActionCallback
    );
    subscription!(
        subscribe_on_aux_audio_properties,
        AuxAudioProperties,
        VoiceManagerAuxAudioPropertiesCallback
    );
    subscription!(
        subscribe_on_capture_devices,
        CaptureDevices,
        VoiceManagerDevicesCallback
    );
    subscription!(
        subscribe_on_connector_created,
        ConnectorCreated,
        VoiceManagerConnectorCreatedCallback
    );
    subscription!(
        subscribe_on_connector_initiate_shutdown,
        ConnectorShutdown,
        VoiceManagerBasicActionCallback
    );
    subscription!(subscribe_on_login, Login, VoiceManagerLoginCallback);
    subscription!(
        subscribe_on_login_state_change,
        LoginState,
        VoiceManagerLoginStateChangeCallback
    );
    subscription!(
        subscribe_on_new_session,
        NewSession,
        VoiceManagerNewSessionCallback
    );
    subscription!(
        subscribe_on_parcel_voice_info,
        ParcelVoiceInfo,
        VoiceManagerParcelVoiceInfoCallback
    );
    subscription!(
        subscribe_on_participant_properties,
        ParticipantProperties,
        VoiceManagerParticipantPropertiesCallback
    );
    subscription!(
        subscribe_on_participant_state_change,
        ParticipantState,
        VoiceManagerParticipantStateChangeCallback
    );
    subscription!(
        subscribe_on_provision_account,
        ProvisionAccount,
        VoiceManagerProvisionAccountCallback
    );
    subscription!(
        subscribe_on_render_devices,
        RenderDevices,
        VoiceManagerDevicesCallback
    );
    subscription!(
        subscribe_on_session_connected,
        SessionConnected,
        VoiceManagerBasicActionCallback
    );
    subscription!(
        subscribe_on_session_created,
        SessionCreated,
        VoiceManagerSessionCreatedCallback
    );
    subscription!(
        subscribe_on_session_state_change,
        SessionState,
        VoiceManagerSessionStateChangeCallback
    );
    subscription!(
        subscribe_on_session_terminated,
        SessionTerminated,
        VoiceManagerBasicActionCallback
    );

    pub fn connect_to_daemon_with_method(&self) -> Result<bool, Error> {
        self.connect_to_daemon_with_string_int32("127.0.0.1".into(), Self::DAEMON_PORT)
    }

    pub fn connect_to_daemon_with_string_int32(
        &self,
        address: String,
        port: i32,
    ) -> Result<bool, Error> {
        self.ensure_active()?;
        self.inner.gateway.connect_to_daemon(address, port)
    }

    pub fn is_daemon_running(&self) -> Result<bool, Error> {
        self.ensure_active()?;
        Ok(self.inner.gateway.daemon_is_running())
    }

    /// Native releases do not bundle or spawn the proprietary daemon.
    pub fn start_daemon(&self) -> Result<bool, Error> {
        self.ensure_active()?;
        Ok(false)
    }

    pub fn stop_daemon(&self) -> Result<(), Error> {
        self.inner.gateway.stop_daemon()
    }

    pub fn get_channel_map(&self) -> Result<HashMap<String, String>, Error> {
        self.ensure_active()?;
        Ok(lock(&self.inner.state).channel_map.clone())
    }

    pub fn current_capture_devices(&self) -> Result<Vec<String>, Error> {
        self.ensure_active()?;
        Ok(lock(&self.inner.state).capture_devices.clone())
    }

    pub fn current_render_devices(&self) -> Result<Vec<String>, Error> {
        self.ensure_active()?;
        Ok(lock(&self.inner.state).render_devices.clone())
    }

    pub fn voice_account_from_uuid(&self, id: UUID) -> Result<String, Error> {
        Ok(format!(
            "x{}",
            base64::engine::general_purpose::STANDARD
                .encode(id.get_bytes()?)
                .replace('+', "-")
                .replace('/', "_")
        ))
    }

    pub fn uuid_from_voice_account(&self, account_name: String) -> Result<UUID, Error> {
        VoiceParticipant::id_from_name(account_name)
    }

    pub fn sipuri_from_voice_account(&self, account: String) -> Result<String, Error> {
        if account.len() > 16 * 1024 || account.chars().any(char::is_control) {
            return Err(Error::Argument);
        }
        Ok(format!("sip:{account}@{}", self.voice_server))
    }

    pub fn request_capture_devices(&self) -> Result<i32, Error> {
        self.inner.gateway.aux_get_capture_devices()
    }

    pub fn request_render_devices(&self) -> Result<i32, Error> {
        self.inner.gateway.aux_get_render_devices()
    }

    pub fn request_create_connector_with_method(&self) -> Result<i32, Error> {
        self.request_create_connector_with_string(self.voice_server.clone())
    }

    pub fn request_create_connector_with_string(&self, voice_server: String) -> Result<i32, Error> {
        let mut logging = VoiceGatewayVoiceLoggingSettings::new()?;
        logging.folder = ".".into();
        logging.file_name_prefix = "vivox-gateway".into();
        let account_server = format!("https://www.{voice_server}/api2/");
        self.inner
            .gateway
            .connector_create("V2 SDK".into(), account_server, 0, u16::MAX, logging)
    }

    pub fn request_login(
        &self,
        account_name: String,
        password: String,
        conn_handle: String,
    ) -> Result<i32, Error> {
        self.inner.gateway.account_login(
            conn_handle,
            account_name,
            password,
            "VerifyAnswer".into(),
            String::new(),
            10,
            false,
        )
    }

    pub fn request_set_render_device(&self, device_name: String) -> Result<i32, Error> {
        self.inner.gateway.aux_set_render_device(device_name)
    }

    pub fn request_start_tuning_mode(&self, duration: i32) -> Result<i32, Error> {
        self.inner.gateway.aux_capture_audio_start(duration)
    }

    pub fn request_stop_tuning_mode(&self) -> Result<i32, Error> {
        self.inner.gateway.aux_capture_audio_stop()
    }

    pub fn request_set_speaker_volume(&self, volume: i32) -> Result<i32, Error> {
        if !(0..=100).contains(&volume) {
            return Err(Error::Argument);
        }
        self.inner.gateway.aux_set_speaker_level(volume)
    }

    pub fn request_set_capture_volume(&self, volume: i32) -> Result<i32, Error> {
        if !(0..=100).contains(&volume) {
            return Err(Error::Argument);
        }
        self.inner.gateway.aux_set_mic_level(volume)
    }

    pub fn request_render_audio_start(&self, file_name: String, loop_: bool) -> Result<i32, Error> {
        lock(&self.inner.state).tuning_sound_file = file_name.clone();
        self.inner.gateway.request_with_string_string(
            "Aux.RenderAudioStart.1".into(),
            Some(format!(
                "{}{}",
                VoiceGateway::make_xml("SoundFilePath".into(), file_name)?,
                VoiceGateway::make_xml("Loop".into(), if loop_ { "1" } else { "0" }.into())?
            )),
        )
    }

    pub fn request_render_audio_stop(&self) -> Result<i32, Error> {
        let file = lock(&self.inner.state).tuning_sound_file.clone();
        self.inner.gateway.request_with_string_string(
            "Aux.RenderAudioStop.1".into(),
            Some(VoiceGateway::make_xml("SoundFilePath".into(), file)?),
        )
    }

    pub fn request_provision_account(&self) -> Result<bool, Error> {
        self.request_capability("ProvisionVoiceAccountRequest", CapabilityKind::Provision)
    }

    pub fn request_parcel_voice_info(&self) -> Result<bool, Error> {
        self.request_capability("ParcelVoiceInfoRequest", CapabilityKind::Parcel)
    }

    pub fn capture_devices(&self) -> Result<Vec<String>, Error> {
        self.blocking_devices(true)
    }

    pub fn render_devices(&self) -> Result<Vec<String>, Error> {
        self.blocking_devices(false)
    }

    pub fn create_connector(&self, status: &mut i32) -> Result<String, Error> {
        let request = self.request_create_connector_with_method()?;
        if request < 0 {
            *status = 0;
            return Ok(String::new());
        }
        let signal = self.signal(request);
        if !signal.wait(self.timeout()) {
            *status = 0;
            return Ok(String::new());
        }
        let state = lock(&self.inner.state);
        *status = state.status_code;
        Ok(if *status == 0 {
            state.connector_handle.clone()
        } else {
            String::new()
        })
    }

    pub fn login(
        &self,
        account_name: String,
        password: String,
        connector_handle: String,
        status: &mut i32,
    ) -> Result<String, Error> {
        let request = self.request_login(account_name, password, connector_handle)?;
        if request < 0 {
            *status = 0;
            return Ok(String::new());
        }
        let signal = self.signal(request);
        if !signal.wait(self.timeout()) {
            *status = 0;
            return Ok(String::new());
        }
        let state = lock(&self.inner.state);
        *status = state.status_code;
        Ok(if *status == 0 {
            state.account_handle.clone()
        } else {
            String::new()
        })
    }

    pub fn dispose(&self) -> Result<(), Error> {
        if self.inner.disposed.swap(true, Ordering::AcqRel) {
            return Ok(());
        }
        self.inner.gateway.dispose()?;
        lock(&self.inner.state).channel_map.clear();
        lock(&self.inner.state).capture_devices.clear();
        lock(&self.inner.state).render_devices.clear();
        lock(&self.inner.signals).clear();
        lock(&self.inner.subscriptions).clear();
        Ok(())
    }

    fn ensure_active(&self) -> Result<(), Error> {
        if self.inner.disposed.load(Ordering::Acquire) {
            Err(Error::InvalidOperation)
        } else {
            Ok(())
        }
    }

    fn request_capability(&self, name: &str, kind: CapabilityKind) -> Result<bool, Error> {
        self.ensure_active()?;
        let network = self.inner.client.network();
        if !network.connected() {
            return Ok(false);
        }
        let Some(simulator) = network.current_sim() else {
            return Ok(false);
        };
        let Some(uri) = simulator.native_capability_uri(name)? else {
            return Ok(false);
        };
        let http = self.inner.client.http_caps_client();
        let weak = Arc::downgrade(&self.inner);
        std::thread::Builder::new()
            .name(format!("vivox-{}-cap", name.to_ascii_lowercase()))
            .spawn(move || {
                let Ok(runtime) = tokio::runtime::Builder::new_current_thread()
                    .enable_all()
                    .build()
                else {
                    return;
                };
                let response = runtime.block_on(
                    http.post_with_uri_osd_format_osd_cancellation_token_i_progress(
                        uri,
                        OSDFormat::Xml,
                        OSD::Map(HashMap::new()),
                        CancellationToken::default(),
                        None,
                    ),
                );
                let Some(inner) = weak.upgrade() else {
                    return;
                };
                let Ok((status, bytes)) = response else {
                    return;
                };
                if !status.is_success_status_code() {
                    return;
                }
                let Ok(OSD::Map(map)) = OSDParser::deserialize_with_bytes(bytes) else {
                    return;
                };
                match kind {
                    CapabilityKind::Provision => {
                        let username = map
                            .get("username")
                            .and_then(|value| value.as_string().ok())
                            .unwrap_or_default();
                        let password = map
                            .get("password")
                            .and_then(|value| value.as_string().ok())
                            .unwrap_or_default();
                        inner
                            .events
                            .emit::<VoiceManagerProvisionAccountCallback, _>(
                                ManagerEvent::ProvisionAccount,
                                move |callback| {
                                    let _ = callback.invoke(username.clone(), password.clone());
                                },
                            );
                    }
                    CapabilityKind::Parcel => {
                        let region = map
                            .get("region_name")
                            .and_then(|value| value.as_string().ok())
                            .unwrap_or_default();
                        let local_id = map
                            .get("parcel_local_id")
                            .and_then(|value| value.as_integer().ok())
                            .unwrap_or_default();
                        let channel = match map.get("voice_credentials") {
                            Some(OSD::Map(credentials)) => credentials
                                .get("channel_uri")
                                .and_then(|value| value.as_string().ok())
                                .filter(|value| !value.is_empty()),
                            _ => None,
                        };
                        inner.events.emit::<VoiceManagerParcelVoiceInfoCallback, _>(
                            ManagerEvent::ParcelVoiceInfo,
                            move |callback| {
                                let _ = callback.invoke(region.clone(), local_id, channel.clone());
                            },
                        );
                    }
                }
            })
            .map_err(|_| Error::InvalidOperation)?;
        Ok(true)
    }

    fn timeout(&self) -> Duration {
        Duration::from_millis(u64::try_from(self.blocking_timeout.max(0)).unwrap_or_default())
    }

    fn signal(&self, cookie: i32) -> Arc<ResponseSignal> {
        lock(&self.inner.signals).entry(cookie).or_default().clone()
    }

    fn blocking_devices(&self, capture: bool) -> Result<Vec<String>, Error> {
        let request = if capture {
            self.request_capture_devices()?
        } else {
            self.request_render_devices()?
        };
        if request < 0 {
            return Ok(Vec::new());
        }
        let signal = self.signal(request);
        if !signal.wait(self.timeout()) {
            return Ok(Vec::new());
        }
        if capture {
            self.current_capture_devices()
        } else {
            self.current_render_devices()
        }
    }

    #[cfg(test)]
    fn process_control_line(&self, line: &str) -> Result<(), Error> {
        self.inner.gateway.process_control_line(line)
    }

    fn process_line(&self, line: &str) -> Result<(), Error> {
        if line.len() > 1024 * 1024 || line.contains("<!DOCTYPE") || line.contains("<!ENTITY") {
            return Err(Error::Argument);
        }
        let document = Document::parse(line).map_err(|_| Error::Argument)?;
        let root = document.root_element();
        let cookie = root
            .descendants()
            .find(|node| node.has_tag_name("Request"))
            .and_then(|node| node.attribute("requestId"))
            .or_else(|| root.attribute("requestId"))
            .and_then(|value| value.parse().ok())
            .unwrap_or(-1);
        let status = value_i32(root, "StatusCode");
        let status_string = value(root, "StatusString");
        if root.has_tag_name("Response") {
            let action = root.attribute("action").unwrap_or_default();
            let mut state = lock(&self.inner.state);
            state.status_code = status;
            match action {
                "Connector.Create.1" => {
                    state.connector_handle = value(root, "ConnectorHandle");
                    let handle = state.connector_handle.clone();
                    drop(state);
                    self.inner
                        .events
                        .emit::<VoiceManagerConnectorCreatedCallback, _>(
                            ManagerEvent::ConnectorCreated,
                            move |callback| {
                                let _ = callback.invoke(
                                    cookie,
                                    status,
                                    status_string.clone(),
                                    handle.clone(),
                                );
                            },
                        );
                }
                "Account.Login.1" => {
                    state.account_handle = value(root, "AccountHandle");
                    let handle = state.account_handle.clone();
                    drop(state);
                    self.inner.events.emit::<VoiceManagerLoginCallback, _>(
                        ManagerEvent::Login,
                        move |callback| {
                            let _ = callback.invoke(
                                cookie,
                                status,
                                status_string.clone(),
                                handle.clone(),
                            );
                        },
                    );
                }
                "Aux.GetCaptureDevices.1" | "Aux.GetRenderDevices.1" => {
                    let capture = action.contains("Capture");
                    let devices = values(
                        root,
                        if capture {
                            "CaptureDevice"
                        } else {
                            "RenderDevice"
                        },
                    );
                    let current = nested(
                        root,
                        if capture {
                            "CurrentCaptureDevice"
                        } else {
                            "CurrentRenderDevice"
                        },
                    );
                    if capture {
                        state.capture_devices = devices;
                    } else {
                        state.render_devices = devices;
                    }
                    drop(state);
                    let key = if capture {
                        ManagerEvent::CaptureDevices
                    } else {
                        ManagerEvent::RenderDevices
                    };
                    self.inner.events.emit::<VoiceManagerDevicesCallback, _>(
                        key,
                        move |callback| {
                            let _ = callback.invoke(
                                cookie,
                                status,
                                status_string.clone(),
                                current.clone(),
                            );
                        },
                    );
                }
                "Session.Create.1" => {
                    let handle = value(root, "SessionHandle");
                    drop(state);
                    self.inner
                        .events
                        .emit::<VoiceManagerSessionCreatedCallback, _>(
                            ManagerEvent::SessionCreated,
                            move |callback| {
                                let _ = callback.invoke(
                                    cookie,
                                    status,
                                    status_string.clone(),
                                    handle.clone(),
                                );
                            },
                        );
                }
                "Account.ChannelGetList.1" => {
                    state.channel_map = channel_map(root);
                    drop(state);
                    self.inner
                        .events
                        .emit::<VoiceManagerBasicActionCallback, _>(
                            ManagerEvent::AccountChannelGetList,
                            move |callback| {
                                let _ = callback.invoke(cookie, status, status_string.clone());
                            },
                        );
                }
                _ => {
                    drop(state);
                    if let Some(key) = basic_action(action) {
                        self.inner
                            .events
                            .emit::<VoiceManagerBasicActionCallback, _>(key, move |callback| {
                                let _ = callback.invoke(cookie, status, status_string.clone());
                            });
                    }
                }
            }
            lock(&self.inner.signals)
                .entry(cookie)
                .or_default()
                .clone()
                .notify();
        } else if root.has_tag_name("Event") {
            self.process_event(root, cookie, status, status_string);
        }
        Ok(())
    }

    fn process_event(&self, root: Node<'_, '_>, cookie: i32, status: i32, status_string: String) {
        match root.attribute("type").unwrap_or_default() {
            "LoginStateChangeEvent" | "AccountLoginStateChangeEvent" => {
                let account = value(root, "AccountHandle");
                let state = value_i32(root, "State");
                self.inner
                    .events
                    .emit::<VoiceManagerLoginStateChangeCallback, _>(
                        ManagerEvent::LoginState,
                        move |callback| {
                            let _ = callback.invoke(
                                cookie,
                                account.clone(),
                                status,
                                status_string.clone(),
                                state,
                            );
                        },
                    );
            }
            "SessionNewEvent" => {
                let account = value(root, "AccountHandle");
                let session = value(root, "SessionHandle");
                let state = value_i32(root, "State");
                let name = value_any(root, &["Name", "ChannelName"]);
                let uri = value_any(root, &["URI", "Uri"]);
                self.inner.events.emit::<VoiceManagerNewSessionCallback, _>(
                    ManagerEvent::NewSession,
                    move |callback| {
                        let _ = callback.invoke(
                            cookie,
                            account.clone(),
                            session.clone(),
                            state,
                            name.clone(),
                            uri.clone(),
                        );
                    },
                );
            }
            "SessionStateChangeEvent" => {
                let uri = value_any(root, &["URI", "Uri"]);
                let session = value(root, "SessionHandle");
                let state = value_i32(root, "State");
                let is_channel = value_bool(root, "IsChannel");
                let name = value_any(root, &["Name", "ChannelName"]);
                self.inner
                    .events
                    .emit::<VoiceManagerSessionStateChangeCallback, _>(
                        ManagerEvent::SessionState,
                        move |callback| {
                            let _ = callback.invoke(
                                cookie,
                                uri.clone(),
                                status,
                                status_string.clone(),
                                session.clone(),
                                state,
                                is_channel,
                                name.clone(),
                            );
                        },
                    );
            }
            "ParticipantStateChangeEvent" => {
                let uri = value_any(root, &["ParticipantUri", "URI", "Uri"]);
                let state = value_i32(root, "State");
                let name = value(root, "AccountName");
                let display_name = value(root, "DisplayName");
                let participant_type = value_i32(root, "ParticipantType");
                self.inner
                    .events
                    .emit::<VoiceManagerParticipantStateChangeCallback, _>(
                        ManagerEvent::ParticipantState,
                        move |callback| {
                            let _ = callback.invoke(
                                cookie,
                                uri.clone(),
                                status,
                                status_string.clone(),
                                state,
                                name.clone(),
                                display_name.clone(),
                                participant_type,
                            );
                        },
                    );
            }
            "ParticipantPropertiesEvent" | "ParticipantUpdatedEvent" => {
                let uri = value_any(root, &["ParticipantUri", "URI", "Uri"]);
                let locally_muted = value_bool(root, "IsLocallyMuted");
                let moderator_muted = value_bool(root, "IsModeratorMuted");
                let speaking = value_bool(root, "IsSpeaking");
                let volume = value_i32(root, "Volume");
                let energy = value_f32(root, "Energy");
                self.inner
                    .events
                    .emit::<VoiceManagerParticipantPropertiesCallback, _>(
                        ManagerEvent::ParticipantProperties,
                        move |callback| {
                            let _ = callback.invoke(
                                cookie,
                                uri.clone(),
                                status,
                                status_string.clone(),
                                locally_muted,
                                moderator_muted,
                                speaking,
                                volume,
                                energy,
                            );
                        },
                    );
            }
            "AuxAudioPropertiesEvent" => {
                let energy = value_f32(root, "MicEnergy");
                self.inner
                    .events
                    .emit::<VoiceManagerAuxAudioPropertiesCallback, _>(
                        ManagerEvent::AuxAudioProperties,
                        move |callback| {
                            let _ = callback.invoke(cookie, energy);
                        },
                    );
            }
            _ => {}
        }
    }
}

fn basic_action(action: &str) -> Option<ManagerEvent> {
    Some(match action {
        "Session.Connect.1" => ManagerEvent::SessionConnected,
        "Session.Terminate.1" => ManagerEvent::SessionTerminated,
        "Account.Logout.1" => ManagerEvent::AccountLogout,
        "Connector.InitiateShutdown.1" => ManagerEvent::ConnectorShutdown,
        _ => return None,
    })
}

fn value_any(root: Node<'_, '_>, names: &[&str]) -> String {
    names
        .iter()
        .map(|name| value(root, name))
        .find(|value| !value.is_empty())
        .unwrap_or_default()
}

fn value_bool(root: Node<'_, '_>, name: &str) -> bool {
    matches!(
        value(root, name).to_ascii_lowercase().as_str(),
        "1" | "true"
    )
}

fn channel_map(root: Node<'_, '_>) -> HashMap<String, String> {
    root.descendants()
        .filter(|node| node.has_tag_name("Channel"))
        .filter_map(|node| {
            let name = value(node, "Name");
            let uri = value_any(node, &["URI", "Uri"]);
            (!name.is_empty() && !uri.is_empty()).then_some((name, uri))
        })
        .collect()
}

fn value(root: Node<'_, '_>, name: &str) -> String {
    root.descendants()
        .find(|node| node.has_tag_name(name))
        .and_then(|node| node.text())
        .unwrap_or_default()
        .to_owned()
}

fn value_i32(root: Node<'_, '_>, name: &str) -> i32 {
    value(root, name).parse().unwrap_or_default()
}

fn value_f32(root: Node<'_, '_>, name: &str) -> f32 {
    value(root, name).parse().unwrap_or_default()
}

fn nested(root: Node<'_, '_>, name: &str) -> String {
    root.descendants()
        .find(|node| node.has_tag_name(name))
        .map(|node| value(node, "Device"))
        .unwrap_or_default()
}

fn values(root: Node<'_, '_>, name: &str) -> Vec<String> {
    root.descendants()
        .filter(|node| node.has_tag_name(name))
        .map(|node| value(node, "Device"))
        .filter(|value| !value.is_empty())
        .collect()
}

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

    #[test]
    fn account_and_sip_conversions_round_trip() {
        let client = libremetaverse::GridClient::new().unwrap();
        let manager = VoiceManager::new(client).unwrap();
        let id = UUID::new_with_string("1673cfd3-8229-4445-8d92-ec3570e5e587".into()).unwrap();
        let account = manager.voice_account_from_uuid(id).unwrap();
        assert_eq!(
            manager.uuid_from_voice_account(account.clone()).unwrap(),
            id
        );
        assert_eq!(
            manager
                .sipuri_from_voice_account(account)
                .unwrap()
                .split('@')
                .count(),
            2
        );
    }

    #[test]
    fn response_parser_updates_device_snapshot_and_invokes_callback() {
        let client = libremetaverse::GridClient::new().unwrap();
        let manager = VoiceManager::new(client).unwrap();
        let observed = Arc::new(Mutex::new(String::new()));
        let output = Arc::clone(&observed);
        let _subscription = manager.subscribe_on_capture_devices(Some(
            VoiceManagerDevicesCallback::from_handler(move |_, _, _, current| {
                *lock(&output) = current;
                Ok(())
            }),
        ));
        manager.process_control_line("<Response requestId=\"7\" action=\"Aux.GetCaptureDevices.1\"><ReturnCode>0</ReturnCode><Results><StatusCode>0</StatusCode><StatusString>OK</StatusString><CaptureDevices><CaptureDevice><Device>USB</Device></CaptureDevice></CaptureDevices><CurrentCaptureDevice><Device>USB</Device></CurrentCaptureDevice></Results></Response>").unwrap();
        assert_eq!(manager.current_capture_devices().unwrap(), ["USB"]);
        assert_eq!(*lock(&observed), "USB");
        assert!(manager.signal(7).wait(Duration::ZERO));
    }

    #[test]
    fn session_and_participant_events_preserve_protocol_payloads() {
        let manager = VoiceManager::new(libremetaverse::GridClient::new().unwrap()).unwrap();
        let (session_sender, session_receiver) = mpsc::channel();
        let _session = manager.subscribe_on_session_state_change(Some(
            VoiceManagerSessionStateChangeCallback::from_handler(
                move |_, uri, status, _, handle, state, channel, name| {
                    session_sender
                        .send((uri, status, handle, state, channel, name))
                        .unwrap();
                    Ok(())
                },
            ),
        ));
        let (participant_sender, participant_receiver) = mpsc::channel();
        let _participant = manager.subscribe_on_participant_properties(Some(
            VoiceManagerParticipantPropertiesCallback::from_handler(
                move |_, uri, _, _, local, moderator, speaking, volume, energy| {
                    participant_sender
                        .send((uri, local, moderator, speaking, volume, energy))
                        .unwrap();
                    Ok(())
                },
            ),
        ));

        manager.process_control_line("<Event type=\"SessionStateChangeEvent\"><StatusCode>0</StatusCode><StatusString>OK</StatusString><SessionHandle>session-1</SessionHandle><URI>sip:channel@example.test</URI><State>4</State><IsChannel>true</IsChannel><ChannelName>Town square</ChannelName></Event>").unwrap();
        manager.process_control_line("<Event type=\"ParticipantPropertiesEvent\"><ParticipantUri>sip:avatar@example.test</ParticipantUri><IsLocallyMuted>0</IsLocallyMuted><IsModeratorMuted>1</IsModeratorMuted><IsSpeaking>true</IsSpeaking><Volume>61</Volume><Energy>0.5</Energy></Event>").unwrap();

        assert_eq!(
            session_receiver.recv().unwrap(),
            (
                "sip:channel@example.test".into(),
                0,
                "session-1".into(),
                4,
                true,
                "Town square".into()
            )
        );
        assert_eq!(
            participant_receiver.recv().unwrap(),
            ("sip:avatar@example.test".into(), false, true, true, 61, 0.5)
        );
    }

    #[test]
    fn channel_list_response_updates_name_to_uri_map() {
        let manager = VoiceManager::new(libremetaverse::GridClient::new().unwrap()).unwrap();
        manager.process_control_line("<Response requestId=\"9\" action=\"Account.ChannelGetList.1\"><Results><StatusCode>0</StatusCode><StatusString>OK</StatusString><Channels><Channel><Name>Town square</Name><URI>sip:town@example.test</URI></Channel></Channels></Results></Response>").unwrap();
        assert_eq!(
            manager
                .get_channel_map()
                .unwrap()
                .get("Town square")
                .map(String::as_str),
            Some("sip:town@example.test")
        );
    }
}
