use aurora_core::{AuroraError, AuroraResult, Pipeline, Value};
use rayon::prelude::*;
use std::borrow::Cow;
use std::fs;
use std::path::Path;
use std::sync::Mutex;
use tree_sitter::{Language, Node, Parser, Tree};
use walkdir::WalkDir;

const SUPPORTED_EXTENSIONS: &[&str] = &[
    "rs", "py", "js", "jsx", "ts", "tsx", "c", "h", "cpp", "cc", "cxx", "hpp", "go",
];

fn language_for_extension(ext: &str) -> Option<Language> {
    match ext {
        "rs" => Some(tree_sitter_rust::LANGUAGE.into()),
        "py" => Some(tree_sitter_python::LANGUAGE.into()),
        "js" | "jsx" => Some(tree_sitter_javascript::LANGUAGE.into()),
        "ts" => Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
        "tsx" => Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
        "c" | "h" => Some(tree_sitter_c::LANGUAGE.into()),
        "cpp" | "cc" | "cxx" | "hpp" => Some(tree_sitter_cpp::LANGUAGE.into()),
        "go" => Some(tree_sitter_go::LANGUAGE.into()),
        _ => None,
    }
}

fn source_files(root: &str, filter_langs: &[&str]) -> Vec<(String, String)> {
    WalkDir::new(root)
        .into_iter()
        .filter_map(|e| e.ok())
        .filter(|e| e.file_type().is_file())
        .filter_map(|e| {
            let path = e.path().to_string_lossy().to_string();
            let ext = Path::new(&path)
                .extension()
                .and_then(|s| s.to_str())
                .unwrap_or("")
                .to_lowercase();
            if SUPPORTED_EXTENSIONS.contains(&ext.as_str())
                && filter_langs.iter().any(|l| *l == ext)
            {
                Some((path, ext))
            } else {
                None
            }
        })
        .collect()
}

fn parse_file(path: &str, lang: Language) -> Option<(Tree, Vec<u8>)> {
    let source = fs::read(path).ok()?;
    let mut parser = Parser::new();
    parser.set_language(&lang).ok()?;
    let tree = parser.parse(&source, None)?;
    Some((tree, source))
}

fn node_text<'a>(node: Node<'a>, source: &'a [u8]) -> Cow<'a, str> {
    node.utf8_text(source)
        .map(Cow::Borrowed)
        .unwrap_or_else(|_| Cow::Owned(String::new()))
}

fn traverse<'tree, F>(node: Node<'tree>, source: &'tree [u8], f: &mut F)
where
    F: FnMut(Node<'tree>, &'tree [u8]),
{
    f(node, source);
    let mut cursor = node.walk();
    if cursor.goto_first_child() {
        loop {
            traverse(cursor.node(), source, f);
            if !cursor.goto_next_sibling() {
                break;
            }
        }
    }
}

fn find_ancestor_kind<'tree>(node: Node<'tree>, kinds: &[&str]) -> Option<Node<'tree>> {
    let mut current = node;
    loop {
        if kinds.contains(&current.kind()) {
            return Some(current);
        }
        current = current.parent()?;
    }
}

fn context_line_at(source: &str, line_1based: usize) -> String {
    let idx = line_1based.saturating_sub(1);
    source
        .lines()
        .nth(idx)
        .unwrap_or("")
        .trim()
        .to_string()
}

// ---------------------------------------------------------------------------
// Pattern 1: "open without close"
//   Find file-handle open calls without a matching close/drop in the same
//   function scope.
// ---------------------------------------------------------------------------
fn check_open_without_close(
    source_text: &str,
    source_bytes: &[u8],
    tree: &Tree,
) -> Vec<(usize, String)> {
    let root = tree.root_node();
    let mut results = Vec::new();

    let open_keywords: &[&str] = &[
        "open",
        "fopen",
        "File::open",
        "create",
        "File::create",
        "OpenFile",
        "os.Open",
        "os.Create",
    ];
    let close_keywords: &[&str] = &[
        "close",
        "drop",
        "shutdown",
        "fclose",
        "CloseHandle",
        "Close",
        "defer",
    ];

    traverse(root, source_bytes, &mut |node, src| {
        let text = node_text(node, src);
        let has_open = open_keywords.iter().any(|kw| text.contains(kw));
        if !has_open {
            return;
        }
        let func_scope = find_ancestor_kind(
            node,
            &[
                "function_item",
                "function_definition",
                "function_declaration",
                "method_definition",
                "arrow_function",
                "closure_expression",
            ],
        );
        let scope = match func_scope {
            Some(s) => s,
            None => return,
        };
        let scope_text = node_text(scope, src);
        let has_close = close_keywords.iter().any(|kw| scope_text.contains(kw));
        if !has_close {
            let line = node.start_position().row + 1;
            let ctx = context_line_at(source_text, line);
            results.push((
                line,
                format!("open call without close: \"{}\"", ctx),
            ));
        }
    });

    results
}

