//! Direct call state tracking.
//!
//! Manages the lifecycle of 1:1 direct calls placed via the `_signal` channel.
//! Each call goes through: Pending → Ringing → Active → Ended.

use std::collections::HashMap;
use std::time::{Duration, Instant};

/// State of a direct call.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum DirectCallState {
    /// Offer sent to callee, waiting for response.
    Pending,
    /// Callee acknowledged, ringing.
    Ringing,
    /// Call accepted, media room active.
    Active,
    /// Call ended (hangup, reject, timeout, or error).
    Ended,
}

/// A tracked direct call between two users.
pub struct DirectCall {
    pub call_id: String,
    pub caller_fingerprint: String,
    pub callee_fingerprint: String,
    pub state: DirectCallState,
    pub accept_mode: Option<wzp_proto::CallAcceptMode>,
    /// Private room name (set when accepted).
    pub room_name: Option<String>,
    pub created_at: Instant,
    pub answered_at: Option<Instant>,
    pub ended_at: Option<Instant>,
}

/// Registry of active direct calls.
pub struct CallRegistry {
    calls: HashMap<String, DirectCall>,
}

impl CallRegistry {
    pub fn new() -> Self {
        Self {
            calls: HashMap::new(),
        }
    }

    /// Create a new pending call. Returns the call_id.
    pub fn create_call(&mut self, call_id: String, caller_fp: String, callee_fp: String) -> &DirectCall {
        let call = DirectCall {
            call_id: call_id.clone(),
            caller_fingerprint: caller_fp,
            callee_fingerprint: callee_fp,
            state: DirectCallState::Pending,
            accept_mode: None,
            room_name: None,
            created_at: Instant::now(),
            answered_at: None,
            ended_at: None,
        };
        self.calls.insert(call_id.clone(), call);
        self.calls.get(&call_id).unwrap()
    }

    /// Get a call by ID.
    pub fn get(&self, call_id: &str) -> Option<&DirectCall> {
        self.calls.get(call_id)
    }

    /// Get a mutable call by ID.
    pub fn get_mut(&mut self, call_id: &str) -> Option<&mut DirectCall> {
        self.calls.get_mut(call_id)
    }

    /// Transition to Ringing state.
    pub fn set_ringing(&mut self, call_id: &str) -> bool {
        if let Some(call) = self.calls.get_mut(call_id) {
            if call.state == DirectCallState::Pending {
                call.state = DirectCallState::Ringing;
                return true;
            }
        }
        false
    }

    /// Transition to Active state.
    pub fn set_active(&mut self, call_id: &str, mode: wzp_proto::CallAcceptMode, room: String) -> bool {
        if let Some(call) = self.calls.get_mut(call_id) {
            if call.state == DirectCallState::Pending || call.state == DirectCallState::Ringing {
                call.state = DirectCallState::Active;
                call.accept_mode = Some(mode);
                call.room_name = Some(room);
                call.answered_at = Some(Instant::now());
                return true;
            }
        }
        false
    }

    /// End a call.
    pub fn end_call(&mut self, call_id: &str) -> Option<DirectCall> {
        if let Some(call) = self.calls.get_mut(call_id) {
            call.state = DirectCallState::Ended;
            call.ended_at = Some(Instant::now());
        }
        self.calls.remove(call_id)
    }

    /// Find active/pending calls involving a fingerprint.
    pub fn calls_for_fingerprint(&self, fp: &str) -> Vec<&DirectCall> {
        self.calls.values()
            .filter(|c| {
                c.state != DirectCallState::Ended
                    && (c.caller_fingerprint == fp || c.callee_fingerprint == fp)
            })
            .collect()
    }

    /// Find the peer's fingerprint in a call.
    pub fn peer_fingerprint(&self, call_id: &str, my_fp: &str) -> Option<&str> {
        self.calls.get(call_id).map(|c| {
            if c.caller_fingerprint == my_fp {
                c.callee_fingerprint.as_str()
            } else {
                c.caller_fingerprint.as_str()
            }
        })
    }

    /// Remove calls that have been pending longer than the timeout.
    /// Returns call IDs of expired calls.
    pub fn expire_stale(&mut self, timeout: Duration) -> Vec<DirectCall> {
        let now = Instant::now();
        let expired: Vec<String> = self.calls.iter()
            .filter(|(_, c)| {
                c.state == DirectCallState::Pending
                    && now.duration_since(c.created_at) > timeout
            })
            .map(|(id, _)| id.clone())
            .collect();

        expired.into_iter()
            .filter_map(|id| self.calls.remove(&id))
            .collect()
    }

    /// Number of active (non-ended) calls.
    pub fn active_count(&self) -> usize {
        self.calls.values()
            .filter(|c| c.state != DirectCallState::Ended)
            .count()
    }
}

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

    #[test]
    fn call_lifecycle() {
        let mut reg = CallRegistry::new();
        reg.create_call("c1".into(), "alice".into(), "bob".into());

        assert_eq!(reg.get("c1").unwrap().state, DirectCallState::Pending);
        assert!(reg.set_ringing("c1"));
        assert_eq!(reg.get("c1").unwrap().state, DirectCallState::Ringing);

        assert!(reg.set_active("c1", wzp_proto::CallAcceptMode::AcceptGeneric, "_call:c1".into()));
        assert_eq!(reg.get("c1").unwrap().state, DirectCallState::Active);
        assert_eq!(reg.get("c1").unwrap().room_name.as_deref(), Some("_call:c1"));

        let ended = reg.end_call("c1").unwrap();
        assert_eq!(ended.state, DirectCallState::Ended);
        assert_eq!(reg.active_count(), 0);
    }

    #[test]
    fn expire_stale_calls() {
        let mut reg = CallRegistry::new();
        reg.create_call("c1".into(), "alice".into(), "bob".into());

        // Not expired yet
        let expired = reg.expire_stale(Duration::from_secs(30));
        assert!(expired.is_empty());

        // Force expiry with 0 timeout
        let expired = reg.expire_stale(Duration::from_secs(0));
        assert_eq!(expired.len(), 1);
        assert_eq!(expired[0].call_id, "c1");
    }

    #[test]
    fn peer_lookup() {
        let mut reg = CallRegistry::new();
        reg.create_call("c1".into(), "alice".into(), "bob".into());
        assert_eq!(reg.peer_fingerprint("c1", "alice"), Some("bob"));
        assert_eq!(reg.peer_fingerprint("c1", "bob"), Some("alice"));
    }
}
