use std::path::Path;

use super::parse_file;

macro_rules! python_parser_fixture {
    ($path:literal) => {
        include_str!(concat!(
            env!("CARGO_MANIFEST_DIR"),
            "/tests/fixtures/python/",
            $path
        ))
    };
}

#[test]
fn test_python_parser_extracts_functions_imports_and_strings() {
    let source = python_parser_fixture!("parser/async_calls_positive.txt");

    let parsed =
        parse_file(Path::new("pkg/service.py"), source).expect("python parsing should succeed");

    assert_eq!(parsed.package_name.as_deref(), Some("service"));
    assert_eq!(parsed.imports.len(), 2);
    assert_eq!(parsed.pkg_strings.len(), 1);
    assert_eq!(parsed.pkg_strings[0].name, "API_TOKEN");
    assert_eq!(parsed.functions.len(), 3);

    let fetch = &parsed.functions[0];
    assert_eq!(fetch.fingerprint.name, "fetch_profile");
    assert_eq!(fetch.fingerprint.kind, "async_function");
    assert_eq!(fetch.local_strings.len(), 2);
    assert_eq!(fetch.python_evidence().concat_loops, vec![15]);
    assert!(fetch.python_evidence().exception_handlers.is_empty());
    assert!(fetch.doc_comment.is_some());
    assert!(
        fetch
            .calls
            .iter()
            .any(|call| call.receiver.as_deref() == Some("req") && call.name == "get")
    );

    let render = &parsed.functions[1];
    assert_eq!(render.fingerprint.kind, "method");
    assert_eq!(
        render.fingerprint.receiver_type.as_deref(),
        Some("Renderer")
    );

    let helper = &parsed.functions[2];
    assert_eq!(helper.fingerprint.name, "helper");
    assert!(helper.calls.iter().any(|call| call.name == "print"));
}

#[test]
fn test_python_parser_marks_syntax_errors() {
    let source = python_parser_fixture!("parser/syntax_error_positive.txt");

    let parsed = parse_file(Path::new("broken.py"), source)
        .expect("python parsing should still return a parsed file");

    assert!(parsed.syntax_error);
}

#[test]
fn test_python_test_detection() {
    let source = python_parser_fixture!("parser/test_detection_positive.txt");

    let parsed = parse_file(Path::new("tests/test_client.py"), source)
        .expect("python parsing should succeed");

    assert!(parsed.is_test_file);
    assert!(parsed.functions[0].is_test_function);
    assert!(parsed.functions[0].test_summary.is_some());
}

#[test]
fn test_python_exception_handler_evidence() {
    let positive = parse_file(
        Path::new("config.py"),
        python_parser_fixture!("maintainability/exception_shapes_positive.txt"),
    )
    .expect("python parsing should succeed");
    let negative = parse_file(
        Path::new("config_safe.py"),
        python_parser_fixture!("maintainability/exception_shapes_negative.txt"),
    )
    .expect("python parsing should succeed");

    let load_config = &positive.functions[0];
    assert_eq!(load_config.python_evidence().exception_handlers.len(), 1);
    assert!(load_config.python_evidence().exception_handlers[0].is_broad);
    assert!(load_config.python_evidence().exception_handlers[0].suppresses);
    assert_eq!(
        load_config.python_evidence().exception_handlers[0]
            .action
            .as_deref(),
        Some("pass")
    );

    let recover_config = &negative.functions[0];
    assert_eq!(recover_config.python_evidence().exception_handlers.len(), 1);
    assert!(!recover_config.python_evidence().exception_handlers[0].is_broad);
}

#[test]
fn test_python_async_io_fixture_contract() {
    let positive = parse_file(
        Path::new("pkg/network_sync.py"),
        python_parser_fixture!("performance/async_io_positive.txt"),
    )
    .expect("python parsing should succeed");
    let negative = parse_file(
        Path::new("pkg/network_async.py"),
        python_parser_fixture!("performance/async_io_negative.txt"),
    )
    .expect("python parsing should succeed");

    let sync_reports = &positive.functions[0];
    assert!(sync_reports.fingerprint.kind.starts_with("async_"));
    assert!(sync_reports.python_evidence().is_async);
    assert!(sync_reports.calls.iter().any(|call| call.name == "get"));

    let async_reports = &negative.functions[0];
    assert!(async_reports.fingerprint.kind.starts_with("async_"));
    assert!(async_reports.python_evidence().is_async);
    assert!(async_reports.calls.iter().any(|call| call.name == "get"));
}

#[test]
fn test_python_type_hint_fixture_contract() {
    let positive = parse_file(
        Path::new("pkg/api_types.py"),
        python_parser_fixture!("maintainability/type_hints_positive.txt"),
    )
    .expect("python parsing should succeed");
    let negative = parse_file(
        Path::new("pkg/api_types_partial.py"),
        python_parser_fixture!("maintainability/type_hints_negative.txt"),
    )
    .expect("python parsing should succeed");

    assert!(
        positive.functions[0]
            .python_evidence()
            .has_complete_type_hints
    );
    assert!(
        !negative.functions[0]
            .python_evidence()
            .has_complete_type_hints
    );
}

