use anyhow::{anyhow, Context, Result};
use base64::engine::general_purpose::URL_SAFE_NO_PAD;
use base64::Engine;
use just_webrtc::types::{ICECandidate, SessionDescription};
use serde::{Deserialize, Serialize};

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct DirectCodeV1 {
    pub v: u8,
    pub desc: SessionDescription,
    pub candidates: Vec<ICECandidate>,
    #[serde(default)]
    pub label: Option<String>,
}

const PREFIX: &str = "every.channel://";

pub fn encode_code(code: &DirectCodeV1) -> Result<String> {
    let json = serde_json::to_vec(code)?;
    Ok(URL_SAFE_NO_PAD.encode(json))
}

pub fn decode_code(code: &str) -> Result<DirectCodeV1> {
    let bytes = URL_SAFE_NO_PAD
        .decode(code.trim())
        .context("invalid base64url code")?;
    let parsed: DirectCodeV1 = serde_json::from_slice(&bytes).context("invalid code json")?;
    if parsed.v != 1 {
        return Err(anyhow!("unsupported direct code version {}", parsed.v));
    }
    Ok(parsed)
}

pub fn build_direct_link(code_b64: &str) -> String {
    format!("every.channel://direct?c={code_b64}")
}

pub fn encode_direct_link(code: &DirectCodeV1) -> Result<String> {
    let b64 = encode_code(code)?;
    Ok(build_direct_link(&b64))
}

pub fn decode_direct_link(link_or_code: &str) -> Result<DirectCodeV1> {
    let s = link_or_code.trim();
    if !s.starts_with(PREFIX) {
        return decode_code(s);
    }
    let rest = &s[PREFIX.len()..];
    let (path, query) = rest.split_once('?').ok_or_else(|| anyhow!("missing '?'"))?;
    if !path.eq_ignore_ascii_case("direct") {
        return Err(anyhow!("not a direct link"));
    }
    for pair in query.split('&') {
        let pair = pair.trim();
        if pair.is_empty() {
            continue;
        }
        let (k, v) = pair.split_once('=').unwrap_or((pair, ""));
        if k.eq_ignore_ascii_case("c") {
            return decode_code(v);
        }
    }
    Err(anyhow!("missing code parameter"))
}

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

    #[test]
    fn code_roundtrips() {
        let code = DirectCodeV1 {
            v: 1,
            desc: SessionDescription {
                sdp_type: SDPType::Offer,
                sdp: "x".to_string(),
            },
            candidates: vec![ICECandidate {
                candidate: "c".to_string(),
                sdp_mid: Some("0".to_string()),
                sdp_mline_index: Some(0),
                username_fragment: None,
            }],
            label: Some("ec".to_string()),
        };
        let enc = encode_code(&code).unwrap();
        let dec = decode_code(&enc).unwrap();
        assert_eq!(dec, code);
        let link = encode_direct_link(&code).unwrap();
        let dec2 = decode_direct_link(&link).unwrap();
        assert_eq!(dec2, code);
    }
}
