// SPDX-License-Identifier: Apache-2.0
use super::*;

fn nested_rust_modules(depth: usize, inner: &str) -> String {
    let mut source = String::new();
    for level in 0..depth {
        source.push_str(&format!("mod layer_{level} {{\n"));
    }
    source.push_str(inner);
    source.push('\n');
    for _ in 0..depth {
        source.push_str("}\n");
    }
    source
}

#[test]
fn test_language_from_path() {
    assert_eq!(
        Language::from_path(std::path::Path::new("foo.rs")),
        Language::Rust
    );
    assert_eq!(
        Language::from_path(std::path::Path::new("foo.py")),
        Language::Python
    );
    assert_eq!(
        Language::from_path(std::path::Path::new("foo.txt")),
        Language::Unknown
    );
}

#[test]
fn test_parse_rust_function() {
    let source = r#"
fn hello_world() -> String {
    "Hello".to_string()
}

fn add(a: i32, b: i32) -> i32 {
    a + b
}
"#;

    let parsed = ParsedFile::parse(source, Language::Rust).expect("Should parse");
    let functions = parsed.extract_functions();

    assert_eq!(functions.len(), 2);
    assert_eq!(functions[0].name, "hello_world");
    assert_eq!(functions[1].name, "add");
    assert!(functions[1].signature.contains("a"));
    assert!(functions[1].signature.contains("b"));
}

#[cfg(all(
    feature = "lang-rust",
    feature = "lang-python",
    feature = "lang-javascript",
    feature = "lang-typescript"
))]
#[test]
fn test_parse_common_language_functions_and_imports() {
    let cases = [
        (
            Language::Rust,
            r#"
use std::collections::HashMap;

pub async fn load_map() -> HashMap<String, usize> {
    HashMap::new()
}
"#,
            "load_map",
            "std::collections",
        ),
        (
            Language::Python,
            r#"
from pathlib import Path

@pytest.mark.slow
async def load_path(root: Path) -> Path:
    return root / "file.txt"
"#,
            "load_path",
            "pathlib",
        ),
        (
            Language::JavaScript,
            r#"
import fs from "node:fs";

export const readConfig = async (path) => {
    return fs.readFileSync(path, "utf8");
};
"#,
            "readConfig",
            "node:fs",
        ),
        (
            Language::TypeScript,
            r#"
import type { Request } from "./types";

export const handleRequest = (request: Request): string => {
    return request.id;
};
"#,
            "handleRequest",
            "./types",
        ),
    ];

    for (language, source, function_name, import_text) in cases {
        let parsed = ParsedFile::parse(source, language)
            .unwrap_or_else(|| panic!("{language:?} should parse"));
        let functions = parsed.extract_functions();
        assert!(
            functions
                .iter()
                .any(|function| function.name == function_name),
            "{language:?} should extract {function_name}: {functions:?}"
        );
        let imports = parsed.extract_imports();
        assert!(
            imports
                .iter()
                .any(|import| import.raw.contains(import_text)),
            "{language:?} should extract import containing {import_text}: {imports:?}"
        );
    }
}

#[cfg(all(
    feature = "lang-c",
    feature = "lang-cpp",
    feature = "lang-go",
    feature = "lang-java"
))]
#[test]
fn test_parse_extended_language_functions_and_imports() {
    let cases = [
        (
            Language::Go,
            r#"
package main

import "context"

func Serve(ctx context.Context) error {
    return nil
}
"#,
            "Serve",
            "context",
        ),
        (
            Language::Java,
            r#"
import java.util.List;

class Handler {
    public String handle(List<String> values) {
        return values.get(0);
    }
}
"#,
            "handle",
            "java.util.List",
        ),
        (
            Language::C,
            r#"
#include <stdio.h>

int add(int left, int right) {
    return left + right;
}
"#,
            "add",
            "",
        ),
        (
            Language::Cpp,
            r#"
#include <vector>

int sum(std::vector<int> values) {
    return values.size();
}
"#,
            "sum",
            "",
        ),
    ];

    for (language, source, function_name, import_text) in cases {
        let parsed = ParsedFile::parse(source, language)
            .unwrap_or_else(|| panic!("{language:?} should parse"));
        let functions = parsed.extract_functions();
        assert!(
            functions
                .iter()
                .any(|function| function.name == function_name),
            "{language:?} should extract {function_name}: {functions:?}"
        );
        if !import_text.is_empty() {
            let imports = parsed.extract_imports();
            assert!(
                imports
                    .iter()
                    .any(|import| import.raw.contains(import_text)),
                "{language:?} should extract import containing {import_text}: {imports:?}"
            );
        }
    }
}

#[test]
fn test_extract_rust_imports() {
    let source = r#"
use std::collections::HashMap;
use serde::{Deserialize, Serialize};
extern crate anyhow;

fn main() {}
"#;

    let parsed = ParsedFile::parse(source, Language::Rust).expect("Should parse");
    let imports = parsed.extract_imports();

    assert_eq!(imports.len(), 3);
    assert!(imports.iter().any(|i| i.raw.contains("std")));
    assert!(imports.iter().any(|i| i.raw.contains("serde")));
    assert!(imports.iter().any(|i| i.raw.contains("anyhow")));
}

#[test]
fn test_extract_functions_handles_deeply_nested_modules() {
    let source = nested_rust_modules(512, "fn deeply_nested() -> i32 { 42 }");

    let parsed = ParsedFile::parse(source, Language::Rust).expect("Should parse");
    let functions = parsed.extract_functions();

    assert_eq!(functions.len(), 1);
    assert_eq!(functions[0].name, "deeply_nested");
}