//! Lightweight context accessor for function-based tools
//!
//! `Ctx` provides a simplified view of `ExecutionContext` for use with
//! the `#[mcp_tool]` attribute macro.

use std::collections::HashMap;

use serde::de::DeserializeOwned;

use crate::logging::McpLogger;
use crate::server::multiplexer::ClientRequester;
use crate::server::session::Session;
use crate::server::visibility::{Environment, ExecutionContext, VisibilityContext};

/// Build a context from an execution context.
///
/// Implement this trait to supply a custom context type for `#[mcp_tool]`,
/// `#[mcp_prompt]`, `#[mcp_resource]`, and `#[mcp_resource_template]` handlers.
pub trait FromExecutionContext<'a>: Sized {
    /// Construct `Self` from the execution context.
    fn from_execution_context(ctx: &'a ExecutionContext<'a>) -> Self;
}

/// Lightweight context accessor for function-based tools
///
/// Provides convenient access to session, environment, and client requester
/// without requiring the full `ExecutionContext` lifetime complexity.
///
/// # Example
///
/// ```rust,ignore
/// #[mcp_tool]
/// async fn my_tool(&self, ctx: Ctx, params: Parameters<MyParams>) -> ToolResult {
///     // Check roles
///     if !ctx.is_admin() {
///         return Err(ToolError::Execution("Admin only".into()));
///     }
///
///     // Access session state
///     if let Some(count) = ctx.get_state::<i32>("call_count") {
///         ctx.logger.info(&format!("Called {} times", count));
///     }
///
///     // Use bidirectional communication
///     if let Some(requester) = ctx.client_requester() {
///         let roots = requester.request_roots(None).await?;
///     }
///
///     Ok(vec![text("Done!")])
/// }
/// ```
#[derive(Clone)]
pub struct Ctx<'a> {
    /// Current session with roles, state, and client info
    pub session: &'a Session,
    /// Logger for sending notifications to the client
    pub logger: &'a McpLogger,
    /// Optional environment state (git status, filesystem, etc.)
    pub environment: Option<&'a dyn Environment>,
    /// Optional client requester for bidirectional communication
    client_requester: Option<ClientRequester>,
    /// URI parameters (extracted from resource URI template)
    uri_params: HashMap<String, String>,
}

impl<'a> Ctx<'a> {
    /// Create a new Ctx from an ExecutionContext
    pub fn from_execution_context(ctx: &'a ExecutionContext<'a>) -> Self {
        Self {
            session: ctx.session,
            logger: ctx.logger,
            environment: ctx.environment,
            client_requester: ctx.client_requester().cloned(),
            uri_params: ctx.uri_params.clone(),
        }
    }

    /// Create a new Ctx with explicit components
    pub fn new(
        session: &'a Session,
        logger: &'a McpLogger,
        environment: Option<&'a dyn Environment>,
        client_requester: Option<ClientRequester>,
    ) -> Self {
        Self {
            session,
            logger,
            environment,
            client_requester,
            uri_params: HashMap::new(),
        }
    }

    /// Create a new Ctx with URI parameters (for resource templates)
    pub fn with_uri_params(
        session: &'a Session,
        logger: &'a McpLogger,
        environment: Option<&'a dyn Environment>,
        client_requester: Option<ClientRequester>,
        uri_params: HashMap<String, String>,
    ) -> Self {
        Self {
            session,
            logger,
            environment,
            client_requester,
            uri_params,
        }
    }

    // ==================== Role Checks ====================

    /// Check if session has the "admin" role
    pub fn is_admin(&self) -> bool {
        self.has_role("admin")
    }

    /// Check if session has the "vip" role
    pub fn is_vip(&self) -> bool {
        self.has_role("vip")
    }

    /// Check if session has a specific role
    pub fn has_role(&self, role: &str) -> bool {
        self.session
            .get_state("roles")
            .and_then(|v| v.as_array().cloned())
            .map(|roles| roles.iter().any(|r| r.as_str() == Some(role)))
            .unwrap_or(false)
    }

    // ==================== Session State ====================