// ---------------------------------------------------------------------------
// Pattern 2: "unused result"
//   Find expression statements whose expression is a function call — the
//   return value is discarded.
// ---------------------------------------------------------------------------
fn check_unused_result(
    source_text: &str,
    source_bytes: &[u8],
    tree: &Tree,
) -> Vec<(usize, String)> {
    let root = tree.root_node();
    let mut results = Vec::new();

    traverse(root, source_bytes, &mut |node, _src| {
        if node.kind() != "expression_statement" {
            return;
        }
        let mut cursor = node.walk();
        if !cursor.goto_first_child() {
            return;
        }
        let child = cursor.node();
        if child.kind() != "call_expression" {
            return;
        }
        let line = node.start_position().row + 1;
        let ctx = context_line_at(source_text, line);
        results.push((
            line,
            format!("unused result: \"{}\"", ctx),
        ));
    });

    results
}

// ---------------------------------------------------------------------------
// Pattern 3: "null/dereference risk"
//   Find unwrap(), expect(), index expressions, unsafe dereference patterns.
// ---------------------------------------------------------------------------
fn check_null_deref_risk(
    source_text: &str,
    source_bytes: &[u8],
    tree: &Tree,
) -> Vec<(usize, String)> {
    let root = tree.root_node();
    let mut results = Vec::new();

    traverse(root, source_bytes, &mut |node, src| {
        let text = node_text(node, src);
        let line = node.start_position().row + 1;
        let ctx = context_line_at(source_text, line);

        if text.contains(".unwrap()") || text.contains("unwrap(") {
            results.push((
                line,
                format!("unwrap call: \"{}\"", ctx),
            ));
        } else if text.contains(".expect(") {
            results.push((
                line,
                format!("expect call: \"{}\"", ctx),
            ));
        } else if node.kind() == "index_expression" {
            results.push((
                line,
                format!("indexing: \"{}\"", ctx),
            ));
        }
    });

    results
}

// ---------------------------------------------------------------------------
// Pattern 4: "error swallowed"
//   Find `let _ =`, bare Result-returning calls whose value is discarded,
//   match expressions that ignore the error arm.
// ---------------------------------------------------------------------------
fn check_error_swallowed(
    source_text: &str,
    source_bytes: &[u8],
    tree: &Tree,
) -> Vec<(usize, String)> {
    let root = tree.root_node();
    let mut results = Vec::new();

    traverse(root, source_bytes, &mut |node, _src| {
        let ctx = context_line_at(source_text, node.start_position().row + 1);
        let line = node.start_position().row + 1;

        match node.kind() {
            "expression_statement" => {
                let mut cursor = node.walk();
                if cursor.goto_first_child() {
                    let child = cursor.node();
                    if child.kind() == "call_expression" {
                        results.push((
                            line,
                            format!("error swallowed: \"{}\"", ctx),
                        ));
                    }
                }
            }
            "assignment" | "let_declaration" => {
                if ctx.contains("let _ =") || ctx.contains("let _=") {
                    results.push((
                        line,
                        format!("error swallowed with `let _`: \"{}\"", ctx),
                    ));
                }
            }
            _ => {}
        }
    });

    results
}

// ---------------------------------------------------------------------------
// Pattern 5: "race condition"
//   Find mutable global/static state without synchronization.
// ---------------------------------------------------------------------------
fn check_race_condition(
    source_text: &str,
    source_bytes: &[u8],
    tree: &Tree,
) -> Vec<(usize, String)> {
    let root = tree.root_node();
    let mut results = Vec::new();

    traverse(root, source_bytes, &mut |node, src| {
        let text = node_text(node, src);
        let line = node.start_position().row + 1;
        let ctx = context_line_at(source_text, line);

        match node.kind() {
            "static_item" | "global_variable" | "mutable_declaration" => {
                let is_mutable = text.contains("mut ");
                let has_sync = text.contains("Mutex")
                    || text.contains("RwLock")
                    || text.contains("Atomic")
                    || text.contains("sync");
                if is_mutable && !has_sync {
                    results.push((
                        line,
                        format!(
                            "mutable global without sync: \"{}\"",
                            ctx
                        ),
                    ));
                }
            }
            _ => {}
        }
    });

    results
}

