// SPDX-FileCopyrightText: 2026 Ololduck <ololduck@vit.am> and contributors
//
// SPDX-License-Identifier: GPL-3.0-or-later

//! Integration tests for note editing.

mod support;

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

/// The password the tests register and log in with.
const PASSWORD: &str = "correct horse battery staple";

/// Registers `name` through the form, which logs them in.
fn register(client: &mut Client, name: &str) {
    let email = format!("{name}@example.org");
    let response = client.post_form(
        "/register",
        &[("name", name), ("email", &email), ("password", PASSWORD)],
    );
    assert_eq!(
        response.status, 303,
        "registering should log in\n{}",
        response.body
    );
}

/// Test: Author can access the edit form for their own note.
#[tokio::test]
async fn author_can_access_edit_form() -> 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 client = Client::new(port);
    register(&mut client, "alice");

    // Create a note
    let response = client.post_multipart(
        "/~alice/notes",
        &[("content", "Original note content")],
        &[],
    );
    assert_eq!(response.status, 303, "creating note should redirect");

    // Extract the note UUID from the redirect location or profile page
    let profile = client.get("/~alice");
    assert!(
        profile.body.contains("Original note content"),
        "note should appear on profile"
    );

    // Find the note UUID from the profile page
    let note_uuid = extract_note_uuid(&profile.body);
    assert!(note_uuid.is_some(), "should find note UUID on profile");

    // Access the edit form
    let edit_page = client.get(&format!("/~alice/notes/{}/edit", note_uuid.unwrap()));
    assert_eq!(
        edit_page.status, 200,
        "author should access edit form\n{}",
        edit_page.body
    );
    assert!(
        edit_page.body.contains("Edit Note"),
        "edit form should have title"
    );
    assert!(
        edit_page.body.contains("Original note content"),
        "edit form should pre-fill content"
    );

    Ok(())
}

/// Test: Non-author cannot access the edit form.
#[tokio::test]
async fn non_author_cannot_access_edit_form() -> miette::Result<()> {
    let port = free_port();
    let mut server = Server::spawn_federated(&format!("127.0.0.1:{port}"));
    server.wait_for_connection(port);

    // Alice creates a note
    let mut alice_client = Client::new(port);
    register(&mut alice_client, "alice");
    let response =
        alice_client.post_multipart("/~alice/notes", &[("content", "Alice's note")], &[]);
    assert_eq!(response.status, 303);

    let profile = alice_client.get("/~alice");
    let note_uuid = extract_note_uuid(&profile.body).unwrap();

    // Bob tries to edit Alice's note
    let mut bob_client = Client::new(port);
    register(&mut bob_client, "bob");
    let edit_page = bob_client.get(&format!("/~alice/notes/{}/edit", note_uuid));
    assert_eq!(
        edit_page.status, 403,
        "non-author should get 403\n{}",
        edit_page.body
    );

    Ok(())
}

/// Test: Anonymous user cannot access the edit form.
#[tokio::test]
async fn anonymous_cannot_access_edit_form() -> miette::Result<()> {
    let port = free_port();
    let mut server = Server::spawn_federated(&format!("127.0.0.1:{port}"));
    server.wait_for_connection(port);

    // Alice creates a note
    let mut alice_client = Client::new(port);
    register(&mut alice_client, "alice");
    alice_client.post_multipart("/~alice/notes", &[("content", "Alice's note")], &[]);

    let profile = alice_client.get("/~alice");
    let note_uuid = extract_note_uuid(&profile.body).unwrap();

    // Anonymous user tries to edit
    let mut anon_client = Client::new(port);
    let edit_page = anon_client.get(&format!("/~alice/notes/{}/edit", note_uuid));
    assert_eq!(
        edit_page.status, 401,
        "anonymous should get 401\n{}",
        edit_page.body
    );

    Ok(())
}

