pub mod checks;
pub mod fetcher;
pub mod fetchers;
pub mod models;
pub mod resources;
pub mod scanner;
pub mod utils;

use anyhow::Result;
use std::path::PathBuf;
use std::sync::Arc;
use tokio::sync::Mutex;

use crate::fetchers::{CompositeFetcher, CrateFetcher};
use serde::Deserialize;
use serde_json::Value;

// MCP-specific imports
#[cfg(feature = "mcp")]
use rmcp::transport::io::stdio;
#[cfg(feature = "mcp")]
use rmcp::ServiceExt;
#[cfg(feature = "mcp")]
use rmcp::{
    model::{
        CallToolRequestParam, CallToolResult, Content, Implementation, ListToolsResult,
        PaginatedRequestParamInner, ProtocolVersion, ServerCapabilities, ServerInfo, Tool,
    },
    tool, Error as McpError, ServerHandler,
};
#[cfg(feature = "mcp")]
use schemars::JsonSchema;
#[cfg(feature = "mcp")]
use rmcp::handler::server::tool::Parameters as McpParametersWrapper;
#[cfg(feature = "mcp")]
use rmcp::service::RequestContext;
#[cfg(feature = "mcp")]
use std::borrow::Cow;

/// MCP Tool implementation
#[cfg(feature = "mcp")]
#[derive(Clone)]
pub struct ScanCrateTool {
    fetcher: Arc<Mutex<CompositeFetcher>>,
}

/// Parameters required for scanning a crate, used in MCP context.
#[cfg(feature = "mcp")]
#[derive(Debug, Deserialize, JsonSchema)]
#[allow(dead_code)] // Fields are used via deserialization by the MCP framework
pub struct ScanCrateParameters {
    #[schemars(
        description = "The crate to scan, optionally with a version (e.g., 'serde' or 'serde@1.0.130')."
    )]
    pub crate_spec: String,
    #[schemars(description = "The regex pattern to search for within the crate's source code.")]
    pub query: String,
    #[schemars(description = "Whether the regex query should be case-sensitive.")]
    pub case_sensitive: bool,
}

/// Parameters for the standalone inspection logic
#[derive(Debug, Clone)]
pub struct InspectionParams {
    pub crate_spec: String,
    pub query: String,
    pub case_sensitive: bool,
}

/// Performs the core crate inspection logic.
/// Returns the JSON result on success or an anyhow::Error on failure.
pub async fn perform_inspection(
    params: InspectionParams,
    fetcher: &impl CrateFetcher,
) -> Result<Value, anyhow::Error> {
    // Prerequisite checks
    checks::check_prerequisites()
        .map_err(|s| anyhow::anyhow!("Prerequisite check failed: {}", s))?;

    // Parse crate spec
    let (crate_name, crate_version_req) = utils::parse_crate_spec(&params.crate_spec);

    // Fetch the crate source code
    let crate_src_path: PathBuf = fetcher
        .fetch_crate_src(&crate_name, crate_version_req.as_deref())
        .await
        .map_err(|e| anyhow::anyhow!("Failed to fetch/find crate source: {}", e))?;

    // Execute ripgrep
    // TODO: Add support for include/exclude patterns and case sensitivity here
    let ripgrep_output =
        utils::execute_ripgrep(&params.query, &crate_src_path, params.case_sensitive)
            .map_err(|e| anyhow::anyhow!("Failed to execute ripgrep: {}", e))?;

    // Parse ripgrep output
    let scan_results: Vec<utils::MatchData> = utils::parse_ripgrep_output(&ripgrep_output)
        .map_err(|e| anyhow::anyhow!("Failed to parse ripgrep output: {}", e))?;

    // Aggregate and format results
    let aggregated_matches = utils::aggregate_matches_by_file(scan_results);
    let success_json = utils::format_success_output(
        &params.query,
        &params.crate_spec,
        &crate_src_path.to_string_lossy(),
        aggregated_matches,
    )
    .map_err(|e| anyhow::anyhow!("Failed to format success output: {}", e))?;

    // Parse the JSON string into a serde_json::Value
    let json_value: Value = serde_json::from_str(&success_json)
        .map_err(|e| anyhow::anyhow!("Failed to parse formatted JSON string: {}", e))?;

    Ok(json_value)
}

