//! Connection URI parser.
//!
//! Supported shapes (picked once at startup — no runtime swap):
//! - `udp://<host>:<port>` — UDP listener / sender pair.
//! - `serial:///dev/<path>` (or `serial:///dev/<path>?baud=<N>`) — POSIX serial.
//!
//! Anything else is a configuration error surfaced via [`AutopilotError::Config`].

use shared::error::{AutopilotError, Result};

/// Default baud rate for ArduPilot serial telem links per the SITL setups we
/// run against (see `architecture.md §10`).
pub const DEFAULT_SERIAL_BAUD: u32 = 57_600;

#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ConnectionUri {
    /// UDP. The peer endpoint to which we both bind and send.
    Udp { host: String, port: u16 },
    /// POSIX serial device.
    Serial { path: String, baud: u32 },
}

impl ConnectionUri {
    pub fn parse(s: &str) -> Result<Self> {
        if let Some(rest) = s.strip_prefix("udp://") {
            let (host, port) = rest
                .rsplit_once(':')
                .ok_or_else(|| AutopilotError::Config(format!("udp uri missing :port — {s}")))?;
            let port: u16 = port
                .parse()
                .map_err(|_| AutopilotError::Config(format!("invalid udp port — {s}")))?;
            if host.is_empty() {
                return Err(AutopilotError::Config(format!(
                    "udp uri missing host — {s}"
                )));
            }
            return Ok(Self::Udp {
                host: host.to_owned(),
                port,
            });
        }

        if let Some(rest) = s.strip_prefix("serial://") {
            let (path, query) = rest.split_once('?').unwrap_or((rest, ""));
            if path.is_empty() {
                return Err(AutopilotError::Config(format!(
                    "serial uri missing device path — {s}"
                )));
            }
            let mut baud = DEFAULT_SERIAL_BAUD;
            for kv in query.split('&').filter(|p| !p.is_empty()) {
                let (k, v) = kv.split_once('=').ok_or_else(|| {
                    AutopilotError::Config(format!("bad serial query token — {kv}"))
                })?;
                if k == "baud" {
                    baud = v
                        .parse()
                        .map_err(|_| AutopilotError::Config(format!("invalid baud rate — {v}")))?;
                }
            }
            return Ok(Self::Serial {
                path: path.to_owned(),
                baud,
            });
        }

        Err(AutopilotError::Config(format!(
            "unsupported mavlink connection uri scheme — {s}"
        )))
    }
}

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

    #[test]
    fn parses_udp() {
        // Act
        let uri = ConnectionUri::parse("udp://127.0.0.1:14550").unwrap();

        // Assert
        assert_eq!(
            uri,
            ConnectionUri::Udp {
                host: "127.0.0.1".to_owned(),
                port: 14_550,
            }
        );
    }

    #[test]
    fn parses_serial_with_baud_override() {
        // Act
        let uri = ConnectionUri::parse("serial:///dev/ttyUSB0?baud=115200").unwrap();

        // Assert
        assert_eq!(
            uri,
            ConnectionUri::Serial {
                path: "/dev/ttyUSB0".to_owned(),
                baud: 115_200,
            }
        );
    }

    #[test]
    fn rejects_unknown_scheme() {
        // Act
        let err = ConnectionUri::parse("tcp://host:1234").unwrap_err();

        // Assert
        assert!(matches!(err, AutopilotError::Config(_)));
    }
}
