Documentation
use std::io;

use base64::{Engine, engine::general_purpose::URL_SAFE_NO_PAD};
use pubky_common::{auth::jws::ClientId, capabilities::Capabilities, crypto::PublicKey};
use url::Url;

use super::{DeepLinkParseError, GrantApprovalFormat, GrantRelayChannel};

pub(super) fn parse_grant_approval_format(
    url: &Url,
) -> Result<GrantApprovalFormat, DeepLinkParseError> {
    let mut values = url
        .query_pairs()
        .filter(|(key, _)| key == "af")
        .map(|(_, value)| value);
    match (values.next(), values.next()) {
        (None, None) => Ok(GrantApprovalFormat::BareGrant),
        (Some(value), None) if value == "v1" => Ok(GrantApprovalFormat::SignedApprovalV1),
        _ => Err(DeepLinkParseError::InvalidQueryParameter(
            "af",
            Box::new(io::Error::new(
                io::ErrorKind::InvalidInput,
                "expected one af=v1 parameter, or none for a bare grant",
            )),
        )),
    }
}

pub(super) fn append_grant_approval_format(url: &mut Url, format: GrantApprovalFormat) {
    if format == GrantApprovalFormat::SignedApprovalV1 {
        url.query_pairs_mut().append_pair("af", "v1");
    }
}

pub(super) fn parse_capabilities(url: &Url) -> Result<Capabilities, DeepLinkParseError> {
    required_query(url, "caps")?
        .parse()
        .map_err(|e| DeepLinkParseError::InvalidQueryParameter("caps", Box::new(e)))
}

pub(super) fn parse_relay(url: &Url) -> Result<Url, DeepLinkParseError> {
    Url::parse(&required_query(url, "relay")?)
        .map_err(|e| DeepLinkParseError::InvalidQueryParameter("relay", Box::new(e)))
}

pub(super) fn parse_secret(url: &Url) -> Result<[u8; 32], DeepLinkParseError> {
    decode_channel_key(&required_query(url, "secret")?, "secret")
}

fn decode_channel_key(raw: &str, name: &'static str) -> Result<[u8; 32], DeepLinkParseError> {
    let bytes = URL_SAFE_NO_PAD
        .decode(raw)
        .map_err(|error| DeepLinkParseError::InvalidQueryParameter(name, Box::new(error)))?;
    bytes.try_into().map_err(|bytes: Vec<u8>| {
        DeepLinkParseError::InvalidQueryParameter(
            name,
            Box::new(io::Error::new(
                io::ErrorKind::InvalidData,
                format!("expected 32 bytes, got {}", bytes.len()),
            )),
        )
    })
}

pub(super) fn parse_homeserver(url: &Url) -> Result<PublicKey, DeepLinkParseError> {
    PublicKey::try_from_z32(&required_query(url, "hs")?)
        .map_err(|e| DeepLinkParseError::InvalidQueryParameter("hs", Box::new(e)))
}

pub(super) fn parse_client_id(url: &Url) -> Result<ClientId, DeepLinkParseError> {
    ClientId::new(&required_query(url, "cid")?)
        .map_err(|e| DeepLinkParseError::InvalidQueryParameter("cid", Box::new(e)))
}

pub(super) fn parse_client_pk(url: &Url) -> Result<PublicKey, DeepLinkParseError> {
    PublicKey::try_from_z32(&required_query(url, "cpk")?)
        .map_err(|e| DeepLinkParseError::InvalidQueryParameter("cpk", Box::new(e)))
}

