//! HTTP Signatures for ActivityPub.
//!
//! Supports both legacy draft-cavage signatures and RFC 9421 HTTP Message Signatures.

mod rfc9421;

use std::collections::HashMap;

use actix_web::http::header::HeaderMap;
use base64::Engine;
use base64::engine::general_purpose::STANDARD as BASE64;
use chrono::Utc;
use sha2::{Digest, Sha256};
use thiserror::Error;

use crate::activitypub::keys;

#[derive(Debug, Error)]
pub enum SignatureError {
    #[error("missing or malformed signature header")]
    MissingSignature,
    #[error("missing required header: {0}")]
    MissingHeader(String),
    #[error("invalid signature algorithm: {0}")]
    InvalidAlgorithm(String),
    #[error("signature verification failed")]
    VerificationFailed,
    #[error("digest mismatch")]
    DigestMismatch,
    #[error("date too old or too new")]
    DateOutOfRange,
    #[error("key not found: {0}")]
    KeyNotFound(String),
    #[error("invalid key id: {0}")]
    InvalidKeyId(String),
    #[error("actor is gone: {0}")]
    ActorGone(String),
}

/// Build the Signature header value for an outbound POST.
///
/// Signs the request using RSA-SHA256 over the specified headers.
/// Returns the signature header value and the digest header value.
pub fn sign_request(
    private_pem: &str,
    key_id: &str,
    method: &str,
    path: &str,
    host: &str,
    date: &str,
    body: &[u8],
) -> Result<(String, String), SignatureError> {
    let digest = format!("SHA-256={}", BASE64.encode(Sha256::digest(body)));
    let content_type = "application/activity+json";

    // Build the signing string
    let signing_string = format!(
        "(request-target): {method} {path}\nhost: {host}\ndate: {date}\ndigest: {digest}\ncontent-type: {content_type}"
    );

    let signature_bytes = keys::sign(private_pem, signing_string.as_bytes())
        .map_err(|_| SignatureError::VerificationFailed)?;
    let signature_b64 = BASE64.encode(signature_bytes);

    let signature_header = format!(
        "keyId=\"{}\",algorithm=\"rsa-sha256\",headers=\"(request-target) host date digest content-type\",signature=\"{}\"",
        key_id, signature_b64
    );

    Ok((signature_header, digest))
}

/// Build the Signature header value for an outbound GET request.
///
/// GET requests don't have a body, so no digest is computed.
/// Returns the signature header value.
pub fn sign_get(
    private_pem: &str,
    key_id: &str,
    path: &str,
    host: &str,
    date: &str,
) -> Result<String, SignatureError> {
    // Build the signing string - no digest for GET
    let signing_string = format!("(request-target): get {path}\nhost: {host}\ndate: {date}");

    let signature_bytes = keys::sign(private_pem, signing_string.as_bytes())
        .map_err(|_| SignatureError::VerificationFailed)?;
    let signature_b64 = BASE64.encode(signature_bytes);

    let signature_header = format!(
        "keyId=\"{}\",algorithm=\"rsa-sha256\",headers=\"(request-target) host date\",signature=\"{}\"",
        key_id, signature_b64
    );

    Ok(signature_header)
}

/// Verify an inbound request's signature.
///
/// Dispatches to RFC 9421 or legacy verification based on the presence
/// of the signature-input header.
///
/// Returns the verified actor URL on success.
///
/// # Arguments
/// * `scheme` - Request scheme (http or https)
/// * `authority` - Request authority (host:port)
/// * `path` - Request path without query
/// * `query` - Optional query string (without leading ?)
/// * `method` - HTTP method (POST)
/// * `headers` - Request headers
/// * `body` - Request body
/// * `fetch_key` - Callback to fetch the public key for a keyId
#[allow(clippy::too_many_arguments)]
pub async fn verify_request<F, Fut>(
    scheme: &str,
    authority: &str,
    path: &str,
    query: Option<&str>,
    method: &str,
    headers: &HeaderMap,
    body: &[u8],
    fetch_key: F,
) -> Result<String, SignatureError>
where
    F: FnOnce(String) -> Fut,
    Fut: std::future::Future<Output = Result<Option<(String, String)>, SignatureError>>,
{
    // Check if this is an RFC 9421 signature (has signature-input header)
    if headers.get("signature-input").is_some() {
        tracing::debug!("detected RFC 9421 signature, using RFC 9421 verification");
        return rfc9421::verify_request(
            scheme, authority, path, query, method, headers, body, fetch_key,
        )
        .await;
    }

    // Otherwise, use legacy verification
    tracing::debug!("detected legacy signature, using legacy verification");
    verify_request_legacy(method, path, query, headers, body, fetch_key).await
}

