mod actor;
pub mod articles;
mod comments;
mod db;
pub mod digester;
pub mod federate;
mod inbox;
pub mod keys;
mod types;
mod util;
mod webfinger;

use self::types::ActorExt;
use activitystreams::{base::AnyBase, prelude::BaseExt};
use anyhow::anyhow;
use axum::{
    routing::{get, post, Router},
    Extension,
};
use log::warn;
use once_cell::sync::Lazy;
use reqwest::{Client, ClientBuilder};
use sqlx::SqlitePool;
use uuid::Uuid;

pub static DOMAIN: Lazy<String> =
    Lazy::new(|| std::env::var("DOMAIN").unwrap_or("shadowfacts.net".to_owned()));

pub fn router(pool: SqlitePool) -> Router {
    Router::new()
        .route("/.well-known/webfinger", get(webfinger::handle))
        .route("/ap/actor", get(actor::handle))
        .route("/ap/actor/followers", get(actor::handle_followers))
        .layer(Extension(pool))
        .route("/ap/inbox", post(inbox::handle))
        .route("/comments", get(comments::handle))
        .route("/interact", post(webfinger::handle_interact))
}

pub fn gen_ap_id() -> String {
    format!("https://{}/ap/{}", *DOMAIN, Uuid::new_v4())
}

pub fn gen_converation_id() -> String {
    format!("https://{}/ap/conversation/{}", *DOMAIN, Uuid::new_v4())
}

pub fn conversation_context() -> AnyBase {
    AnyBase::from_arbitrary_json(serde_json::json!({
        "ostatus": "http://ostatus.org#",
        "conversation": "ostatus:conversation"
    }))
    .unwrap()
}

const ACCEPT_INVALID_CERTS: bool = cfg!(debug_assertions);
pub static CLIENT: Lazy<Client> = Lazy::new(|| {
    if ACCEPT_INVALID_CERTS {
        warn!("Accepting invalid certs for ActivityPub request");
    }

    ClientBuilder::new()
        .danger_accept_invalid_certs(ACCEPT_INVALID_CERTS)
        .build()
        .expect("couldn't build reqwest client")
});

pub async fn get_actor(id: &str, pool: &SqlitePool) -> anyhow::Result<ActorExt> {
    if let Some(actor) = db::fetch_cached_actor(id, pool).await {
        let actor = serde_json::from_str::<ActorExt>(&actor.actor_object)?;
        Ok(actor)
    } else {
        fetch_actor(id, pool).await
    }
}

pub async fn fetch_actor(id: &str, pool: &SqlitePool) -> anyhow::Result<ActorExt> {
    let actor = federate::fetch_actor(id).await?;
    if actor.id_unchecked().unwrap().as_str() != id {
        return Err(anyhow!("got mismatched id for actor at {}", id));
    }
    db::add_cached_actor(&actor, pool).await?;
    Ok(actor)
}
