#![forbid(unsafe_code)]

use std::time::Duration;

use crate::handshake::PeerCaps;
use crate::input::camera::{CameraCodec, CameraConfig};

pub const DEFAULT_SERVER_ADDR: &str = "http://38.28.125.112:50051";

#[must_use]
/// Resolve the server address from `--server`, positional args, env, or default.
pub fn resolve_server_addr(args: &[String], env_addr: Option<&str>) -> String {
    args.windows(2)
        .find_map(|pair| {
            (pair[0] == "--server" && !pair[1].starts_with("--")).then(|| pair[1].clone())
        })
        .or_else(|| args.iter().find(|arg| !arg.starts_with("--")).cloned())
        .or_else(|| env_addr.map(ToOwned::to_owned))
        .unwrap_or_else(|| DEFAULT_SERVER_ADDR.to_string())
}

#[must_use]
/// Build local camera capture settings from negotiated peer capabilities.
pub fn camera_config_from_caps(caps: &PeerCaps) -> Option<CameraConfig> {
    let codec = parse_camera_codec(caps.camera_codec.as_deref()?)?;
    Some(CameraConfig {
        codec,
        width: caps.camera_width?,
        height: caps.camera_height?,
        fps: caps.camera_fps?,
    })
}

#[must_use]
/// Clamp queue depth to a floor that keeps renderer bursts stable.
pub fn sanitize_video_queue(queue: Option<usize>) -> usize {
    queue.unwrap_or(256).max(16)
}

#[must_use]
/// Exponential reconnect delay capped at 30 seconds.
pub fn next_delay(current: Duration) -> Duration {
    match current.as_secs() {
        1..=15 => current * 2,
        _ => Duration::from_secs(30),
    }
}

fn parse_camera_codec(raw: &str) -> Option<CameraCodec> {
    match raw.trim().to_ascii_lowercase().as_str() {
        "mjpeg" | "mjpg" | "jpeg" => Some(CameraCodec::Mjpeg),
        "h264" => Some(CameraCodec::H264),
        _ => None,
    }
}

#[cfg(test)]
mod tests {
    use super::{
        DEFAULT_SERVER_ADDR, camera_config_from_caps, next_delay, resolve_server_addr,
        sanitize_video_queue,
    };
    use crate::handshake::PeerCaps;
    use crate::input::camera::CameraCodec;
    use std::time::Duration;

    #[test]
    fn resolve_server_addr_prefers_cli_then_env_then_default() {
        assert_eq!(
            resolve_server_addr(&[String::from("http://cli:1")], Some("http://env:2")),
            "http://cli:1"
        );
        assert_eq!(
            resolve_server_addr(
                &[
                    String::from("--no-launcher"),
                    String::from("--server"),
                    String::from("http://cli-flag:3"),
                ],
                Some("http://env:2"),
            ),
            "http://cli-flag:3"
        );
        assert_eq!(
            resolve_server_addr(&[String::from("--launcher")], Some("http://env:2")),
            "http://env:2"
        );
        assert_eq!(
            resolve_server_addr(&[], Some("http://env:2")),
            "http://env:2"
        );
        assert_eq!(resolve_server_addr(&[], None), DEFAULT_SERVER_ADDR);
    }

    #[test]
    fn camera_config_from_caps_requires_complete_profile() {
        let mut caps = PeerCaps {
            camera: true,
            microphone: false,
            server_version: None,
            camera_output: Some(String::from("uvc")),
            camera_codec: Some(String::from("mjpeg")),
            camera_width: Some(1280),
            camera_height: Some(720),
            camera_fps: Some(25),
            eye_width: None,
            eye_height: None,
            eye_fps: None,
        };

        let config = camera_config_from_caps(&caps).expect("complete caps should map");
        assert!(matches!(config.codec, CameraCodec::Mjpeg));
        assert_eq!(config.width, 1280);

        caps.camera_codec = Some(String::from("vp9"));
        assert!(camera_config_from_caps(&caps).is_none());
    }

    #[test]
    fn sanitize_video_queue_enforces_floor() {
        assert_eq!(sanitize_video_queue(None), 256);
        assert_eq!(sanitize_video_queue(Some(8)), 16);
        assert_eq!(sanitize_video_queue(Some(512)), 512);
    }

    #[test]
    fn next_delay_doubles_until_capped() {
        assert_eq!(next_delay(Duration::from_secs(1)), Duration::from_secs(2));
        assert_eq!(next_delay(Duration::from_secs(15)), Duration::from_secs(30));
        assert_eq!(next_delay(Duration::from_secs(31)), Duration::from_secs(30));
    }
}
