use std::sync::Arc;

use crate::config::RtcConfig;
use crate::session::{CallSession, SessionEventReceiver};
use crate::types::{CallState, RtcEvent, SignalingMessage};
use crate::MockWebRtcEngine;

async fn read_events<R>(receiver: &R, count: usize) -> Vec<RtcEvent>
where
    R: SessionEventReceiver + Sync,
{
    let mut items = Vec::new();
    for _ in 0..count {
        if let Ok(event) = receiver.recv_event().await {
            items.push(event);
        }
    }
    items
}

#[tokio::test]
async fn outgoing_call_reaches_connected() {
    let config = RtcConfig::default();
    let engine = Arc::new(MockWebRtcEngine::new());
    let session = CallSession::with_engine(config, engine.clone()).unwrap();
    let offer = session.create_offer().unwrap();
    match offer {
        SignalingMessage::Offer { .. } => {}
        _ => panic!("unexpected offer"),
    }
    assert_eq!(session.state(), CallState::WaitingAnswer);
    session.accept_answer("remote-answer").unwrap();
    assert_eq!(session.state(), CallState::Connected);
    let events = read_events(&session, 4).await;
    assert!(events
        .iter()
        .any(|e| matches!(e, RtcEvent::StateChanged(CallState::CreatingOffer))));
    assert!(events
        .iter()
        .any(|e| matches!(e, RtcEvent::StateChanged(CallState::WaitingAnswer))));
    assert!(events
        .iter()
        .any(|e| matches!(e, RtcEvent::ConnectionEstablished)));
    engine.emit_candidate("local").unwrap();
    let candidate_event = session.next_event().await.unwrap();
    match candidate_event {
        RtcEvent::LocalIceCandidate(SignalingMessage::IceCandidate { .. }) => {}
        _ => panic!("missing candidate"),
    }
}

#[tokio::test]
async fn incoming_call_transitions() {
    let config = RtcConfig::default();
    let engine = Arc::new(MockWebRtcEngine::new());
    let session = CallSession::with_engine(config, engine).unwrap();
    let answer = session.accept_offer("remote-offer").unwrap();
    match answer {
        SignalingMessage::Answer { .. } => {}
        _ => panic!("unexpected answer"),
    }
    assert_eq!(session.state(), CallState::Connected);
    let events = read_events(&session, 3).await;
    assert!(events
        .iter()
        .any(|e| matches!(e, RtcEvent::StateChanged(CallState::IncomingOffer))));
    assert!(events
        .iter()
        .any(|e| matches!(e, RtcEvent::StateChanged(CallState::Connected))));
    assert!(events
        .iter()
        .any(|e| matches!(e, RtcEvent::ConnectionEstablished)));
}

#[tokio::test]
async fn hangup_emits_closed_event() {
    let config = RtcConfig::default();
    let engine = Arc::new(MockWebRtcEngine::new());
    let session = CallSession::with_engine(config, engine).unwrap();
    session.create_offer().unwrap();
    session.accept_answer("remote-answer").unwrap();
    let hangup = session.hangup(Some("done")).unwrap();
    match hangup {
        SignalingMessage::Hangup { reason } => assert_eq!(reason, Some("done".to_string())),
        _ => panic!("unexpected hangup"),
    }
    assert_eq!(session.state(), CallState::Ended);
    let events = read_events(&session, 7).await;
    assert!(events
        .iter()
        .any(|e| matches!(e, RtcEvent::StateChanged(CallState::Ending))));
    assert!(events
        .iter()
        .any(|e| matches!(e, RtcEvent::StateChanged(CallState::Ended))));
    assert!(events
        .iter()
        .any(|e| matches!(e, RtcEvent::ConnectionClosed)));
}