#[test]
fn test_python_phase4_parser_evidence() {
    let source = python_parser_fixture!("parser/class_summary_positive.txt");

    let parsed =
        parse_file(Path::new("pkg/service.py"), source).expect("python parsing should succeed");

    let process_items = &parsed.functions[0];
    assert_eq!(
        process_items.python_evidence().none_comparison_lines,
        vec![3]
    );
    assert_eq!(
        process_items.python_evidence().redundant_return_none_lines,
        vec![4]
    );
    assert_eq!(
        process_items
            .python_evidence()
            .side_effect_comprehension_lines,
        vec![6]
    );
    assert_eq!(
        process_items.python_evidence().list_materialization_lines,
        vec![7]
    );
    assert_eq!(
        process_items.python_evidence().deque_operation_lines,
        vec![9]
    );
    assert_eq!(
        process_items.python_evidence().temp_collection_lines,
        vec![13]
    );
    assert_eq!(
        process_items.python_evidence().list_membership_loop_lines,
        vec![15]
    );
    assert_eq!(
        process_items.python_evidence().repeated_len_loop_lines,
        vec![12]
    );
    assert_eq!(
        process_items.python_evidence().builtin_candidate_lines,
        vec![12]
    );
    assert!(process_items.python_evidence().has_varargs);
    assert!(process_items.python_evidence().has_kwargs);
    assert!(!process_items.python_evidence().has_complete_type_hints);
    assert!(
        process_items
            .python_evidence()
            .validation_signature
            .is_some()
    );
    assert!(!process_items.python_evidence().normalized_body.is_empty());

    let scan_tree = &parsed.functions[1];
    assert_eq!(scan_tree.python_evidence().recursive_call_lines, vec![23]);

    let read_config = &parsed.functions[2];
    assert_eq!(
        read_config.python_evidence().missing_context_manager_lines,
        vec![26]
    );

    assert_eq!(parsed.class_summaries().len(), 2);
    let payload_manager = &parsed.class_summaries()[1];
    assert_eq!(payload_manager.name, "PayloadManager");
    assert_eq!(payload_manager.method_count, 3);
    assert_eq!(payload_manager.instance_attribute_count, 13);
    assert_eq!(payload_manager.public_method_count, 2);
    assert_eq!(payload_manager.base_classes, vec!["BaseManager"]);
    assert_eq!(payload_manager.constructor_collaborator_count, 3);
}

#[test]
fn test_python_init_reexports_are_indexed_as_symbols() {
    let source = python_parser_fixture!("parser/reexports_positive.txt");

    let parsed = parse_file(Path::new("pkg/widgets/__init__.py"), source)
        .expect("python parsing should succeed");

    assert!(parsed.imports.iter().all(|import| import.is_public));
    assert!(
        parsed
            .symbols
            .iter()
            .any(|symbol| symbol.name == "WidgetTemplate")
    );
    assert!(
        parsed
            .symbols
            .iter()
            .any(|symbol| symbol.name == "LayoutConfig")
    );
    assert!(parsed.symbols.iter().any(|symbol| symbol.name == "Heading"));
    assert!(
        parsed
            .symbols
            .iter()
            .any(|symbol| symbol.name == "render_widget")
    );
}

#[test]
fn test_python_non_init_relative_imports_do_not_become_public_reexports() {
    let source = python_parser_fixture!("parser/reexports_negative.txt");

    let parsed = parse_file(Path::new("pkg/widgets/helpers.py"), source)
        .expect("python parsing should succeed");

    assert!(parsed.imports.iter().all(|import| !import.is_public));
    assert!(parsed.symbols.iter().all(|symbol| {
        !matches!(
            symbol.name.as_str(),
            "WidgetTemplate" | "LayoutConfig" | "_HiddenWidget" | "render_widget"
        )
    }));
}

#[test]
fn test_python_parenthesized_from_import_ignores_inline_comments() {
    let source = python_parser_fixture!("parser/parenthesized_imports_positive.txt");

    let parsed = parse_file(Path::new("tests/test_widgets.py"), source)
        .expect("python parsing should succeed");

    assert!(
        parsed
            .imports
            .iter()
            .any(|import| import.alias == "WidgetTemplate")
    );
    assert!(
        parsed
            .imports
            .iter()
            .any(|import| import.alias == "LayoutConfig")
    );
    assert!(
        parsed
            .imports
            .iter()
            .any(|import| import.alias == "Heading")
    );
}

#[test]
fn test_python_commentary_fixtures_extract_real_comments_only() {
    let positive = parse_file(
        Path::new("pkg/comments.py"),
        python_parser_fixture!("ai_smells/commentary_positive.txt"),
    )
    .expect("python parsing should succeed");
    let negative = parse_file(
        Path::new("pkg/comments_safe.py"),
        python_parser_fixture!("ai_smells/commentary_negative.txt"),
    )
    .expect("python parsing should succeed");

    assert_eq!(positive.comments.len(), 2);
    assert_eq!(positive.comments[0].text, "set the running total");
    assert_eq!(positive.comments[1].text, "return the final count");
    assert!(
        positive
            .comments
            .iter()
            .all(|comment| !comment.text.contains("not a comment"))
    );
    assert!(negative.comments.is_empty());
}

