#![cfg(test)]
use crate::{algorithms::ptd::Type, test::Client, Error};

use super::{endpoint_for, process_incoming_webmention};
use miette::IntoDiagnostic;
use url::Url;

#[macro_export]
macro_rules! run_stock_webmention_test {
    ($slug: expr, $mock_page: expr, $resulting_url: expr) => {
        run_webmention_test_with_client!(
            $crate::test::Client::new().await,
            $slug,
            $mock_page,
            $resulting_url
        )
    };
}

#[macro_export]
macro_rules! run_webmention_test_with_client {
    ($client: expr, $slug: expr, $mock_page: expr, $resulting_url: expr) => {
        let mut client = $client;
        let lpath = format!("/page-{}", $slug);
        let resource_url = client.merge_into_url(&lpath);

        let mut resource_mock = client
            .mock_server
            .mock("GET", lpath.as_str())
            .with_status($mock_page.status().as_u16().into());

        for (name, value) in $mock_page.headers().into_iter() {
            resource_mock = resource_mock.with_header(name.as_str(), value.to_str().unwrap());
        }

        resource_mock
            .with_body($mock_page.into_body().as_bytes())
            .create();

        let result = endpoint_for(&client, &resource_url).await;

        let compared_url = if $resulting_url.starts_with("/") {
            Ok(client.merge_into_url(&$resulting_url))
        } else {
            Ok($resulting_url.parse::<url::Url>().unwrap())
        };

        assert_eq!(
            result.map(|u| u.to_string()),
            compared_url.map(|u| u.to_string()),
            "resolution found the URL {resource_url}",
        );
    };
}

#[tokio::test]
// Implements <https://webmention.rocks/test/1>
async fn http_link_header_unquoted_rel_relative_url() {
    run_stock_webmention_test!(
        "1",
        ::http::Response::builder()
            .status(::http::StatusCode::OK)
            .header(::http::header::LINK, "</test/1/webmention>; rel=webmention")
            .body(crate::http::Body::Empty)
            .unwrap_or_default(),
        "/test/1/webmention"
    );
}

#[tokio::test]
// Implments <https://webmention.rocks/test/2>
async fn http_link_header_unquoted_rel_absolute_url() {
    run_stock_webmention_test!(
        "2",
        ::http::Response::builder()
            .status(::http::StatusCode::OK)
            .header(::http::header::LINK, "</test/2/webmention>; rel=webmention",)
            .body(crate::http::Body::Empty)
            .unwrap_or_default(),
        "/test/2/webmention"
    );
}

#[tokio::test]
// Implements <https://webmention.rocks/test/3>
async fn http_link_tag_relative_url() {
    let whole_url = "/test/3/webmention";
    run_stock_webmention_test!(
        "3",
        ::http::Response::builder()
            .status(::http::StatusCode::OK)
            .header("content-type", "text/html")
            .body(crate::http::Body::from(format!(
                r#"
          <html>
            <head>
                <link rel="webmention" href="{}" />
            </head>
          </html>
            "#,
                whole_url
            )))
            .unwrap(),
        whole_url
    );
}

#[tokio::test]
// Implements <https://webmention.rocks/test/4>
async fn http_link_tag_absolute_url() {
    let client = Client::new().await;
    let whole_url = format!("{}/test/4/webmention", client.mock_server.url());
    run_webmention_test_with_client!(
        client,
        "4",
        ::http::Response::builder()
            .status(::http::StatusCode::OK)
            .body(crate::http::Body::from(format!(
                r#"
          <html>
            <head>
                <link rel="webmention" href="{}" />
            </head>
          </html>
            "#,
                whole_url
            )))
            .unwrap_or_default(),
        whole_url
    );
}

#[tokio::test]
// Implements <https://webmention.rocks/test/5>
async fn http_a_tag_relative_url() {
    let whole_url = "/test/5/webmention";
    run_stock_webmention_test!(
        "5",
        ::http::Response::builder()
            .status(::http::StatusCode::OK)
            .body(crate::http::Body::from(format!(
                r#"
          <html>
            <head>
                <link rel="webmention" href="{}" />
            </head>
          </html>
            "#,
                whole_url
            )))
            .unwrap_or_default(),
        whole_url
    );
}

