use super::{actor::ID, util, CLIENT, DOMAIN};
use anyhow::anyhow;
use axum::{
    extract::{Form, Query, RequestParts},
    response::{IntoResponse, Redirect, Response},
    Json,
};
use hyper::{header::ACCEPT, Body, Request, StatusCode};
use once_cell::sync::Lazy;
use serde::{Deserialize, Serialize};

static ACCT: Lazy<String> = Lazy::new(|| format!("blog@{}", *DOMAIN));

pub async fn handle(req: Request<Body>) -> Response {
    if is_resource_blog(req).await {
        Json(WebfingerResponse::new()).into_response()
    } else {
        // mastodon responds with 400 and an empty body, so we do too
        StatusCode::BAD_REQUEST.into_response()
    }
}

pub async fn handle_interact(
    Form(InteractParams { acct, permalink }): Form<InteractParams>,
) -> Response {
    if let Ok(response) = query(&acct).await {
        for link in response.links {
            if let Link {
                payload: LinkPayload::OStatusSubscribe { template },
                ..
            } = link
            {
                let url = template.replace("{uri}", &format!("https://{}{}", &*DOMAIN, permalink));
                return Redirect::to(&url).into_response();
            }
        }
    }

    (
        StatusCode::BAD_REQUEST,
        "Unable to find remote subscribe URL",
    )
        .into_response()
}

async fn is_resource_blog(req: Request<Body>) -> bool {
    let mut parts = RequestParts::new(req);
    if let Ok(Query(params)) = parts.extract::<Query<Params>>().await {
        let resource = if params.resource.starts_with("acct:") {
            &params.resource[5..]
        } else {
            &params.resource
        };
        resource == ACCT.as_str()
    } else {
        false
    }
}

async fn query(acct: &str) -> anyhow::Result<WebfingerResponse> {
    let (_, domain) = util::parse_acct(acct).ok_or(anyhow!("invalid acct"))?;
    let response = CLIENT
        .get(format!(
            "https://{}/.well-known/webfinger?resource={}",
            domain, acct
        ))
        .header(ACCEPT, "application/json")
        .send()
        .await?;
    Ok(response.json().await?)
}

#[derive(Deserialize)]
pub struct InteractParams {
    permalink: String,
    #[serde(rename = "remote_follow[acct]")]
    acct: String,
}

#[derive(Deserialize)]
pub struct Params {
    resource: String,
}

#[derive(Debug, PartialEq, Serialize, Deserialize)]
pub struct WebfingerResponse {
    subject: String,
    links: Vec<Link>,
}

impl WebfingerResponse {
    fn new() -> Self {
        Self {
            subject: ACCT.to_owned(),
            links: vec![Link {
                rel: "self".to_owned(),
                payload: LinkPayload::Webfinger {
                    r#type: "application/activity+json".to_owned(),
                    href: ID.to_string(),
                },
            }],
        }
    }
}

#[derive(Debug, PartialEq, Serialize, Deserialize)]
pub struct Link {
    rel: String,
    #[serde(flatten)]
    payload: LinkPayload,
}

#[derive(Debug, PartialEq, Serialize, Deserialize)]
#[serde(untagged)]
pub enum LinkPayload {
    Webfinger { r#type: String, href: String },
    OStatusSubscribe { template: String },
}

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

    #[test]
    fn test_deserialize_link_payloads() {
        let json = r#"
{
  "subject": "acct:Gargron@mastodon.social",
  "aliases": [
    "https://mastodon.social/@Gargron",
    "https://mastodon.social/users/Gargron"
  ],
  "links": [
    {
      "rel": "http://webfinger.net/rel/profile-page",
      "type": "text/html",
      "href": "https://mastodon.social/@Gargron"
    },
    {
      "rel": "self",
      "type": "application/activity+json",
      "href": "https://mastodon.social/users/Gargron"
    },
    {
      "rel": "http://ostatus.org/schema/1.0/subscribe",
      "template": "https://mastodon.social/authorize_interaction?uri={uri}"
    }
  ]
}
"#;
        let response: WebfingerResponse = serde_json::from_str(&json).unwrap();
        assert_eq!(
            response,
            WebfingerResponse {
                subject: "acct:Gargron@mastodon.social".to_owned(),
                links: vec![
                    Link {
                        rel: "http://webfinger.net/rel/profile-page".to_owned(),
                        payload: LinkPayload::Webfinger {
                            r#type: "text/html".to_owned(),
                            href: "https://mastodon.social/@Gargron".to_owned()
                        }
                    },
                    Link {
                        rel: "self".to_owned(),
                        payload: LinkPayload::Webfinger {
                            r#type: "application/activity+json".to_owned(),
                            href: "https://mastodon.social/users/Gargron".to_owned()
                        }
                    },
                    Link {
                        rel: "http://ostatus.org/schema/1.0/subscribe".to_owned(),
                        payload: LinkPayload::OStatusSubscribe {
                            template: "https://mastodon.social/authorize_interaction?uri={uri}"
                                .to_owned()
                        }
                    },
                ]
            }
        );
    }
}
