use crate::analysis::{Language, ParsedFile, ParsedFunction};
use crate::index::RepositoryIndex;
use crate::model::Finding;

#[cfg(test)]
pub(crate) use crate::analysis::rust::findings::{
    alias_lookup, call_matches_import, import_matches_item,
};

use crate::analysis::rust::findings::{
    doc_marker_findings, non_test_call_findings, non_test_macro_findings, rust_call_findings,
    rust_import_findings, unsafe_findings,
};

use super::{
    api_design_file_findings, api_design_function_findings, async_file_findings,
    async_function_findings, boundary_file_findings, boundary_function_findings, domain_findings,
    module_surface_file_findings, performance_file_findings, performance_function_findings,
    runtime_file_findings, runtime_function_findings, runtime_ownership_function_findings,
    security_footguns_file_findings, security_footguns_function_findings,
    unsafe_soundness_findings,
};

pub(crate) const BINDING_LOCATION: &str = file!();

const NON_TEST_MACRO_RULES: [(&str, &str, &str); 5] = [
    (
        "todo!",
        "todo_macro_leftover",
        "leaves todo! in non-test Rust code",
    ),
    (
        "unimplemented!",
        "unimplemented_macro_leftover",
        "leaves unimplemented! in non-test Rust code",
    ),
    (
        "dbg!",
        "dbg_macro_leftover",
        "leaves dbg! in non-test Rust code",
    ),
    (
        "panic!",
        "panic_macro_leftover",
        "leaves panic! in non-test Rust code",
    ),
    (
        "unreachable!",
        "unreachable_macro_leftover",
        "leaves unreachable! in non-test Rust code",
    ),
];

const NON_TEST_CALL_RULES: [(&str, &str, &str); 2] = [
    (
        "unwrap",
        "unwrap_in_non_test_code",
        "calls unwrap() in non-test Rust code",
    ),
    (
        "expect",
        "expect_in_non_test_code",
        "calls expect() in non-test Rust code",
    ),
];

pub(crate) fn evaluate_rust_file_hygiene_findings(
    file: &ParsedFile,
    function: &ParsedFunction,
) -> Vec<Finding> {
    let mut findings = Vec::new();

    for (macro_name, rule_id, message_suffix) in NON_TEST_MACRO_RULES {
        findings.extend(non_test_macro_findings(
            file,
            function,
            macro_name,
            rule_id,
            message_suffix,
        ));
    }

    for (call_name, rule_id, message_suffix) in NON_TEST_CALL_RULES {
        findings.extend(non_test_call_findings(
            file,
            function,
            call_name,
            rule_id,
            message_suffix,
        ));
    }

    findings.extend(unsafe_findings(file, function));
    findings.extend(doc_marker_findings(file, function));
    findings
}

pub(crate) fn rust_api_design_file_findings(file: &ParsedFile) -> Vec<Finding> {
    api_design_file_findings(file)
}

pub(crate) fn rust_api_design_function_findings(
    file: &ParsedFile,
    function: &ParsedFunction,
) -> Vec<Finding> {
    api_design_function_findings(file, function)
}

pub(crate) fn rust_async_file_findings(file: &ParsedFile) -> Vec<Finding> {
    async_file_findings(file)
}

pub(crate) fn rust_async_function_findings(
    file: &ParsedFile,
    function: &ParsedFunction,
) -> Vec<Finding> {
    async_function_findings(file, function)
}

pub(crate) fn rust_boundary_file_findings(file: &ParsedFile) -> Vec<Finding> {
    boundary_file_findings(file)
}

pub(crate) fn rust_boundary_function_findings(
    file: &ParsedFile,
    function: &ParsedFunction,
) -> Vec<Finding> {
    boundary_function_findings(file, function)
}

pub(crate) fn rust_domain_file_findings(file: &ParsedFile) -> Vec<Finding> {
    domain_findings(file)
}

pub(crate) fn rust_module_surface_file_findings(file: &ParsedFile) -> Vec<Finding> {
    module_surface_file_findings(file)
}

pub(crate) fn rust_performance_file_findings(file: &ParsedFile) -> Vec<Finding> {
    performance_file_findings(file)
}

pub(crate) fn rust_performance_function_findings(
    file: &ParsedFile,
    function: &ParsedFunction,
) -> Vec<Finding> {
    performance_function_findings(file, function)
}

pub(crate) fn rust_runtime_file_findings(
    file: &ParsedFile,
    index: &RepositoryIndex,
) -> Vec<Finding> {
    runtime_file_findings(file, index)
}

pub(crate) fn rust_runtime_function_findings(
    file: &ParsedFile,
    function: &ParsedFunction,
) -> Vec<Finding> {
    runtime_function_findings(file, function)
}

pub(crate) fn rust_runtime_ownership_function_findings(
    file: &ParsedFile,
    function: &ParsedFunction,
) -> Vec<Finding> {
    runtime_ownership_function_findings(file, function)
}

pub(crate) fn rust_security_file_findings(
    file: &ParsedFile,
    index: &RepositoryIndex,
) -> Vec<Finding> {
    security_footguns_file_findings(file, index)
}

pub(crate) fn rust_security_function_findings(
    file: &ParsedFile,
    function: &ParsedFunction,
) -> Vec<Finding> {
    security_footguns_function_findings(file, function)
}

pub(crate) fn rust_unsafe_soundness_findings(
    file: &ParsedFile,
    function: &ParsedFunction,
) -> Vec<Finding> {
    unsafe_soundness_findings(file, function)
}

pub(crate) fn rust_import_resolution_findings(
    file: &ParsedFile,
    function: &ParsedFunction,
    index: &RepositoryIndex,
) -> Vec<Finding> {
    let Some(package_name) = &file.package_name else {
        return Vec::new();
    };
    let Some(current_package) = index.package_for_file(Language::Rust, &file.path, package_name)
    else {
        return Vec::new();
    };

    rust_import_findings(file, function, index, &file.imports, current_package)
}

pub(crate) fn rust_local_call_findings(
    file: &ParsedFile,
    function: &ParsedFunction,
    index: &RepositoryIndex,
) -> Vec<Finding> {
    rust_call_findings(file, function, index, &file.imports)
}
