use actix_web::{http::StatusCode, web, App, HttpRequest, HttpResponse, HttpServer, ResponseError};
use base64::{prelude::BASE64_STANDARD, Engine};
use http_signature_normalization_actix_extractor::{
    Algorithm, Config, ConfigGenerator, DeprecatedAlgorithm, Signed, VerifyKey,
};
use sha2::Sha256;

#[actix_web::main]
async fn main() -> std::io::Result<()> {
    HttpServer::new(|| App::new().route("/", web::post().to(protected)))
        .bind("127.0.0.1:8010")?
        .run()
        .await
}

async fn protected(signed_request: Signed<String, Cfg, Sha256, Key>) -> &'static str {
    let (value, signature) = signed_request.into_parts();

    println!("{}", value);
    println!("{:#?}", signature);

    "hewwo, mr obama"
}

pub struct Cfg;

#[derive(Debug)]
pub struct Key;

#[derive(Debug)]
pub enum VerifyError {
    Algorithm,
    Decode,
    Key,
}

impl std::fmt::Display for VerifyError {
    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
        match self {
            Self::Algorithm => f.write_str("Unsupported algorithm"),
            Self::Decode => f.write_str("Couldn't decode signature"),
            Self::Key => f.write_str("Invalid key"),
        }
    }
}

impl std::error::Error for VerifyError {}

impl ConfigGenerator for Cfg {
    fn config() -> Config {
        Config::new().require_header("accept")
    }
}

impl VerifyKey for Key {
    type Error = VerifyError;

    async fn init(
        _: &HttpRequest,
        key_id: &str,
        algorithm: Option<&Algorithm>,
    ) -> Result<Self, Self::Error> {
        match algorithm {
            Some(Algorithm::Hs2019 | Algorithm::Deprecated(DeprecatedAlgorithm::RsaSha256)) => (),
            _ => return Err(VerifyError::Algorithm),
        };

        if key_id != "my-key-id" {
            return Err(VerifyError::Key);
        }

        Ok(Key)
    }

    fn verify(&mut self, signature: &str, signing_string: &str) -> Result<bool, Self::Error> {
        use subtle::ConstantTimeEq;

        let decoded = BASE64_STANDARD
            .decode(signature)
            .map_err(|_| VerifyError::Decode)?;

        Ok(decoded.ct_eq(signing_string.as_bytes()).into())
    }
}

impl ResponseError for VerifyError {
    fn status_code(&self) -> StatusCode {
        StatusCode::BAD_REQUEST
    }

    fn error_response(&self) -> HttpResponse {
        HttpResponse::BadRequest().finish()
    }
}
