use super::handlers;
use super::tooling::schema_to_object_map;
use crate::mcp::McpCallResponse;
use crate::types::ErrorResponse;
use crate::{history, AppState};
use axum::http::StatusCode;
use axum::response::Json;
use rmcp::{model::*, ServiceExt};
use serde_json::Value;
use std::borrow::Cow;
use std::env;
use std::sync::Arc;
use std::time::{Duration, Instant};
use tracing::{info, warn};

fn status_code_to_error_code(status: StatusCode) -> ErrorCode {
    match status {
        StatusCode::BAD_REQUEST | StatusCode::UNPROCESSABLE_ENTITY => ErrorCode::INVALID_PARAMS,
        StatusCode::NOT_FOUND => ErrorCode::METHOD_NOT_FOUND,
        _ => ErrorCode::INTERNAL_ERROR,
    }
}

fn mcp_call_response_to_stdio_result(response: McpCallResponse) -> CallToolResult {
    let content = response
        .content
        .into_iter()
        .map(|item| Content::text(item.text))
        .collect();

    if response.is_error {
        CallToolResult::error(content)
    } else {
        CallToolResult::success(content)
    }
}

fn convert_http_handler_result_with_metrics(
    tool_name: &str,
    started_at: Instant,
    result: Result<Json<McpCallResponse>, (StatusCode, Json<ErrorResponse>)>,
) -> Result<CallToolResult, ErrorData> {
    match result {
        Ok(Json(response)) => Ok(mcp_call_response_to_stdio_result(
            super::http::instrument_tool_response(response, tool_name, started_at),
        )),
        Err((status, Json(err))) => Err(ErrorData::new(
            status_code_to_error_code(status),
            err.error,
            None,
        )),
    }
}

#[derive(Clone, Debug)]
pub struct McpService {
    pub state: Arc<AppState>,
}

impl McpService {
    pub async fn new() -> anyhow::Result<Self> {
        tracing_subscriber::fmt()
            .with_writer(std::io::stderr)
            .with_env_filter(crate::build_env_filter("warn"))
            .try_init()
            .ok();

        // Pre-flight checklist (non-interactive) at startup
        let report = crate::setup::check_all(crate::setup::SetupOptions::default()).await;
        info!("{}", report.summarize_for_logs());
        if report.has_failures() {
            warn!("shadow-setup: startup checklist found failures; run cortex-scout-mcp --setup for guided remediation");
            report.print_action_required_blocks();
        }

        info!("Starting MCP Service");

        let http_timeout = env::var("HTTP_TIMEOUT_SECS")
            .ok()
            .and_then(|v| v.parse::<u64>().ok())
            .unwrap_or(30);
        let connect_timeout = env::var("HTTP_CONNECT_TIMEOUT_SECS")
            .ok()
            .and_then(|v| v.parse::<u64>().ok())
            .unwrap_or(10);

        let http_client = reqwest::Client::builder()
            .timeout(std::time::Duration::from_secs(http_timeout))
            .connect_timeout(std::time::Duration::from_secs(connect_timeout))
            .build()?;

        let mut state = AppState::new(http_client);

        // Proxy manager — synchronous file read, fast.
        let ip_list_path = env::var("IP_LIST_PATH").unwrap_or_else(|_| "ip.txt".to_string());
        if tokio::fs::metadata(&ip_list_path).await.is_ok() {
            info!("Loading proxy manager from IP list: {}", ip_list_path);
            match crate::proxy_manager::ProxyManager::new(&ip_list_path).await {
                Ok(proxy_manager) => {
                    let status = proxy_manager.get_status().await?;
                    state = state.with_proxy_manager(Arc::new(proxy_manager));
                    info!(
                        "Proxy manager initialized: {} total proxies, {} enabled",
                        status.total_proxies, status.enabled_proxies
                    );
                }
                Err(e) => warn!("Failed to initialize proxy manager: {}", e),
            }
        } else {
            info!(
                "IP list not found at {}. Proxy feature disabled.",
                ip_list_path
            );
        }

        // Wrap state in Arc now — MCP transport can start immediately.
        let state = Arc::new(state);

        // LanceDB / semantic memory — may trigger IVF index rebuild on large datasets
        // (several seconds). Spawn in background so MCP handshake is not delayed.
        if let Some(lancedb_uri) = crate::core::config::lancedb_uri() {
            let state_bg = Arc::clone(&state);
            if !lancedb_uri.contains("://") {
                let _ = tokio::fs::create_dir_all(&lancedb_uri).await;
            }
            info!("Initializing memory with LanceDB at: {} (background)", lancedb_uri);
            tokio::spawn(async move {
                match history::MemoryManager::new(&lancedb_uri).await {
                    Ok(memory) => {
                        state_bg.install_memory(Arc::new(memory));
                        info!("Memory initialized successfully (background)");
                    }
                    Err(e) => {
                        state_bg.mark_memory_failed();
                        warn!(
                            "Failed to initialize memory: {}. Continuing without memory.",
                            e
                        );
                    }
                }
            });
        } else {
            info!("Semantic memory disabled (CORTEX_SCOUT_MEMORY_DISABLED=1)");
        }

        Ok(Self { state })
    }
}

impl rmcp::ServerHandler for McpService {
    fn get_info(&self) -> ServerInfo {
        let server_info = Implementation::from_build_env()
            .with_title("Search & Sync MCP")
            .with_description(
                "A pure Rust web research service using federated search plus high-integrity content synchronization for consistent downstream analysis."
            );
        let mut info = ServerInfo::new(ServerCapabilities::builder().enable_tools().build());
        info.protocol_version = ProtocolVersion::LATEST;
        info.server_info = server_info;
        info.instructions = Some(
            "Use these tools to discover sources and synchronize web content into consistent, analysis-ready outputs."
                .to_string(),
        );
        info
    }