#[tokio::test]
// Implements <https://webmention.rocks/test/6>
async fn http_a_tag_absolute_url() {
    let client = Client::new().await;
    let whole_url: Url = format!("{}/test/6/webmention", client.mock_server.url())
        .parse()
        .unwrap();
    run_webmention_test_with_client!(
        client,
        "6",
        ::http::Response::builder()
            .status(::http::StatusCode::OK)
            .body(crate::http::Body::from(format!(
                r#"
          <html>
            <body>
                <a rel="webmention" href="{}" />
            </body>
          </html>
            "#,
                whole_url
            )))
            .unwrap_or_default(),
        whole_url.as_str()
    );
}

#[tokio::test]
// Implements <https://webmention.rocks/test/7>
async fn http_link_header_strange_casing() {
    let client = Client::new().await;
    let whole_url: Url = format!("{}/test/7/webmention", client.mock_server.url())
        .parse()
        .unwrap();
    let resp = ::http::Response::builder()
        .header("Link", format!("<{}>; rel=webmention", whole_url).as_str())
        .body(crate::http::Body::Empty)
        .unwrap_or_default();
    run_webmention_test_with_client!(client, "7", resp, whole_url.as_str());
}

// Implements <https://webmention.rocks/test/8>
#[tracing_test::traced_test]
#[tokio::test]
async fn http_link_header_quoted_rel() {
    let client = Client::new().await;
    let whole_url: Url = format!("{}/test/8/webmention", client.mock_server.url())
        .parse()
        .unwrap();
    let resp = ::http::Response::builder()
        .header(
            "Link",
            format!("<{}>; rel=\"webmention\"", whole_url).as_str(),
        )
        .body(crate::http::Body::Empty)
        .unwrap_or_default();
    run_webmention_test_with_client!(client, "8", resp, whole_url.as_str());
}

// Implements <https://webmention.rocks/test/9>
#[tracing_test::traced_test]
#[tokio::test]
async fn http_multiple_rel_values_on_link_tag() {
    let client = Client::new().await;
    let whole_url = client.merge_into_url("/test/9/webmention");
    let resp = ::http::Response::builder()
        .body(crate::http::Body::Bytes(
            format!(
                r#"
          <html>
            <head>
                <link rel="webmention something-else" href="{}" />
            </head>
          </html>
            "#,
                whole_url
            )
            .into(),
        ))
        .unwrap_or_default();
    run_webmention_test_with_client!(client, "9", resp, whole_url.path());
}

// Implements <https://webmention.rocks/test/10>
#[tracing_test::traced_test]
#[tokio::test]
async fn http_rel_values_on_rel_headers() {
    let client = Client::new().await;
    let whole_url: Url = format!("{}/test/10/webmention", client.mock_server.url())
        .parse()
        .unwrap();
    let resp = ::http::Response::builder()
        .header(
            "LinK",
            format!("<{}>; rel=\"webmention somethingelse\"", whole_url).as_str(),
        )
        .body(crate::http::Body::Empty)
        .unwrap_or_default();
    run_webmention_test_with_client!(client, "10", resp, whole_url.path());
}

#[tokio::test]
// Implements <https://webmention.rocks/test/11>
async fn http_rel_multiple_webmention_endpoints_advertised() {
    let client = Client::new().await;
    let expected_url = format!("{}/test/11/webmention/header", client.mock_server.url());
    let resp = ::http::Response::builder()
        .header(
            "LinK",
            format!(
                "<{}/test/11/webmention/header>; rel=\"webmention somethingelse\"",
                client.mock_server.url()
            )
            .as_str(),
        )
        .body(crate::http::Body::from(format!(
            r#"
            <html>
                <head>
                    <link rel="webmention" href="{0}/test/11/webmention/link-tag" />
                </head>
                <body>
                    <a rel="webmention" href="{0}/test/11/webmention/a-tag" />
                </body>
            </html>
                "#,
            client.mock_server.url()
        )))
        .unwrap_or_default();
    run_webmention_test_with_client!(
        client,
        "11",
        resp,
        expected_url
    );
}

#[tokio::test]
// Implements <https://webmention.rocks/test/12>
async fn http_rel_checking_for_exact_match_of_rel_webmention() {
    let client = Client::new().await;
    let expected_url = format!("{}/test/12/webmention/right", client.mock_server.url());
    let resp = ::http::Response::builder()
        .body(crate::http::Body::from(format!(
            r#"
            <html>
                <head>
                    <link rel="webmention" href="{0}/test/12/webmention/right" />
                    <link rel="not-webmention" href="{0}/test/12/webmention/wrong" />
                </head>
            </html>
                "#,
            client.mock_server.url()
        )))
        .unwrap_or_default();
    run_webmention_test_with_client!(
        client,
        "12",
        resp,
        expected_url
    );
}