/// Legacy draft-cavage signature verification.
async fn verify_request_legacy<F, Fut>(
    method: &str,
    path: &str,
    query: Option<&str>,
    headers: &HeaderMap,
    body: &[u8],
    fetch_key: F,
) -> Result<String, SignatureError>
where
    F: FnOnce(String) -> Fut,
    Fut: std::future::Future<Output = Result<Option<(String, String)>, SignatureError>>,
{
    // Reconstruct path_and_query for legacy signatures
    let path_and_query = if let Some(q) = query {
        format!("{}?{}", path, q)
    } else {
        path.to_string()
    };

    // 1. Parse signature header
    let signature_header = headers
        .get("signature")
        .and_then(|v| v.to_str().ok())
        .ok_or(SignatureError::MissingSignature)?;

    tracing::debug!(header = %signature_header, "parsing signature header");

    let sig_parts = parse_signature_header(signature_header)?;

    // Check algorithm
    let algorithm = sig_parts
        .get("algorithm")
        .map(|s| s.as_str())
        .unwrap_or("rsa-sha256");
    if algorithm != "rsa-sha256" {
        return Err(SignatureError::InvalidAlgorithm(algorithm.to_string()));
    }

    let key_id = sig_parts
        .get("keyId")
        .ok_or_else(|| SignatureError::InvalidKeyId("missing keyId".to_string()))?;
    let headers_list = sig_parts
        .get("headers")
        .ok_or_else(|| SignatureError::MissingHeader("headers list".to_string()))?;
    let signature_b64 = sig_parts
        .get("signature")
        .ok_or_else(|| SignatureError::MissingHeader("signature value".to_string()))?;

    tracing::debug!(key_id = %key_id, headers = %headers_list, "signature components");

    let signed_headers: Vec<&str> = headers_list.split_whitespace().collect();

    // 2. Check required headers
    if !signed_headers.contains(&"(request-target)") {
        return Err(SignatureError::MissingHeader(
            "(request-target)".to_string(),
        ));
    }
    if !signed_headers.contains(&"host") {
        return Err(SignatureError::MissingHeader("host".to_string()));
    }
    if !signed_headers.contains(&"date") {
        return Err(SignatureError::MissingHeader("date".to_string()));
    }

    // 5. Check date within 300 seconds
    let date_header = headers
        .get("date")
        .and_then(|v| v.to_str().ok())
        .ok_or_else(|| SignatureError::MissingHeader("date".to_string()))?;
    let request_date = parse_http_date(date_header).ok_or(SignatureError::DateOutOfRange)?;
    let now = Utc::now();
    let diff = (now - request_date).num_seconds().abs();
    if diff > 300 {
        return Err(SignatureError::DateOutOfRange);
    }

    // 4. Verify digest
    let expected_digest = headers
        .get("digest")
        .and_then(|v| v.to_str().ok())
        .ok_or_else(|| SignatureError::MissingHeader("digest".to_string()))?;
    let actual_digest = format!("SHA-256={}", BASE64.encode(Sha256::digest(body)));
    if expected_digest != actual_digest {
        return Err(SignatureError::DigestMismatch);
    }

    // 3. Rebuild signing string
    let mut signing_parts = Vec::new();
    for header_name in &signed_headers {
        if *header_name == "(request-target)" {
            signing_parts.push(format!(
                "(request-target): {} {path_and_query}",
                method.to_lowercase()
            ));
        } else {
            let value = headers
                .get(*header_name)
                .and_then(|v| v.to_str().ok())
                .ok_or_else(|| SignatureError::MissingHeader(header_name.to_string()))?;
            signing_parts.push(format!("{header_name}: {value}"));
        }
    }
    let signing_string = signing_parts.join("\n");

    tracing::debug!(signing_string = %signing_string, "reconstructed signing string");

    // Fetch the public key
    tracing::debug!(key_id = %key_id, "fetching public key");
    let (actor_url, public_key_pem) = fetch_key(key_id.clone())
        .await?
        .ok_or_else(|| SignatureError::KeyNotFound(key_id.clone()))?;

    tracing::debug!(actor_url = %actor_url, "public key fetched successfully");

    // Verify the signature
    let signature_bytes = BASE64
        .decode(signature_b64)
        .map_err(|_| SignatureError::VerificationFailed)?;

    if !keys::verify(&public_key_pem, signing_string.as_bytes(), &signature_bytes) {
        return Err(SignatureError::VerificationFailed);
    }

    // 6. Return verified actor URL
    Ok(actor_url)
}

