//! Phase 1 tests: Remote actor discovery via webfinger (now integrated with follow)

mod support;

use support::ap::RemoteActor;
use support::{Client, Server, free_port};

/// Register a user through the web form.
fn register(client: &mut Client, name: &str) {
    let email = format!("{name}@example.org");
    let response = client.post_form(
        "/register",
        &[
            ("name", name),
            ("email", &email),
            ("password", "correct horse battery staple"),
        ],
    );
    assert_eq!(
        response.status, 303,
        "registering should log in\n{}",
        response.body
    );
}

/// Test: Follow a remote actor by handle (includes discovery)
#[tokio::test]
async fn discover_by_handle_succeeds() -> miette::Result<()> {
    let port = free_port();
    let mut server = Server::spawn_federated(&format!("127.0.0.1:{port}"));
    server.wait_for_connection(port);

    let mut alice_client = Client::new(port);
    register(&mut alice_client, "alice");

    // Start a remote actor that serves webfinger
    let recorder_port = free_port();
    let _recorder = RemoteActor::serve(recorder_port);

    // Alice follows bob@127.0.0.1:recorder_port (discovery happens automatically)
    let handle = format!("remote@127.0.0.1:{recorder_port}");

    // POST to follow endpoint with handle (discovery + follow in one step)
    let response = alice_client.post_form("/~alice/follow", &[("handle", &handle)]);

    // Should redirect to the profile page after successful follow
    assert_eq!(
        response.status, 303,
        "follow with handle should redirect\n{}",
        response.body
    );

    // Verify the redirect includes a success notice
    assert!(
        response.location.as_ref().unwrap().contains("notice="),
        "redirect should include notice parameter"
    );

    Ok(())
}

/// Test: Follow with unknown handle returns error
#[tokio::test]
async fn discover_unknown_handle_errors() -> miette::Result<()> {
    let port = free_port();
    let mut server = Server::spawn_federated(&format!("127.0.0.1:{port}"));
    server.wait_for_connection(port);

    let mut alice_client = Client::new(port);
    register(&mut alice_client, "alice");

    // Try to follow a non-existent user
    let response =
        alice_client.post_form("/~alice/follow", &[("handle", "nonexistent@example.com")]);

    // Should redirect with an error parameter
    assert_eq!(
        response.status, 303,
        "follow should redirect\n{}",
        response.body
    );
    assert!(
        response.location.as_ref().unwrap().contains("error="),
        "redirect should include error parameter"
    );

    Ok(())
}

/// Test: Follow with bare actor URL skips webfinger
#[tokio::test]
async fn discover_by_url_skips_webfinger() -> miette::Result<()> {
    let port = free_port();
    let mut server = Server::spawn_federated(&format!("127.0.0.1:{port}"));
    server.wait_for_connection(port);

    let mut alice_client = Client::new(port);
    register(&mut alice_client, "alice");

    // Start a remote actor
    let recorder_port = free_port();
    let recorder = RemoteActor::serve(recorder_port);

    // Follow using the bare actor URL (skips webfinger)
    let response = alice_client.post_form("/~alice/follow", &[("actor_url", &recorder.actor_url)]);

    assert_eq!(
        response.status, 303,
        "follow with actor_url should redirect\n{}",
        response.body
    );

    Ok(())
}

/// Test: Discovery fails when JRD has no ActivityPub self link
#[tokio::test]
async fn discover_without_activitypub_link_errors() -> miette::Result<()> {
    let port = free_port();
    let mut server = Server::spawn_federated(&format!("127.0.0.1:{port}"));
    server.wait_for_connection(port);

    let mut alice_client = Client::new(port);
    register(&mut alice_client, "alice");

    // Try to follow a handle that doesn't exist (no webfinger response)
    let response = alice_client.post_form(
        "/~alice/follow",
        &[("handle", "@nonexistent@invalid.example")],
    );

    assert_eq!(
        response.status, 303,
        "follow should redirect with error\n{}",
        response.body
    );
    assert!(
        response.location.as_ref().unwrap().contains("error="),
        "redirect should include error parameter, got: {:?}",
        response.location
    );

    Ok(())
}

/// Test: Discovery fails gracefully for malformed handle
#[tokio::test]
async fn discover_malformed_handle_errors() -> miette::Result<()> {
    let port = free_port();
    let mut server = Server::spawn_federated(&format!("127.0.0.1:{port}"));
    server.wait_for_connection(port);

    let mut alice_client = Client::new(port);
    register(&mut alice_client, "alice");

    // Try to follow with malformed handle (missing @)
    let response = alice_client.post_form("/~alice/follow", &[("handle", "invalidhandle")]);

    assert_eq!(
        response.status, 303,
        "follow should redirect with error\n{}",
        response.body
    );
    assert!(
        response.location.as_ref().unwrap().contains("error="),
        "redirect should include error parameter for malformed handle, got: {:?}",
        response.location
    );

    Ok(())
}

/// Test: Discovery with bare URL works without webfinger
#[tokio::test]
async fn discover_bare_url_skips_webfinger() -> miette::Result<()> {
    let port = free_port();
    let mut server = Server::spawn_federated(&format!("127.0.0.1:{port}"));
    server.wait_for_connection(port);

    let mut alice_client = Client::new(port);
    register(&mut alice_client, "alice");

    // Start a remote actor
    let recorder_port = free_port();
    let recorder = RemoteActor::serve(recorder_port);

    // Follow using bare actor URL (should skip webfinger)
    let response = alice_client.post_form("/~alice/follow", &[("handle", &recorder.actor_url)]);

    assert_eq!(
        response.status, 303,
        "follow with bare URL should redirect\n{}",
        response.body
    );
    assert!(
        response.location.as_ref().unwrap().contains("notice="),
        "redirect should include notice parameter for successful follow, got: {:?}",
        response.location
    );

    Ok(())
}