#[tokio::test]
// Implements <https://webmention.rocks/test/13>
async fn http_rel_false_endpoint_inside_html_element() {
    let client = Client::new().await;
    let expected_url = format!("{}/test/13/webmention/right", client.mock_server.url());
    let resp = ::http::Response::builder()
        .body(crate::http::Body::from(format!(
            r#"
            <html>
                <body>
                <!--
                    <a rel="webmention" href="{0}/test/13/webmention/wrong" />
                -->

                    <a rel="webmention" href="{0}/test/13/webmention/right" />
                </body>
            </html>
                "#,
            client.mock_server.url()
        )))
        .unwrap_or_default();
    run_webmention_test_with_client!(
        client,
        "13",
        resp,
        expected_url
    );
}

#[tokio::test]
// Implements <https://webmention.rocks/test/14>
async fn http_rel_false_endpoint_inside_escaped_html() {
    let client = Client::new().await;
    let expected_url = format!("{}/test/14/webmention/right", client.mock_server.url());
    let resp = ::http::Response::builder()
    .body(crate::http::Body::from(
        r#"
            <html>
                <body>
                    This post contains sample code with escaped HTML which should not
                    be discovered by the Webmention client.
                    <code>&lt;a href="/test/14/webmention/error" rel="webmention"&gt;&lt;/a&gt;</code>
                    There is also a <a href="/test/14/webmention/right" rel="webmention">correct endpoint</a>
                    defined, so if your comment appears below, it means you successfully ignored the false endpoint.
                </body>
            </html>
                "#
    .to_string()))
    .unwrap_or_default();
    run_webmention_test_with_client!(
        client,
        "14",
        resp,
        expected_url
    );
}

#[tokio::test]
// Implements <https://webmention.rocks/test/15>
async fn http_rel_webmention_href_empty_string() {
    let client = Client::new().await;
    let expected_url = format!("{}/page-15", client.mock_server.url());
    let resp = ::http::Response::builder()
        .body(crate::http::Body::from(
            r#"
            <html>
                <head>
                    <link rel="webmention" href="" />
                </head>
            </html>

                "#
            .to_string(),
        ))
        .unwrap_or_default();
    run_webmention_test_with_client!(
        client,
        "15",
        resp,
        expected_url
    );
}

// Implements <https://webmention.rocks/test/16>
#[tracing_test::traced_test]
#[tokio::test]
async fn http_rel_multiple_webmention_endpoints_advertised_link_a() {
    let resp = ::http::Response::builder()
    .body(crate::http::Body::from(
        r#"
            <html>
                <body>
                    This post advertises its Webmention endpoint in an HTML
                    <a href="https://webmention.rocks/test/16/webmention"
                    rel="webmention">&lt;a&gt; tag</a>, followed by a later
                    definition in a &lt;link&gt; tag. Your Webmention client
                    must only send a Webmention to the one in the &lt;a&gt;
                    tag since it appears first in the document.
                    <link rel="webmention" href="https://webmention.rocks/test/16/webmention/error" />
                </body>
            </html>

                "#
        .to_string(),
    ))
    .expect("yeup");
    run_stock_webmention_test!("16", resp, "https://webmention.rocks/test/16/webmention");
}

#[tokio::test]
// Implements <https://webmention.rocks/test/17>
async fn http_rel_multiple_webmention_endpoints_advertised_a_link() {
    let client = Client::new().await;
    let expected_url = format!("{}/test/17/webmention", client.mock_server.url());
    let resp = ::http::Response::builder()
        .body(crate::http::Body::from(
            r#"
            <html>
                <body>
                    This post advertises its Webmention endpoint in an
                    HTML &lt;link&gt; tag <link rel="webmention" href="/test/17/webmention">
                    followed by a later definition in an <a href="/test/17/webmention/error"
                    rel="webmention">&lt;a&gt; tag</a>. Your Webmention client must only send
                    a Webmention to the one in the &lt;link&gt; tag since it appears first in
                    the document
                </body>
            </html>

                "#
            .to_string(),
        ))
        .unwrap_or_default();
    run_webmention_test_with_client!(
        client,
        "17",
        resp,
        expected_url
    );
}

#[tokio::test]
// Implements <https://webmention.rocks/test/18>
async fn http_rel_multiple_link_http_headers() {
    let client = Client::new().await;
    let expected_url = format!("{}/test/18/webmention", client.mock_server.url());
    let resp = ::http::Response::builder()
        .header(
            "LinK",
            format!(
                r#"<{0}/test/18/webmention>; rel="webmention""#,
                client.mock_server.url()
            )
            .as_str(),
        )
        .header(
            ::http::header::LINK,
            format!(
                r#"<{0}/test/18/webmention/error>; rel="other""#,
                client.mock_server.url()
            )
            .as_str(),
        )
        .body(crate::http::Body::Empty)
        .unwrap_or_default();
    run_webmention_test_with_client!(
        client,
        "18",
        resp,
        expected_url
    );
}