/// Parse a signature header into its components.
fn parse_signature_header(header: &str) -> Result<HashMap<String, String>, SignatureError> {
    let mut parts = HashMap::new();
    let mut current_key = String::new();
    let mut current_value = String::new();
    let mut in_quotes = false;
    let mut in_value = false;
    let mut escape_next = false;

    for ch in header.chars() {
        if escape_next {
            current_value.push(ch);
            escape_next = false;
            continue;
        }

        match ch {
            '\\' if in_quotes => escape_next = true,
            '"' => in_quotes = !in_quotes,
            '=' if !in_quotes && !in_value && !current_key.is_empty() => {
                // Key ended, value starts
                in_value = true;
            }
            ',' if !in_quotes => {
                // End of this key=value pair
                if !current_key.is_empty() {
                    parts.insert(current_key.trim().to_string(), current_value.clone());
                    current_key.clear();
                    current_value.clear();
                    in_value = false;
                }
            }
            _ => {
                if !in_value && !in_quotes && ch.is_alphanumeric() {
                    // Building the key
                    current_key.push(ch);
                } else {
                    // Building the value
                    current_value.push(ch);
                }
            }
        }
    }

    // Don't forget the last pair
    if !current_key.is_empty() {
        parts.insert(current_key.trim().to_string(), current_value);
    }

    Ok(parts)
}

/// Parse an HTTP date (RFC 7231 format).
fn parse_http_date(date_str: &str) -> Option<chrono::DateTime<Utc>> {
    // Try RFC 7231 format: "Sun, 06 Nov 1994 08:49:37 GMT"
    chrono::DateTime::parse_from_rfc2822(date_str)
        .ok()
        .map(|dt| dt.with_timezone(&Utc))
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::activitypub::keys;
    use chrono::Datelike;

    #[test]
    fn sign_and_verify_round_trip() {
        let (private_pem, _public_pem) = keys::generate().unwrap();
        let key_id = "https://example.com/actor#main-key";
        let method = "POST";
        let path = "/inbox";
        let host = "example.com";
        let date = "Wed, 21 Oct 2026 07:28:00 GMT";
        let body = b"test body";

        let (signature, digest) =
            sign_request(&private_pem, key_id, method, path, host, date, body).unwrap();

        assert!(signature.contains("keyId="));
        assert!(signature.contains("algorithm=\"rsa-sha256\""));
        assert!(digest.starts_with("SHA-256="));
    }

    #[test]
    fn sign_get_no_digest() {
        let (private_pem, _public_pem) = keys::generate().unwrap();
        let key_id = "https://example.com/actor#main-key";
        let path = "/actor";
        let host = "example.com";
        let date = "Wed, 21 Oct 2026 07:28:00 GMT";

        let signature = sign_get(&private_pem, key_id, path, host, date).unwrap();

        assert!(signature.contains("keyId="));
        assert!(signature.contains("algorithm=\"rsa-sha256\""));
        assert!(signature.contains("headers=\"(request-target) host date\""));
        assert!(!signature.contains("digest"));
    }

    #[test]
    fn parse_signature_header_basic() {
        let header = r#"keyId="https://example.com/key",algorithm="rsa-sha256",headers="(request-target) host date",signature="abc123""#;
        let parts = parse_signature_header(header).unwrap();

        assert_eq!(parts.get("keyId").unwrap(), "https://example.com/key");
        assert_eq!(parts.get("algorithm").unwrap(), "rsa-sha256");
        assert_eq!(parts.get("headers").unwrap(), "(request-target) host date");
        assert_eq!(parts.get("signature").unwrap(), "abc123");
    }

    #[test]
    fn parse_http_date_rfc7231() {
        let date_str = "Wed, 21 Oct 2026 07:28:00 GMT";
        let parsed = parse_http_date(date_str).unwrap();
        assert_eq!(parsed.year(), 2026);
        assert_eq!(parsed.month(), 10);
        assert_eq!(parsed.day(), 21);
    }
}