    /// Get typed state from session
    ///
    /// Deserializes the JSON value stored at `key` into type `T`.
    pub fn get_state<T: DeserializeOwned>(&self, key: &str) -> Option<T> {
        self.session
            .get_state(key)
            .and_then(|v| serde_json::from_value(v).ok())
    }

    /// Get session ID
    pub fn session_id(&self) -> &str {
        &self.session.id
    }

    // ==================== Client Info ====================

    /// Get client name (if available)
    pub fn client_name(&self) -> Option<&str> {
        self.session.client_info.as_ref().map(|c| c.name.as_str())
    }

    /// Get client version (if available)
    pub fn client_version(&self) -> Option<&str> {
        self.session
            .client_info
            .as_ref()
            .map(|c| c.version.as_str())
    }

    /// Get negotiated protocol version
    pub fn protocol_version(&self) -> Option<&str> {
        self.session.protocol_version.as_deref()
    }

    /// Get client capabilities
    pub fn client_capabilities(
        &self,
    ) -> Option<&crate::protocol::capabilities::ClientCapabilities> {
        self.session.capabilities.as_ref()
    }

    // ==================== Bidirectional Communication ====================

    /// Get client requester for server-to-client requests
    ///
    /// Returns `Some(&ClientRequester)` if bidirectional communication is available.
    /// Use this to call `roots/list`, `sampling/createMessage`, or `elicitation/create`.
    pub fn client_requester(&self) -> Option<&ClientRequester> {
        self.client_requester.as_ref()
    }

    /// Check if client supports roots capability
    pub fn supports_roots(&self) -> bool {
        self.client_requester
            .as_ref()
            .map(|r| r.supports_roots())
            .unwrap_or(false)
    }

    /// Check if client supports sampling capability
    pub fn supports_sampling(&self) -> bool {
        self.client_requester
            .as_ref()
            .map(|r| r.supports_sampling())
            .unwrap_or(false)
    }

    // ==================== Environment ====================

    /// Check if working directory has a git repository
    pub fn has_git_repo(&self) -> bool {
        self.environment.map(|e| e.has_git_repo()).unwrap_or(false)
    }

    /// Check if git repository is clean (no uncommitted changes)
    pub fn git_is_clean(&self) -> bool {
        self.environment.map(|e| e.git_is_clean()).unwrap_or(true)
    }

    /// Get number of commits ahead of tracking branch
    pub fn git_commits_ahead(&self) -> usize {
        self.environment.map(|e| e.git_commits_ahead()).unwrap_or(0)
    }

    // ==================== URI Parameters ====================

    /// Get a URI parameter by name (for resource templates)
    ///
    /// # Example
    ///
    /// ```rust,ignore
    /// // For template "file:///{path}" matched against "file:///src/main.rs"
    /// let path = ctx.get_uri_param("path"); // Some("src/main.rs")
    /// ```
    pub fn get_uri_param(&self, name: &str) -> Option<String> {
        self.uri_params.get(name).cloned()
    }

    /// Get all URI parameters
    pub fn uri_params(&self) -> &HashMap<String, String> {
        &self.uri_params
    }

    // ==================== Visibility Context ====================

    /// Convert to VisibilityContext for visibility checks
    pub fn as_visibility_context(&self) -> VisibilityContext<'a> {
        VisibilityContext {
            session: self.session,
            environment: self.environment,
        }
    }
}

impl<'a> FromExecutionContext<'a> for Ctx<'a> {
    fn from_execution_context(ctx: &'a ExecutionContext<'a>) -> Self {
        Ctx::from_execution_context(ctx)
    }
}

impl<'a> FromExecutionContext<'a> for &'a ExecutionContext<'a> {
    fn from_execution_context(ctx: &'a ExecutionContext<'a>) -> Self {
        ctx
    }
}

impl std::fmt::Debug for Ctx<'_> {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        f.debug_struct("Ctx")
            .field("session_id", &self.session.id)
            .field("has_environment", &self.environment.is_some())
            .field("has_client_requester", &self.client_requester.is_some())
            .finish()
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_ctx_debug() {
        // Basic sanity test - full tests require mock Session/Logger
        let debug_output = format!("{:?}", "Ctx test");
        assert!(debug_output.contains("Ctx"));
    }
}