#[tokio::test]
// Implements <https://webmention.rocks/test/19>
async fn http_rel_single_link_header_multiple_values() {
    let client = Client::new().await;
    let expected_url = format!("{}/test/19/webmention", client.mock_server.url());
    let resp = ::http::Response::builder()
        .header(
            "LinK",
            format!(
                r#"<{0}/test/19/webmention/error>; rel="other", <{0}/test/19/webmention>; rel="webmention""#,
                client.mock_server.url()
            )
            .as_str(),
        )
    .body(crate::http::Body::Empty)
    .unwrap_or_default();
    run_webmention_test_with_client!(
        client,
        "18",
        resp,
        expected_url
    );
}

#[tracing_test::traced_test]
#[tokio::test]
// Implements <https://webmention.rocks/test/20>
async fn link_tag_with_no_href_attribute() {
    let client = Client::new().await;
    let expected_url = format!("{}/test/20/webmention", client.mock_server.url());
    let resp = ::http::Response::builder()
        .body(crate::http::Body::from(
            r#"
            <html>
                <body>
                    This post has a &lt;link&gt; tag <link rel="webmention"> which
                    has no href attribute. Your Webmention client should not find
                    this link tag, and should send the webmention to
                    <a href="/test/20/webmention" rel="webmention">this endpoint</a>
                    instead.
                </body>
            </html>
                "#
            .to_string(),
        ))
        .unwrap_or_default();
    run_webmention_test_with_client!(
        client,
        "20",
        resp,
        expected_url
    );
}

#[tokio::test]
// Implements <https://webmention.rocks/test/21>
async fn webmention_endpoint_with_query_params() {
    let client = Client::new().await;
    let expected_url = format!("{}/test/21/webmention?query=yes", client.mock_server.url());
    let resp = ::http::Response::builder()
        .body(crate::http::Body::from(
            r#"
            <html>
                <link rel="webmention" href="/test/21/webmention?query=yes">
            </html>
                "#
            .to_string(),
        ))
        .unwrap_or_default();
    run_webmention_test_with_client!(
        client,
        "21",
        resp,
        expected_url
    );
}

#[tokio::test]
// Implements <https://webmention.rocks/test/22>
async fn webmention_endpoint_relative_to_path() {
    let client = Client::new().await;
    let expected_url = format!("{}/22/webmention", client.mock_server.url());
    let resp = ::http::Response::builder()
        .body(crate::http::Body::from(
            r#"
            <html>
                <link rel="webmention" href="22/webmention">
            </html>
                "#
            .to_string(),
        ))
        .unwrap_or_default();
    run_webmention_test_with_client!(
        client,
        "22",
        resp,
        expected_url
    );
}

