use libremetaverse_voice_vivox::VivoxControlClient;
use std::net::Ipv4Addr;
use std::time::Duration;
use tokio::io::{AsyncBufReadExt, AsyncWriteExt, BufReader};
use tokio::net::TcpListener;

const EXPECTED_ACTIONS: [&str; 10] = [
    "Aux.GetCaptureDevices.1",
    "Aux.GetRenderDevices.1",
    "Connector.Create.1",
    "Account.Login.1",
    "Session.Create.1",
    "Session.Connect.1",
    "Session.SetParticipantVolumeForMe.1",
    "Session.Terminate.1",
    "Account.Logout.1",
    "Connector.InitiateShutdown.1",
];

fn response(id: &str, action: &str, result: &str) -> String {
    format!(
        "<Response requestId=\"{id}\" action=\"{action}\"><ReturnCode>0</ReturnCode><Results><StatusCode>0</StatusCode><StatusString>OK</StatusString>{result}</Results></Response>\n\n\n"
    )
}

#[tokio::test]
async fn native_vivox_protocol_preserves_control_order_events_and_teardown() {
    let listener = TcpListener::bind((Ipv4Addr::LOCALHOST, 0)).await.unwrap();
    let endpoint = listener.local_addr().unwrap();
    let server = tokio::spawn(async move {
        let (stream, _) = listener.accept().await.unwrap();
        let (reader, mut writer) = stream.into_split();
        let mut reader = BufReader::new(reader);
        let mut actions = Vec::new();
        loop {
            let mut line = String::new();
            assert_ne!(reader.read_line(&mut line).await.unwrap(), 0);
            let line = line.trim();
            if line.is_empty() {
                continue;
            }
            let id = line
                .split("requestId=\"")
                .nth(1)
                .and_then(|value| value.split('"').next())
                .unwrap();
            let action = line
                .split("action=\"")
                .nth(1)
                .and_then(|value| value.split('"').next())
                .unwrap();
            actions.push(action.to_owned());
            let result = match action {
                "Aux.GetCaptureDevices.1" => {
                    "<CaptureDevices><CaptureDevice><Device>Mic</Device></CaptureDevice></CaptureDevices><CurrentCaptureDevice><Device>Mic</Device></CurrentCaptureDevice>"
                }
                "Aux.GetRenderDevices.1" => {
                    "<RenderDevices><RenderDevice><Device>Speakers</Device></RenderDevice></RenderDevices><CurrentRenderDevice><Device>Speakers</Device></CurrentRenderDevice>"
                }
                "Connector.Create.1" => "<ConnectorHandle>connector-secret</ConnectorHandle>",
                "Account.Login.1" => "<AccountHandle>account-secret</AccountHandle>",
                "Session.Create.1" => "<SessionHandle>session-secret</SessionHandle>",
                "Session.Connect.1"
                | "Session.SetParticipantVolumeForMe.1"
                | "Session.Terminate.1"
                | "Account.Logout.1"
                | "Connector.InitiateShutdown.1" => "",
                _ => panic!("unexpected Vivox action {action}"),
            };
            writer
                .write_all(response(id, action, result).as_bytes())
                .await
                .unwrap();
            if action == "Session.Connect.1" {
                writer.write_all(b"<Event type=\"ParticipantPropertiesEvent\"><SessionHandle>session-secret</SessionHandle><ParticipantURI>sip:participant-token@example.test</ParticipantURI><DisplayName>Alice Resident</DisplayName><IsSpeaking>true</IsSpeaking><Volume>0</Volume><Energy>0.25</Energy></Event>\n\n\n").await.unwrap();
            }
            if action == "Connector.InitiateShutdown.1" {
                writer.shutdown().await.unwrap();
                return actions;
            }
        }
    });

    let mut client = VivoxControlClient::connect(endpoint, Duration::from_secs(2))
        .await
        .unwrap();
    assert_eq!(client.capture_devices().await.unwrap().available, ["Mic"]);
    assert_eq!(
        client.render_devices().await.unwrap().available,
        ["Speakers"]
    );
    client
        .create_connector("https://www.voice.example.test/api2/")
        .await
        .unwrap();
    client.login("voice-user", "voice-password").await.unwrap();
    let session = client
        .create_session("sip:channel-token@example.test", "Parity Region", None)
        .await
        .unwrap();
    client.connect_session(&session).await.unwrap();
    let event = client.next_event().await.unwrap();
    assert_eq!(event.kind, "ParticipantPropertiesEvent");
    assert_eq!(event.display_name.as_deref(), Some("Alice Resident"));
    assert_eq!(event.is_speaking, Some(true));
    let participant = event.participant_uri.unwrap();
    client
        .set_participant_volume(&session, &participant, 0)
        .await
        .unwrap();
    client.terminate_session(&session).await.unwrap();
    client.logout().await.unwrap();
    client.shutdown().await.unwrap();
    client.shutdown().await.unwrap();
    let actions = server.await.unwrap();
    assert_eq!(actions, EXPECTED_ACTIONS);
}
