use std::path::PathBuf;

use nexus_lang::{Nexus, Session, WarningKind};

/// Directories holding the `use_*` import fixtures.
const PASS_DIR: &str = concat!(env!("CARGO_MANIFEST_DIR"), "/tests/interpreter/pass");
const FAIL_DIR: &str = concat!(env!("CARGO_MANIFEST_DIR"), "/tests/interpreter/fail");

#[test]
fn load_string_roundtrip() {
    let mut session = Session::new();
    let id = session.load_string("let x = 42;", Some("example.nxs"));
    let source = session.source(id).unwrap();
    assert_eq!(source.code(), "let x = 42;");
    assert_eq!(source.name(), Some("example.nxs"));
}

#[test]
fn load_string_no_name() {
    let mut session = Session::new();
    let id = session.load_string("let x = 1;", None);
    assert_eq!(session.source(id).unwrap().name(), None);
}

#[test]
fn load_file_roundtrip() {
    let mut session = Session::new();
    let path = concat!(env!("CARGO_MANIFEST_DIR"), "/Cargo.toml");
    let id = session.load_file(path).unwrap();
    assert!(session.source(id).unwrap().code().contains("[package]"));
}

#[test]
fn load_file_dedup() {
    let mut session = Session::new();
    let path = concat!(env!("CARGO_MANIFEST_DIR"), "/Cargo.toml");
    let id1 = session.load_file(path).unwrap();
    let id2 = session.load_file(path).unwrap();
    assert_eq!(id1, id2);
}

#[test]
fn load_file_missing() {
    let mut session = Session::new();
    assert!(session.load_file("/nonexistent/path/file.nxs").is_err());
}

#[test]
fn source_foreign_id_returns_none() {
    let mut session1 = Session::new();
    let id = session1.load_string("let x = 1;", None);

    let session2 = Session::new();
    assert!(session2.source(id).is_none());
}

#[test]
fn with_search_paths_preserves_session() {
    let mut session = Session::new().with_search_paths(vec![PathBuf::from("/usr/local/lib/nexus")]);
    let id = session.load_string("let x = 1;", None);
    assert!(session.source(id).is_some());
}

#[test]
fn tokens_nonempty_for_valid_source() {
    let mut session = Session::new();
    let id = session.load_string("let x = 42;", None);
    let tokens = session.tokens(id).unwrap();
    assert!(!tokens.is_empty());
}

#[test]
fn tokens_cached() {
    let mut session = Session::new();
    let id = session.load_string("let x = 42;", None);
    assert!(session.tokens(id).is_ok());
    assert!(session.tokens(id).is_ok());
}

#[test]
fn ast_nonempty_for_valid_source() {
    let mut session = Session::new();
    let id = session.load_string("let x = 42;", None);
    let ast = session.ast(id).unwrap();
    assert!(!ast.is_empty());
}

#[test]
fn ast_cached() {
    let mut session = Session::new();
    let id = session.load_string("let x = 42;", None);
    assert!(session.ast(id).is_ok());
    assert!(session.ast(id).is_ok());
}

#[test]
fn ast_scan_error() {
    let mut session = Session::new();
    let id = session.load_string("let x = \"unterminated", None);
    assert!(session.ast(id).is_err());
}

#[test]
fn ast_parse_error() {
    let mut session = Session::new();
    let id = session.load_string("let 42;", None);
    assert!(session.ast(id).is_err());
}

// --- End-to-end import resolution through the session path (Nexus::run_file) ---

#[test]
fn run_file_resolves_basic_import() {
    assert!(Nexus::run_file(format!("{PASS_DIR}/use_basic.nxs")).is_ok());
}

#[test]
fn run_file_resolves_transitive_import() {
    assert!(Nexus::run_file(format!("{PASS_DIR}/use_transitive.nxs")).is_ok());
}

#[test]
fn run_file_duplicate_import_is_ok() {
    // `use foo; use foo;` in one file must not double-merge into a collision.
    assert!(Nexus::run_file(format!("{PASS_DIR}/use_dedup.nxs")).is_ok());
}

#[test]
fn run_file_genuine_collision_errors() {
    assert!(Nexus::run_file(format!("{FAIL_DIR}/use_name_collision.nxs")).is_err());
}

#[test]
fn run_file_missing_import_errors() {
    assert!(Nexus::run_file(format!("{FAIL_DIR}/use_file_not_found.nxs")).is_err());
}

#[test]
fn run_file_use_inside_block_errors() {
    assert!(Nexus::run_file(format!("{FAIL_DIR}/use_not_top_level.nxs")).is_err());
}

#[test]
fn evaluate_returns_warnings_separately() {
    // `let r = node Router;` is unused → at least one warning surfaces via the session.
    let mut session = Session::new();
    let id = session
        .load_file(format!("{PASS_DIR}/use_basic.nxs"))
        .unwrap();
    let _network = session.evaluate(id).unwrap();
    assert!(!session.warnings().is_empty());
}

#[test]
fn dangling_connection_warns_and_is_dropped() {
    // A connection whose endpoints never reach the result tree must not survive
    // into `Network.connections`, and must be reported instead of silently dropped.
    let (network, warnings) = Nexus::run(
        "node type Q { in i; out o; }
         let p = node Q;
         let q = node Q;
         p.o -> q.i;
         print p;",
    )
    .unwrap();

    assert!(network.root.is_none());
    assert!(network.connections.is_empty());
    assert!(
        warnings
            .iter()
            .any(|w| w.kind == WarningKind::DanglingConnection)
    );
}