    async fn list_tools(
        &self,
        _page: Option<PaginatedRequestParams>,
        _context: rmcp::service::RequestContext<rmcp::RoleServer>,
    ) -> Result<ListToolsResult, ErrorData> {
        let tools = self
            .state
            .tool_registry
            .public_specs()
            .into_iter()
            .map(|spec| {
                let mut tool = Tool::default();
                tool.name = Cow::Owned(spec.public_name);
                tool.title = Some(spec.public_title);
                tool.description = Some(Cow::Owned(spec.public_description));
                tool.input_schema = schema_to_object_map(&spec.public_input_schema);
                tool
            })
            .collect();

        Ok(ListToolsResult {
            tools,
            ..Default::default()
        })
    }

    async fn call_tool(
        &self,
        request: CallToolRequestParams,
        _context: rmcp::service::RequestContext<rmcp::RoleServer>,
    ) -> Result<CallToolResult, ErrorData> {
        let tool_start = Instant::now();
        info!(
            "MCP tool call: {} with args: {:?}",
            request.name, request.arguments
        );

        let args_map = request.arguments.as_ref().ok_or_else(|| {
            ErrorData::new(
                ErrorCode::INVALID_PARAMS,
                "Missing required arguments object",
                None,
            )
        })?;

        let internal_name = self
            .state
            .tool_registry
            .resolve_incoming_tool_name(request.name.as_ref())
            .or_else(|| match request.name.as_ref() {
                "scout_browser_automate" => Some("browser_automate".to_string()),
                "scout_browser_close" => Some("browser_close".to_string()),
                "scout_agent_profile_auth" => Some("agent_profile_auth".to_string()),
                _ => None,
            })
            .ok_or_else(|| {
                ErrorData::new(
                    ErrorCode::METHOD_NOT_FOUND,
                    format!("Unknown tool: {}", request.name),
                    None,
                )
            })?;

        // rmcp stdio arguments are an object map; mcp_handlers expect a serde_json::Value.
        // Clone is fine here (tool inputs are small) and keeps handler API consistent across transports.
        let public_args = Value::Object(args_map.clone());
        let internal_args = self
            .state
            .tool_registry
            .map_public_arguments_to_internal(&internal_name, public_args);

        let tool_timeout =
            Duration::from_secs(crate::core::config::mcp_tool_timeout_secs(&internal_name));
        let request_name = request.name.to_string();
        let state = Arc::clone(&self.state);
        let dispatch_name = internal_name.clone();

        let dispatch = async move {
            match dispatch_name.as_str() {
                "search_web" => handlers::search_web::handle(Arc::clone(&state), &internal_args).await,
                "search_structured" => handlers::search_structured::handle(Arc::clone(&state), &internal_args).await,
                "scrape_url" => handlers::scrape_url::handle(Arc::clone(&state), &internal_args).await,
                "crawl_website" => handlers::crawl_website::handle(Arc::clone(&state), &internal_args).await,
                "scrape_batch" => handlers::scrape_batch::handle(Arc::clone(&state), &internal_args).await,
                "deep_research" => handlers::deep_research::handle(Arc::clone(&state), &internal_args).await,
                "extract_structured" => handlers::extract_structured::handle(Arc::clone(&state), &internal_args).await,
                "fetch_then_extract" => handlers::fetch_then_extract::handle(Arc::clone(&state), &internal_args).await,
                "research_history" => handlers::research_history::handle(Arc::clone(&state), &internal_args).await,
                "proxy_manager" => handlers::proxy_manager::handle(Arc::clone(&state), &internal_args).await,
                "non_robot_search" => handlers::non_robot_search::handle(Arc::clone(&state), &internal_args).await,
                "visual_scout" => handlers::visual_scout::handle(Arc::clone(&state), &internal_args).await,
                "human_auth_session" => handlers::human_auth_session::handle(Arc::clone(&state), &internal_args).await,
                "browser_automate" | "scout_browser_automate" => {
                    handlers::automate::handle(Arc::clone(&state), &internal_args).await
                }
                "browser_close" | "scout_browser_close" => {
                    handlers::automate::handle_close(Arc::clone(&state), &internal_args).await
                }
                "agent_profile_auth" | "scout_agent_profile_auth" => {
                    handlers::automate::handle_profile_auth(Arc::clone(&state), &internal_args).await
                }
                _ => Err((
                    StatusCode::BAD_REQUEST,
                    Json(ErrorResponse {
                        error: format!("Unknown tool: {}", request_name),
                    }),
                )),
            }
        };

        match tokio::time::timeout(tool_timeout, dispatch).await {
            Ok(result) => {
                convert_http_handler_result_with_metrics(request.name.as_ref(), tool_start, result)
            }
            Err(_) => Ok(mcp_call_response_to_stdio_result(
                super::http::instrument_tool_response(
                    super::timeout::timeout_call_response(request.name.as_ref(), tool_timeout),
                    request.name.as_ref(),
                    tool_start,
                ),
            )),
        }
    }
}

pub async fn run() -> anyhow::Result<()> {
    let service = McpService::new().await?;
    let running = service.serve(rmcp::transport::stdio()).await?;
    info!("MCP stdio server initialized; waiting for client session");
    let quit_reason = running.waiting().await?;
    warn!("MCP stdio server stopped: {:?}", quit_reason);
    Ok(())
}