/// Test: Author can edit their note.
#[tokio::test]
async fn author_can_edit_note() -> 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 client = Client::new(port);
    register(&mut client, "alice");

    // Create a note
    client.post_multipart("/~alice/notes", &[("content", "Original content")], &[]);

    let profile = client.get("/~alice");
    let note_uuid = extract_note_uuid(&profile.body).unwrap();

    // Edit the note
    let response = client.post_multipart(
        &format!("/~alice/notes/{}/edit", note_uuid),
        &[("content", "Updated content")],
        &[],
    );
    assert_eq!(
        response.status, 303,
        "editing should redirect\n{}",
        response.body
    );

    // Verify the update
    let note_page = client.get(&format!("/~alice/notes/{}", note_uuid));
    assert_eq!(note_page.status, 200);
    assert!(
        note_page.body.contains("Updated content"),
        "note should show updated content\n{}",
        note_page.body
    );
    assert!(
        note_page.body.contains("(edited)"),
        "note should show edited indicator\n{}",
        note_page.body
    );

    Ok(())
}

/// Test: Non-author cannot edit a note.
#[tokio::test]
async fn non_author_cannot_edit_note() -> miette::Result<()> {
    let port = free_port();
    let mut server = Server::spawn_federated(&format!("127.0.0.1:{port}"));
    server.wait_for_connection(port);

    // Alice creates a note
    let mut alice_client = Client::new(port);
    register(&mut alice_client, "alice");
    alice_client.post_multipart("/~alice/notes", &[("content", "Alice's note")], &[]);

    let profile = alice_client.get("/~alice");
    let note_uuid = extract_note_uuid(&profile.body).unwrap();

    // Bob tries to edit
    let mut bob_client = Client::new(port);
    register(&mut bob_client, "bob");
    let response = bob_client.post_form(
        &format!("/~alice/notes/{}/edit", note_uuid),
        &[("content", "Bob's edit")],
    );
    assert_eq!(
        response.status, 403,
        "non-author should get 403\n{}",
        response.body
    );

    Ok(())
}

/// Test: Edit form shows edit button only to author.
#[tokio::test]
async fn edit_button_visible_to_author_only() -> miette::Result<()> {
    let port = free_port();
    let mut server = Server::spawn_federated(&format!("127.0.0.1:{port}"));
    server.wait_for_connection(port);

    // Alice creates a note
    let mut alice_client = Client::new(port);
    register(&mut alice_client, "alice");
    alice_client.post_multipart("/~alice/notes", &[("content", "Alice's note")], &[]);

    let profile = alice_client.get("/~alice");
    let note_uuid = extract_note_uuid(&profile.body).unwrap();

    // Alice sees edit button
    let note_page = alice_client.get(&format!("/~alice/notes/{}", note_uuid));
    assert!(
        note_page.body.contains("/edit"),
        "author should see edit link\n{}",
        note_page.body
    );

    // Bob doesn't see edit button
    let mut bob_client = Client::new(port);
    register(&mut bob_client, "bob");
    let bob_note_page = bob_client.get(&format!("/~alice/notes/{}", note_uuid));
    assert!(
        !bob_note_page.body.contains("Edit Note"),
        "non-author should not see edit button\n{}",
        bob_note_page.body
    );

    Ok(())
}

/// Helper to extract the first note UUID from a profile page.
fn extract_note_uuid(html: &str) -> Option<String> {
    // Look for note links in the format href="/~username/notes/{uuid}"
    let pattern = "href=\"/~";
    if let Some(start) = html.find(pattern) {
        let after_href = &html[start + pattern.len()..];
        // Find /notes/ in the remaining string
        if let Some(notes_pos) = after_href.find("/notes/") {
            let uuid_start = notes_pos + "/notes/".len();
            let uuid_part = &after_href[uuid_start..];
            // UUID is 36 characters, extract it
            if uuid_part.len() >= 36 {
                let uuid = &uuid_part[..36];
                // Verify it looks like a UUID (contains hyphens)
                if uuid.contains('-') {
                    return Some(uuid.to_string());
                }
            }
        }
    }
    None
}