#[cfg(feature = "mcp")]
#[tool(tool_box)]
impl ScanCrateTool {
    pub fn new(fetcher: CompositeFetcher) -> Self {
        Self {
            fetcher: Arc::new(Mutex::new(fetcher)),
        }
    }

    #[tool(
        name = "scan_crate",
        description = r#"Performs a regex search in a Rust crate's source by directly passing the 'query' string to `ripgrep` (rg) as the regex pattern.
No intermediate un-escaping is done by this tool. Standard `ripgrep` regex syntax (Rust's `regex` crate) applies.

Key Escaping Rules:
1. Literal Regex Special Chars: Escape with a single backslash.
   - Example: To match 'foo()', use query: 'foo\(\)'
   - Example: To match 'MyType::new', use query: 'MyType::new' (no special chars)
2. Literal Backslash: To match a '\' in text, use '\\' in your regex pattern.
   - Example: For path 'C:\tmp', the regex for the backslash is '\\', so overall: 'C:\\tmp'
3. JSON Escaping (for MCP users): If providing the query within a JSON string, remember JSON also requires escaping backslashes.
   - A regex 'foo\(\)' becomes "foo\\(\\)" in a JSON string.
   - A regex 'C:\\tmp' (to match 'C:\tmp') becomes "C:\\\\tmp" in a JSON string.

For full regex syntax details, please refer to the Rust `regex` crate and `ripgrep` documentation.

AI Assistant Usage Guidelines (for context enhancement):
Upon receiving search results (file paths and line numbers):
1. Identify all unique source files containing matches. Prioritize files within primary source directories (e.g., 'src/') over test or example files.
2. For EACH unique, prioritized source file, proactively read content around ALL specified line numbers (e.g., ~5-10 lines before and after each match).
3. Do NOT ask for confirmation before reading these initial context snippets.
4. Internally synthesize the information gathered from all read snippets to build a comprehensive understanding of the searched pattern's usage and context across the crate.
5. To further broaden understanding, especially if the crate is unfamiliar or being analyzed for the first time in the current session:
   a. Attempt to locate and read the crate's `README.md` file. This file is typically found at the root of the downloaded crate source path (which is part of the search results).
   b. Analyze the file paths returned by the search to infer the crate's top-level directory structure (e.g., noting the presence of `src`, `tests`, `examples`, `benches` directories).
6. Use this comprehensive understanding (from specific matches, README content, and directory structure) to inform your responses and actions. When presenting to the user, summarize key insights, provide representative code examples, and include the full list of match locations. Be prepared to perform deeper analysis of specific files if the initial context warrants further investigation."#
    )]
    async fn scan_crate(
        &self,
        params_wrapper: McpParametersWrapper<ScanCrateParameters>,
    ) -> Result<CallToolResult, McpError> {
        let params = params_wrapper.0;
        log::info!("Received scan_crate request with params: {params:?}");

        // Map MCP params to standalone params
        let inspection_params = InspectionParams {
            crate_spec: params.crate_spec,
            query: params.query,
            case_sensitive: params.case_sensitive,
        };

        // Lock the fetcher and call the standalone logic
        let fetcher_guard = self.fetcher.lock().await;
        match perform_inspection(inspection_params, &*fetcher_guard).await {
            Ok(success_json) => {
                // Use the serde_json::Value directly if possible, or serialize it
                // For now, assume Content::text needs a string, so serialize the Value
                let json_string = match serde_json::to_string(&success_json) {
                    Ok(s) => s,
                    Err(e) => {
                        let err_msg = format!("Failed to serialize success JSON: {e}");
                        log::error!("{err_msg}"); // Use log::error
                        return Ok(CallToolResult::error(vec![Content::text(&err_msg)]));
                    }
                };
                log::info!("Scan successful, returning result.");
                Ok(CallToolResult::success(vec![Content::text(&json_string)]))
            }
            Err(e) => {
                let err_msg = format!("{e}"); // Use the anyhow error message directly
                log::error!("Inspection failed: {err_msg}");
                Ok(CallToolResult::error(vec![Content::text(&err_msg)]))
            }
        }
    }
}