#[tokio::test]
async fn incoming_ok() -> miette::Result<()> {
    let mut test_client = Client::new().await;

    let target_url = format!("{}/target", test_client.mock_server.url())
        .parse()
        .into_diagnostic()?;
    let source_url = format!("{}/source", test_client.mock_server.url())
        .parse()
        .into_diagnostic()?;
    let src_mock = test_client
        .mock_server
        .mock("GET", "/source")
        .with_header("Content-Type", "text/html")
        .with_body(format!(
            r#"
 <html>
     <body class="h-entry">
         <a href="" class="u-url"></a>
         I mentioned <a class="u-mention" href="{target_url}">your page</a>.
     </body>
 </html>
             "#
        ))
        .with_status(200)
        .create();

    let relation = process_incoming_webmention(
        &test_client,
        &crate::standards::webmention::Request {
            source: source_url,
            target: target_url,
            private: None,
            vouch: Default::default(),
            token: None,
        },
    )
    .await;
    src_mock.assert();

    assert!(relation.is_ok());

    let relation = relation?;

    assert_eq!(relation.r#type, Type::Note);
    assert!(
        relation.source.is_some(),
        "resolves a MF2 item from the incoming source"
    );

    Ok(())
}

#[tokio::test]
async fn incoming_deleted() -> miette::Result<()> {
    let mut test_client = Client::new().await;

    let target_url = format!("{}/target", test_client.mock_server.url())
        .parse()
        .into_diagnostic()?;
    let source_url = format!("{}/source", test_client.mock_server.url())
        .parse()
        .into_diagnostic()?;
    let src_mock = test_client
        .mock_server
        .mock("GET", "/source")
        .with_status(401)
        .with_header("WWW-Authenticate", "fome sit")
        .create();

    let relation = process_incoming_webmention(
        &test_client,
        &crate::standards::webmention::Request {
            source: source_url,
            target: target_url,
            private: None,
            vouch: Default::default(),
            token: None,
        },
    )
    .await;
    src_mock.assert();

    assert!(relation.is_err());

    Ok(())
}

#[tokio::test]
async fn incoming_need_auth() -> miette::Result<()> {
    let mut test_client = Client::new().await;

    let target_url: Url = format!("{}/target", test_client.mock_server.url())
        .parse()
        .into_diagnostic()?;
    let source_url: Url = format!("{}/source", test_client.mock_server.url())
        .parse()
        .into_diagnostic()?;
    let src_mock = test_client
        .mock_server
        .mock("GET", "/source")
        .with_status(401)
        .create();

    let relation = process_incoming_webmention(
        &test_client,
        &crate::standards::webmention::Request {
            source: source_url.clone(),
            target: target_url.clone(),
            private: None,
            vouch: Default::default(),
            token: None,
        },
    )
    .await;
    src_mock.assert();

    assert_eq!(
        relation,
        Err(Error::WebmentionUnauthorized { url: source_url })
    );

    Ok(())
}

#[tokio::test]
async fn incoming_invalid_creds() -> miette::Result<()> {
    let mut test_client = Client::new().await;

    let target_url: url::Url = format!("{}/target", test_client.mock_server.url())
        .parse()
        .into_diagnostic()?;
    let source_url: url::Url = format!("{}/source", test_client.mock_server.url())
        .parse()
        .into_diagnostic()?;
    let src_mock = test_client
        .mock_server
        .mock("GET", "/source")
        .with_status(401)
        .create();

    let relation = process_incoming_webmention(
        &test_client,
        &crate::standards::webmention::Request {
            source: source_url.clone(),
            target: target_url.clone(),
            private: None,
            vouch: Default::default(),
            token: None,
        },
    )
    .await;
    src_mock.assert();

    assert_eq!(
        relation,
        Err(Error::WebmentionUnauthorized { url: source_url })
    );

    Ok(())
}

#[tokio::test]
async fn private_incoming_process() -> miette::Result<()> {
    let mut test_client = Client::new().await;

    let target_url: url::Url = format!("{}/target", test_client.mock_server.url())
        .parse()
        .into_diagnostic()?;
    let source_url: url::Url = format!("{}/source", test_client.mock_server.url())
        .parse()
        .into_diagnostic()?;
    let authenticated_source_mock = test_client
        .mock_server
        .mock("GET", source_url.path())
        .match_header("Authorization", "Bearer the-expected-token")
        .with_status(200)
        .with_header("Content-Type", "text/html")
        .with_body(format!(
            r#"
 <html>
     <body class="h-entry">
         <a href="{source_url}" class="u-url"></a>
         I mentioned <a class="u-mention" href="{target_url}">your page</a>.
     </body>
 </html>
             "#
        ))
        .create_async()
        .await;

    let relation = process_incoming_webmention(
        &test_client,
        &crate::standards::webmention::Request {
            source: source_url.clone(),
            target: target_url.clone(),
            private: Some(crate::standards::webmention::PrivateRequest {
                code: "the-expected-code".to_string(),
                realm: None,
            }),
            vouch: Default::default(),
            token: Some("the-expected-token".to_string()),
        },
    )
    .await;
    authenticated_source_mock.assert();

    assert_eq!(relation.err(), None);

    Ok(())
}

#[tokio::test]
async fn incoming_not_found() -> miette::Result<()> {
    let mut test_client = Client::new().await;

    let target_url = format!("{}/target", test_client.mock_server.url())
        .parse()
        .into_diagnostic()?;
    let source_url = format!("{}/source", test_client.mock_server.url())
        .parse()
        .into_diagnostic()?;
    let src_mock = test_client
        .mock_server
        .mock("GET", "/source")
        .with_status(404)
        .create();

    let relation = process_incoming_webmention(
        &test_client,
        &crate::standards::webmention::Request {
            source: source_url,
            target: target_url,
            private: None,
            vouch: Default::default(),
            token: None,
        },
    )
    .await;
    src_mock.assert();

    assert!(relation.is_err());
    assert!(matches!(relation.err().unwrap(), crate::Error::WebmentionNotFound { .. }));

    Ok(())
}