// ---------------------------------------------------------------------------
// Pattern matching dispatch
// ---------------------------------------------------------------------------
fn pattern_name_from_query(query: &str) -> &'static str {
    let q = query.to_lowercase();
    if q.contains("open") && (q.contains("close") || q.contains("leak")) {
        "open_without_close"
    } else if q.contains("unused") && q.contains("result") {
        "unused_result"
    } else if q.contains("null")
        || q.contains("deref")
        || q.contains("unwrap")
        || q.contains("panic")
    {
        "null_deref_risk"
    } else if q.contains("error")
        && (q.contains("swallow")
            || q.contains("ignore")
            || q.contains("silent"))
    {
        "error_swallowed"
    } else if q.contains("race")
        || q.contains("thread")
        || (q.contains("mutable") && q.contains("global"))
        || q.contains("sync")
    {
        "race_condition"
    } else {
        "unused_result"
    }
}

fn apply_pattern(
    pattern: &str,
    source_text: &str,
    source_bytes: &[u8],
    tree: &Tree,
) -> Vec<(usize, String)> {
    match pattern {
        "open_without_close" => check_open_without_close(source_text, source_bytes, tree),
        "unused_result" => check_unused_result(source_text, source_bytes, tree),
        "null_deref_risk" => check_null_deref_risk(source_text, source_bytes, tree),
        "error_swallowed" => check_error_swallowed(source_text, source_bytes, tree),
        "race_condition" => check_race_condition(source_text, source_bytes, tree),
        _ => check_unused_result(source_text, source_bytes, tree),
    }
}

fn confidence_for(pattern: &str) -> f64 {
    match pattern {
        "open_without_close" => 0.75,
        "unused_result" => 0.6,
        "null_deref_risk" => 0.8,
        "error_swallowed" => 0.7,
        "race_condition" => 0.6,
        _ => 0.5,
    }
}

/// Perform semantic search based on natural-language query.
///
/// `query` — a natural-language description like "find where we open a file
///           but don't close".
/// `path`  — the root directory to scan.
/// `lang`  — language extensions to include (e.g. ["rs", "py", "js"]).
pub fn semantic_search(
    query: &str,
    path: &str,
    lang: &[&str],
) -> AuroraResult<Pipeline> {
    if lang.is_empty() {
        return Err(AuroraError::InvalidInput(
            "at least one language must be specified".to_string(),
        ));
    }

    let pattern = pattern_name_from_query(query);
    let files = source_files(path, lang);
    let results = Mutex::new(Vec::new());

    files.par_iter().for_each(|(file_path, ext)| {
        let source_text = match fs::read_to_string(file_path) {
            Ok(s) => s,
            Err(_) => return,
        };
        let lang_obj = match language_for_extension(ext) {
            Some(l) => l,
            None => return,
        };
        let parsed = parse_file(file_path, lang_obj);
        let (tree, source_bytes) = match parsed {
            Some(p) => p,
            None => return,
        };

        let matches = apply_pattern(pattern, &source_text, &source_bytes, &tree);
        let conf = confidence_for(pattern);

        let mut lock = results.lock().unwrap_or_else(|e| e.into_inner());
        for (line, desc) in matches {
            lock.push((
                file_path.clone(),
                line,
                desc,
                conf,
            ));
        }
    });

    let lock = results.into_inner().unwrap_or_default();
    let rows: Vec<Vec<Value>> = lock
        .into_iter()
        .map(|(file, line, desc, conf)| {
            vec![
                Value::String(file),
                Value::Int(line as i64),
                Value::String(desc),
                Value::Float(conf),
            ]
        })
        .collect();

    Ok(Pipeline::table(
        vec![
            "file".to_string(),
            "line".to_string(),
            "match".to_string(),
            "confidence".to_string(),
        ],
        rows,
    ))
}