#[cfg(feature = "mcp")]
impl ServerHandler for ScanCrateTool {
    fn get_info(&self) -> ServerInfo {
        ServerInfo {
            protocol_version: ProtocolVersion::LATEST,
            capabilities: ServerCapabilities::builder().enable_tools().build(),
            server_info: Implementation {
                name: "scan_crate".to_string(),
                version: "0.1.0".to_string(),
            },
            instructions: Some("Provide crate spec & regex query. The tool will return matches. See tool description for AI guidelines on processing results.".into()),
        }
    }

    async fn list_tools(
        &self,
        _request: Option<PaginatedRequestParamInner>,
        _context: RequestContext<rmcp::service::RoleServer>,
    ) -> Result<ListToolsResult, McpError> {
        log::info!("[ScanCrateTool ServerHandler] Restored manual list_tools");
        tracing::debug!("[MCP Flow] ScanCrateTool::list_tools entered.");

        // Generate schema from ScanCrateParameters
        let schema = schemars::schema_for!(ScanCrateParameters);
        let schema_value = serde_json::to_value(&schema).map_err(|e| {
            McpError::internal_error(format!("Failed to serialize schema: {e}"), None)
        })?;

        let input_schema_map = match schema_value {
            serde_json::Value::Object(map) => map,
            _ => {
                log::error!("Schema serialization did not result in a JSON object.");
                return Err(McpError::internal_error(
                    "Schema serialization did not result in a JSON object.",
                    None,
                ));
            }
        };

        let tool = Tool {
            name: Cow::Borrowed("scan_crate"), // Matches the name in #[tool]
            description: Cow::Borrowed(
                r#"Performs a regex search in a Rust crate's source by directly passing the 'query' string to `ripgrep` (rg) as the regex pattern.
No intermediate un-escaping is done by this tool. Standard `ripgrep` regex syntax (Rust's `regex` crate) applies.

Key Escaping Rules:
1. Literal Regex Special Chars: Escape with a single backslash.
   - Example: To match 'foo()', use query: 'foo\(\)'
   - Example: To match 'MyType::new', use query: 'MyType::new' (no special chars)
2. Literal Backslash: To match a '\' in text, use '\\' in your regex pattern.
   - Example: For path 'C:\tmp', the regex for the backslash is '\\', so overall: 'C:\\tmp'
3. JSON Escaping (for MCP users): If providing the query within a JSON string, remember JSON also requires escaping backslashes.
   - A regex 'foo\(\)' becomes "foo\\(\\)" in a JSON string.
   - A regex 'C:\\tmp' (to match 'C:\tmp') becomes "C:\\\\tmp" in a JSON string.

For full regex syntax details, please refer to the Rust `regex` crate and `ripgrep` documentation.

AI Assistant Usage Guidelines (for context enhancement):
Upon receiving search results (file paths and line numbers):
1. Identify all unique source files containing matches. Prioritize files within primary source directories (e.g., 'src/') over test or example files.
2. For EACH unique, prioritized source file, proactively read content around ALL specified line numbers (e.g., ~5-10 lines before and after each match).
3. Do NOT ask for confirmation before reading these initial context snippets.
4. Internally synthesize the information gathered from all read snippets to build a comprehensive understanding of the searched pattern's usage and context across the crate.
5. To further broaden understanding, especially if the crate is unfamiliar or being analyzed for the first time in the current session:
   a. Attempt to locate and read the crate's `README.md` file. This file is typically found at the root of the downloaded crate source path (which is part of the search results).
   b. Analyze the file paths returned by the search to infer the crate's top-level directory structure (e.g., noting the presence of `src`, `tests`, `examples`, `benches` directories).
6. Use this comprehensive understanding (from specific matches, README content, and directory structure) to inform your responses and actions. When presenting to the user, summarize key insights, provide representative code examples, and include the full list of match locations. Be prepared to perform deeper analysis of specific files if the initial context warrants further investigation.
7. After reviewing all files, provide a concise summary of what you learned rather than detailed descriptions of your search process."#,
            ),
            input_schema: Arc::new(input_schema_map),
            // annotations: None, // Field removed in new rmcp version
        };
        let tools_vec = vec![tool]; // Create the vector first
        tracing::debug!(
            "[MCP Flow] ScanCrateTool::list_tools: Prepared {} tool(s).",
            tools_vec.len()
        ); // Log its length
        Ok(ListToolsResult {
            tools: tools_vec, // Use the created vector
            next_cursor: None,
        })
    }

    async fn call_tool(
        &self,
        request: CallToolRequestParam,
        _context: RequestContext<rmcp::service::RoleServer>,
    ) -> Result<CallToolResult, McpError> {
        log::info!(
            "[ScanCrateTool ServerHandler] Manual call_tool received request for tool: {}",
            request.name
        );

        if request.name == "scan_crate" {
            let arguments = request.arguments.unwrap_or_default(); // Get JsonObject or empty
            match serde_json::from_value::<ScanCrateParameters>(serde_json::Value::Object(
                arguments,
            )) {
                Ok(parsed_params) => {
                    log::info!("Successfully parsed params: {parsed_params:?}");
                    // Call the actual tool method
                    self.scan_crate(McpParametersWrapper(parsed_params)).await
                }
                Err(e) => {
                    log::error!("Failed to parse arguments for scan_crate: {e}");
                    Ok(CallToolResult::error(vec![Content::text(format!(
                        "Invalid parameters: {e}"
                    ))]))
                }
            }
        } else {
            log::warn!("Tool name '{}' not found.", request.name);
            Err(McpError::method_not_found::<
                rmcp::model::CallToolRequestMethod,
            >())
        }
    }
}

/// Initializes and runs the MCP server for the `ScanCrateTool`.
///
/// This function sets up the server to listen for tool calls (e.g., `scan_crate`)
/// over standard input/output, allowing integration with MCP-compatible clients like Cursor.
#[cfg(feature = "mcp")]
pub async fn run_mcp_server() -> Result<()> {
    // Composite fetcher handles all types of crate sources
    let fetcher = CompositeFetcher::new();

    // Original code restored:
    log::info!("[MCP Server] Entered run_mcp_server");
    tracing::debug!("[MCP Flow] inspector-lib/run_mcp_server: Entered function.");

    // Create the necessary CrateFetcher
    let tool = ScanCrateTool::new(fetcher);
    log::info!("[MCP Server] Created ScanCrateTool service");

    // Use the rmcp specific serve method
    log::info!("[MCP Server] Attempting tool.serve(stdio()).await...");
    tracing::debug!("[MCP Flow] inspector-lib/run_mcp_server: About to call tool.serve(stdio()).");
    let serve_result = tool.serve(stdio()).await;

    match serve_result {
        Ok(service_controller) => {
            log::info!("[MCP Server] tool.serve(stdio()).await successful, now waiting...");
            tracing::debug!("[MCP Flow] inspector-lib/run_mcp_server: tool.serve() successful. Waiting on controller.");
            service_controller.waiting().await?;
            tracing::debug!(
                "[MCP Flow] inspector-lib/run_mcp_server: Service controller finished waiting."
            );
            log::info!("[MCP Server] Service finished cleanly.");
        }
        Err(e) => {
            log::error!("[MCP Server] tool.serve(stdio()).await failed: {e:?}");
            tracing::error!(
                "[MCP Flow] inspector-lib/run_mcp_server: tool.serve() failed: {:?}",
                e
            );
            return Err(anyhow::anyhow!("MCP serve failed: {}", e));
        }
    }
    tracing::debug!("[MCP Flow] inspector-lib/run_mcp_server: Exiting function successfully.");
    Ok(())
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::fetchers::CrateFetcher;
    use crate::models::ScanCrateError;
    use anyhow::Result;
    use std::fs::{self, File};
    use std::io::Write;
    use std::path::PathBuf;
    use tempfile::TempDir;

    // Helper struct for mock crate fetcher
    struct MockCrateFetcher {
        temp_dir: TempDir,
        crate_name: String,
    }

    impl MockCrateFetcher {
        fn new(crate_name: &str, version: &str, content: &str) -> Result<Self, std::io::Error> {
            let temp_dir = TempDir::new()?;
            let crate_root = temp_dir.path().join(format!("{}-{}", crate_name, version));
            let src_dir = crate_root.join("src");
            fs::create_dir_all(&src_dir)?;

            let lib_rs_path = src_dir.join("lib.rs");
            let mut file = File::create(lib_rs_path)?;
            writeln!(file, "{}", content)?;

            Ok(Self {
                temp_dir, // Keep TempDir to ensure it's not dropped prematurely
                crate_name: crate_name.to_string(),
            })
        }

        // Returns the path to the root of the mock crate, not src.
        // perform_inspection expects the crate source path.
        // The fetch_crate_src should return the directory containing Cargo.toml,
        // and ripgrep will search from there.
        // For testing ripgrep, we give it the directory to search.
        // Let's adjust this to return the directory that ripgrep should search,
        // which is the root of the temporary crate structure.
        fn get_search_path(&self) -> PathBuf {
            self.temp_dir
                .path()
                .join(format!("{}-{}", self.crate_name, "0.1.0")) // Assuming a fixed version for path construction
        }
    }

    #[async_trait::async_trait]
    impl CrateFetcher for MockCrateFetcher {
        async fn fetch_crate_src(
            &self,
            _crate_name: &str,
            _version_req: Option<&str>,
        ) -> std::result::Result<PathBuf, ScanCrateError> {
            // The `perform_inspection` function expects the path to the crate's source root
            // (the directory ripgrep will scan).
            // Our MockCrateFetcher sets up .../temp_dir/crate_name-version/src/lib.rs
            // So, the path to return for scanning is .../temp_dir/crate_name-version/
            Ok(self.get_search_path())
        }
    }

    async fn run_inspection_test(
        crate_content: &str,
        query: &str,
        case_sensitive: bool,
        expected_matches: Option<(&str, Vec<u64>)>, // (file_path_suffix, line_numbers)
    ) {
        let crate_name = "testcase";
        let crate_version = "0.1.0";
        let mock_fetcher = MockCrateFetcher::new(crate_name, crate_version, crate_content).unwrap();

        let params = InspectionParams {
            crate_spec: format!("{}@{}", crate_name, crate_version),
            query: query.to_string(),
            case_sensitive,
        };

        let result = perform_inspection(params, &mock_fetcher).await;

        if let Err(e) = &result {
            eprintln!("Inspection failed: {:?}", e); // Print error for debugging in tests
        }
        assert!(result.is_ok(), "perform_inspection failed");

        let json_value = result.unwrap();
        let matches_map = json_value.get("matches").and_then(|m| m.as_object());

        if let Some((expected_file_suffix, expected_lines)) = expected_matches {
            assert!(
                matches_map.is_some(),
                "Expected matches, but 'matches' field is missing or not an object. JSON: {}",
                serde_json::to_string_pretty(&json_value).unwrap_or_default()
            );
            let matches_map_unwrapped = matches_map.unwrap();

            // Construct the expected full file path
            let expected_key = mock_fetcher
                .get_search_path()
                .join(expected_file_suffix)
                .to_string_lossy()
                .into_owned();

            let expected_key_str = expected_key.as_str(); // Get a &str explicitly
            assert!(
                matches_map_unwrapped.contains_key(expected_key_str),
                "Expected file '{}' not found in matches. Found: {:?}. JSON: {}",
                expected_key, // This is String, fine for format arg
                matches_map_unwrapped.keys().collect::<Vec<_>>(),
                serde_json::to_string_pretty(&json_value).unwrap_or_default()
            );

            let lines_val = matches_map_unwrapped.get(expected_key_str).unwrap();
            let actual_lines: Vec<u64> = lines_val
                .as_array()
                .expect("Match lines are not an array")
                .iter()
                .map(|v| v.as_u64().expect("Line number is not u64"))
                .collect();

            assert_eq!(
                actual_lines,
                expected_lines,
                "Mismatch in matched line numbers for file '{}'. JSON: {}",
                expected_key,
                serde_json::to_string_pretty(&json_value).unwrap_or_default()
            );
        } else {
            // Expected no matches
            if let Some(map) = matches_map {
                assert!(
                    map.is_empty()
                        || map
                            .values()
                            .all(|v| v.as_array().map_or(true, |a| a.is_empty())),
                    "Expected no matches, but found some: {:?}. JSON: {}",
                    map,
                    serde_json::to_string_pretty(&json_value).unwrap_or_default()
                );
            }
            // If matches_map itself is None, that also means no matches, which is fine.
        }
    }

    #[tokio::test]
    async fn test_scan_simple_literal() {
        let content = "fn main() {\n  println!(\"hello\");\n}\nfn another() {}";
        run_inspection_test(content, "fn", false, Some(("src/lib.rs", vec![1, 4]))).await;
    }

    #[tokio::test]
    async fn test_scan_escaped_curly_brace() {
        let content = "struct MyStruct { field: i32 }\nimpl MyStruct { fn new() -> Self { MyStruct { field: 0 } } }";
        // Query to match "MyStruct {" (literal curly brace)
        run_inspection_test(
            content,
            "MyStruct \\{",
            false,
            Some(("src/lib.rs", vec![1, 2])),
        )
        .await;
    }

    #[tokio::test]
    async fn test_scan_escaped_parenthesis() {
        let content = "my_func(arg1, arg2);\nother_call();";
        // Query to match "my_func(" (literal parenthesis)
        run_inspection_test(content, "my_func\\(", false, Some(("src/lib.rs", vec![1]))).await;
    }

    #[tokio::test]
    async fn test_scan_word_boundary() {
        let content = "let main_val = 0;\nfn main() {}\n// mainframe";
        // Query to match "main" as a whole word. Regex needs \b, so string is "\\b"
        run_inspection_test(content, "\\bmain\\b", false, Some(("src/lib.rs", vec![2]))).await;
    }

    #[tokio::test]
    async fn test_scan_no_match() {
        let content = "fn main() {}";
        run_inspection_test(content, "nonexistent_pattern", false, None).await;
    }

    #[tokio::test]
    async fn test_scan_case_sensitive_match() {
        let content = "Hello world\nhello Rust";
        run_inspection_test(content, "Hello", true, Some(("src/lib.rs", vec![1]))).await;
    }

    #[tokio::test]
    async fn test_scan_case_insensitive_match() {
        let content = "Hello world\nhello Rust";
        // Case-insensitive, "hello" should match "Hello" and "hello"
        run_inspection_test(content, "hello", false, Some(("src/lib.rs", vec![1, 2]))).await;
    }

    #[tokio::test]
    async fn test_scan_dot_metacharacter() {
        let content = "cat\ncut\ncot";
        // "c.t" should match "cat", "cut", "cot"
        run_inspection_test(content, "c.t", false, Some(("src/lib.rs", vec![1, 2, 3]))).await;
    }

    #[tokio::test]
    async fn test_scan_star_quantifier() {
        let content = "ab\nabc\nabbc\nac";
        // "ab*c" should match "ac", "abc", "abbc"
        // Lines are 1-indexed.
        // "ac" is on line 4
        // "abc" is on line 2
        // "abbc" is on line 3
        run_inspection_test(content, "ab*c", false, Some(("src/lib.rs", vec![2, 3, 4]))).await;
    }

    #[tokio::test]
    async fn test_scan_plus_quantifier() {
        let content = "ac\nabc\nabbc";
        // "ab+c" should match "abc", "abbc"
        run_inspection_test(content, "ab+c", false, Some(("src/lib.rs", vec![2, 3]))).await;
    }

    #[test]
    fn it_works() {
        let result = add(2, 2);
        assert_eq!(result, 4);
    }
}

// Restore the add function
#[cfg(test)]
fn add(left: usize, right: usize) -> usize {
    left + right
}
