use super::*;
use std::{
    io::{Read, Write},
    net::TcpListener,
    thread,
};

fn commit(sha: &str, parents: &[&str]) -> models::Commit {
    models::Commit {
        sha: Some(sha.into()),
        parents: Some(
            parents
                .iter()
                .map(|sha| models::CommitMeta {
                    sha: Some((*sha).into()),
                    ..Default::default()
                })
                .collect(),
        ),
        ..Default::default()
    }
}

#[test]
fn lays_out_shared_commit_graph() {
    let rows = build_graph(
        vec![
            (
                0,
                "main".into(),
                vec![
                    commit("merge", &["left", "right"]),
                    commit("left", &["base"]),
                    commit("base", &[]),
                ],
            ),
            (
                1,
                "feature".into(),
                vec![commit("right", &["base"]), commit("base", &[])],
            ),
        ],
        PullMetadata::default(),
    );
    assert_eq!(rows.len(), 4);
    assert!(rows.iter().any(|row| row.refs == ["main"]));
    assert!(rows.iter().any(|row| row.refs == ["feature"]));
    let merge = rows
        .iter()
        .find(|row| row.commit.sha.as_deref() == Some("merge"))
        .unwrap();
    assert_eq!(merge.node_lane, Some(0));
    assert_eq!(merge.bottom_connections, [1]);
    let right = rows
        .iter()
        .find(|row| row.commit.sha.as_deref() == Some("right"))
        .unwrap();
    assert_eq!(right.node_lane, Some(1));
    let base = rows
        .iter()
        .find(|row| row.commit.sha.as_deref() == Some("base"))
        .unwrap();
    assert_eq!(base.node_lane, Some(0));
    assert_eq!(base.top_connections, [1]);
}

#[test]
fn labels_first_commit_of_pull_branch() {
    let rows = build_graph(
        vec![(
            0,
            "main".into(),
            vec![
                commit("merge", &["main", "head"]),
                commit("head", &["first"]),
                commit("first", &["base"]),
                commit("main", &["base"]),
                commit("base", &[]),
            ],
        )],
        PullMetadata {
            tips: HashMap::from([("head".into(), vec!["feature".into()])]),
            starts: vec![PullBranch {
                head: "head".into(),
                base: "base".into(),
                label: "feature".into(),
            }],
        },
    );
    let first = rows
        .iter()
        .find(|row| row.commit.sha.as_deref() == Some("first"))
        .unwrap();
    assert_eq!(first.branch_starts, ["feature"]);
    assert!(
        rows.iter()
            .find(|row| row.commit.sha.as_deref() == Some("head"))
            .unwrap()
            .refs
            .contains(&"feature".into())
    );
}

#[tokio::test]
async fn reads_nested_directory_from_compatible_contents_endpoint() {
    let listener = TcpListener::bind("127.0.0.1:0").unwrap();
    let address = listener.local_addr().unwrap();
    let server = thread::spawn(move || {
        let (mut stream, _) = listener.accept().unwrap();
        let mut buffer = [0; 4096];
        let length = stream.read(&mut buffer).unwrap();
        let request = String::from_utf8_lossy(&buffer[..length]).into_owned();
        let body = r#"[{"name":"guide.md","path":"docs/private guide/guide.md","type":"file"}]"#;
        write!(
            stream,
            "HTTP/1.1 200 OK\r\nContent-Type: application/json\r\nContent-Length: {}\r\nConnection: close\r\n\r\n{body}",
            body.len()
        )
        .unwrap();
        request
    });

    let client =
        Client::with_provider(&format!("http://{address}"), None, crate::Provider::Forgejo)
            .unwrap();
    let repository = RepositoryId::new("forgejo", "forgejo").unwrap();
    let contents = client
        .repository_contents(&repository, "docs/private guide")
        .await
        .unwrap();

    assert_eq!(contents.len(), 1);
    assert_eq!(
        contents[0].path.as_deref(),
        Some("docs/private guide/guide.md")
    );
    assert!(
        server
            .join()
            .unwrap()
            .starts_with("GET /api/v1/repos/forgejo/forgejo/contents/docs%2Fprivate%20guide ")
    );
}

#[tokio::test]
async fn reads_file_with_spaces_from_raw_endpoint() {
    let listener = TcpListener::bind("127.0.0.1:0").unwrap();
    let address = listener.local_addr().unwrap();
    let server = thread::spawn(move || {
        let (mut stream, _) = listener.accept().unwrap();
        let mut buffer = [0; 4096];
        let length = stream.read(&mut buffer).unwrap();
        let request = String::from_utf8_lossy(&buffer[..length]).into_owned();
        let body = "contents";
        write!(
            stream,
            "HTTP/1.1 200 OK\r\nContent-Length: {}\r\nConnection: close\r\n\r\n{body}",
            body.len()
        )
        .unwrap();
        request
    });

    let client = Client::new(&format!("http://{address}"), None).unwrap();
    let repository = RepositoryId::new("gitea", "gitea").unwrap();
    let contents = client
        .repository_file(&repository, "docs/private guide/read me.md")
        .await
        .unwrap();

    assert_eq!(contents, b"contents");
    assert!(
        server.join().unwrap().starts_with(
            "GET /api/v1/repos/gitea/gitea/raw/docs%2Fprivate%20guide%2Fread%20me.md "
        )
    );
}