pub(super) fn parse_grant_relay_channel(
    url: &Url,
    approval_format: GrantApprovalFormat,
) -> Result<GrantRelayChannel, DeepLinkParseError> {
    let ephemeral_public_key = parse_ephemeral_public_key(url)?;
    let shared_secret = optional_query(url, "secret")
        .map(|secret| decode_channel_key(&secret, "secret"))
        .transpose()?;

    match (ephemeral_public_key, shared_secret, approval_format) {
        (Some(ephemeral_public_key), None, _) => Ok(GrantRelayChannel::Hpke {
            ephemeral_public_key,
        }),
        (Some(_), Some(_), _) => Err(DeepLinkParseError::InvalidQueryParameter(
            "secret",
            Box::new(io::Error::new(
                io::ErrorKind::InvalidInput,
                "secret must be omitted when epk is present",
            )),
        )),
        (None, _, GrantApprovalFormat::SignedApprovalV1) => Err(missing_ephemeral_public_key()),
        (None, Some(secret), GrantApprovalFormat::BareGrant) => {
            Ok(GrantRelayChannel::SharedSecret(secret))
        }
        (None, None, GrantApprovalFormat::BareGrant) => {
            Err(DeepLinkParseError::MissingQueryParameter("secret"))
        }
    }
}

fn missing_ephemeral_public_key() -> DeepLinkParseError {
    DeepLinkParseError::InvalidQueryParameter(
        "epk",
        Box::new(io::Error::new(
            io::ErrorKind::InvalidInput,
            "af=v1 requires one epk parameter",
        )),
    )
}

fn parse_ephemeral_public_key(url: &Url) -> Result<Option<[u8; 32]>, DeepLinkParseError> {
    optional_query(url, "epk")
        .map(|encoded| decode_channel_key(&encoded, "epk"))
        .transpose()
}

pub(super) fn required_query(url: &Url, key: &'static str) -> Result<String, DeepLinkParseError> {
    optional_query(url, key).ok_or(DeepLinkParseError::MissingQueryParameter(key))
}

pub(super) fn optional_query(url: &Url, key: &str) -> Option<String> {
    url.query_pairs()
        .find(|(param_key, _)| param_key == key)
        .map(|(_, value)| value.to_string())
}

pub(super) fn append_signin_params(
    url: &mut Url,
    capabilities: &Capabilities,
    relay: &Url,
    secret: &[u8; 32],
) {
    append_relay_params(url, capabilities, relay);
    url.query_pairs_mut()
        .append_pair("secret", &URL_SAFE_NO_PAD.encode(secret));
}

pub(super) fn append_relay_params(url: &mut Url, capabilities: &Capabilities, relay: &Url) {
    url.query_pairs_mut()
        .append_pair("caps", &capabilities.to_string())
        .append_pair("relay", relay.as_str());
}

pub(super) fn append_signup_params(
    url: &mut Url,
    capabilities: &Capabilities,
    relay: &Url,
    secret: &[u8; 32],
    homeserver: &PublicKey,
    signup_token: Option<&str>,
) {
    append_signin_params(url, capabilities, relay, secret);
    append_signup_details(url, homeserver, signup_token);
}

pub(super) fn append_signup_details(
    url: &mut Url,
    homeserver: &PublicKey,
    signup_token: Option<&str>,
) {
    let mut query = url.query_pairs_mut();
    query.append_pair("hs", &homeserver.z32());
    if let Some(signup_token) = signup_token {
        query.append_pair("st", signup_token);
    }
}

pub(super) fn append_grant_params(url: &mut Url, client_id: &ClientId, client_pk: &PublicKey) {
    url.query_pairs_mut()
        .append_pair("cid", &client_id.to_string())
        .append_pair("cpk", &client_pk.z32());
}

pub(super) fn append_grant_relay_channel(url: &mut Url, channel: GrantRelayChannel) {
    match channel {
        GrantRelayChannel::SharedSecret(secret) => {
            url.query_pairs_mut()
                .append_pair("secret", &URL_SAFE_NO_PAD.encode(secret));
        }
        GrantRelayChannel::Hpke {
            ephemeral_public_key,
        } => {
            url.query_pairs_mut()
                .append_pair("epk", &URL_SAFE_NO_PAD.encode(ephemeral_public_key));
        }
    }
}