#[test]
fn test_python_boundary_fixture_contract() {
    let network_positive = parse_file(
        Path::new("pkg/network_sync.py"),
        python_parser_fixture!("maintainability/boundary_network_positive.txt"),
    )
    .expect("python parsing should succeed");
    let network_negative = parse_file(
        Path::new("pkg/network_safe.py"),
        python_parser_fixture!("maintainability/boundary_network_negative.txt"),
    )
    .expect("python parsing should succeed");
    let config_positive = parse_file(
        Path::new("pkg/config_loader.py"),
        python_parser_fixture!("maintainability/boundary_config_positive.txt"),
    )
    .expect("python parsing should succeed");
    let cli_positive = parse_file(
        Path::new("pkg/cli.py"),
        python_parser_fixture!("maintainability/boundary_cli_positive.txt"),
    )
    .expect("python parsing should succeed");
    let cli_negative = parse_file(
        Path::new("pkg/cli_safe.py"),
        python_parser_fixture!("maintainability/boundary_cli_negative.txt"),
    )
    .expect("python parsing should succeed");

    let sync_reports = &network_positive.functions[0];
    assert!(
        sync_reports
            .calls
            .iter()
            .any(|call| { call.receiver.as_deref() == Some("requests") && call.name == "get" })
    );
    assert!(
        !sync_reports
            .body_text
            .to_ascii_lowercase()
            .contains("timeout=")
    );

    let safe_reports = &network_negative.functions[0];
    assert!(
        safe_reports
            .calls
            .iter()
            .any(|call| { call.receiver.as_deref() == Some("requests") && call.name == "get" })
    );
    assert!(
        safe_reports
            .body_text
            .to_ascii_lowercase()
            .contains("timeout=5")
    );

    let load_runtime_config = &config_positive.functions[0];
    assert!(
        load_runtime_config
            .calls
            .iter()
            .any(|call| { call.receiver.as_deref() == Some("os") && call.name == "getenv" })
    );

    let run_cli = &cli_positive.functions[0];
    assert!(
        run_cli
            .calls
            .iter()
            .any(|call| { call.receiver.as_deref() == Some("json") && call.name == "loads" })
    );
    assert!(!run_cli.body_text.contains("len(sys.argv) < 2"));

    let safe_cli = &cli_negative.functions[0];
    assert!(
        safe_cli
            .calls
            .iter()
            .any(|call| { call.receiver.as_deref() == Some("json") && call.name == "loads" })
    );
    assert!(safe_cli.body_text.contains("len(sys.argv) < 2"));
}

#[test]
fn test_python_symbol_extraction_preserves_naming_styles() {
    let source = python_parser_fixture!("ai_smells/naming_styles_positive.txt");

    let parsed =
        parse_file(Path::new("pkg/naming_mix.py"), source).expect("python parsing should succeed");

    let symbol_names = parsed
        .symbols
        .iter()
        .map(|symbol| symbol.name.as_str())
        .collect::<Vec<_>>();

    assert!(symbol_names.contains(&"HTTPClient"));
    assert!(symbol_names.contains(&"widget_renderer"));
    assert!(symbol_names.contains(&"render_json"));
    assert!(symbol_names.contains(&"buildHTML"));
}

#[test]
fn test_python_advanceplan2_parser_evidence() {
    let source = python_parser_fixture!("parser/advanceplan2_evidence.txt");

    let parsed =
        parse_file(Path::new("pkg/service.py"), source).expect("python parsing should succeed");

    assert_eq!(parsed.module_scope_calls.len(), 1);
    assert_eq!(parsed.module_scope_calls[0].name, "Client");
    assert_eq!(parsed.top_level_bindings.len(), 1);
    assert_eq!(parsed.top_level_bindings[0].name, "SETTINGS");
    assert_eq!(parsed.python_models().len(), 2);

    let payload = parsed
        .python_models()
        .iter()
        .find(|model| model.name == "Payload")
        .expect("expected dataclass summary");
    assert!(payload.is_dataclass);
    assert_eq!(payload.fields.len(), 2);
    assert_eq!(payload.fields[0].default_text.as_deref(), Some("[]"));

    let payload_shape = parsed
        .python_models()
        .iter()
        .find(|model| model.name == "PayloadShape")
        .expect("expected typeddict summary");
    assert!(payload_shape.is_typed_dict);
    assert_eq!(payload_shape.fields.len(), 2);
    assert_eq!(
        payload_shape.fields[1].annotation_text.as_deref(),
        Some("NotRequired[str]")
    );

    let orchestrate = parsed
        .functions
        .iter()
        .find(|function| function.fingerprint.name == "orchestrate")
        .expect("expected async function summary");
    assert!(orchestrate.python_evidence().is_async);
    assert_eq!(
        orchestrate.python_evidence().await_points,
        vec![orchestrate.body_start_line + 2]
    );
    assert!(
        orchestrate
            .signature_text
            .contains("async def orchestrate(lock):")
    );
}
