//! Completion system for argument value suggestions
//!
//! Provides `completion/complete` handler for autocomplete suggestions on
//! prompt arguments and resource URIs.
//!
//! # MCP Spec
//!
//! The completion system allows clients to request argument value completions.
//! Servers can provide suggestions based on:
//! - Prompt argument names and current partial values
//! - Resource URI patterns and partial paths
//!
//! # Example
//!
//! ```rust,ignore
//! use mcp_host::managers::completion::{CompletionManager, CompletionProvider};
//! use mcp_host::protocol::types::{CompleteRequest, CompleteResult, CompletionValue};
//!
//! struct MyCompletionProvider;
//!
//! #[async_trait]
//! impl CompletionProvider for MyCompletionProvider {
//!     async fn complete_prompt(
//!         &self,
//!         prompt_name: &str,
//!         argument_name: &str,
//!         argument_value: &str,
//!     ) -> Vec<CompletionValue> {
//!         if prompt_name == "greet" && argument_name == "name" {
//!             vec![
//!                 CompletionValue::new("Alice"),
//!                 CompletionValue::new("Bob"),
//!             ]
//!             .into_iter()
//!             .filter(|v| v.value.starts_with(argument_value))
//!             .collect()
//!         } else {
//!             vec![]
//!         }
//!     }
//! }
//! ```

use std::sync::Arc;

use async_trait::async_trait;
use dashmap::DashMap;
use thiserror::Error;

use crate::protocol::types::{
    CompleteRequest, CompleteResult, CompletionInfo, CompletionRef, CompletionValue,
};

/// Completion errors
#[derive(Debug, Error)]
pub enum CompletionError {
    /// Invalid completion reference
    #[error("Invalid reference: {0}")]
    InvalidReference(String),

    /// Provider not found for the given reference
    #[error("No completion provider for: {0}")]
    NoProvider(String),

    /// Internal error during completion
    #[error("Completion error: {0}")]
    Internal(String),
}

/// Maximum number of completion values to return by default
pub const DEFAULT_MAX_COMPLETIONS: usize = 100;

/// Trait for providing completion suggestions
///
/// Implement this trait to provide custom completion logic for prompts and resources.
#[async_trait]
pub trait CompletionProvider: Send + Sync {
    /// Provide completions for a prompt argument
    ///
    /// # Arguments
    ///
    /// * `prompt_name` - Name of the prompt being completed
    /// * `argument_name` - Name of the argument being completed
    /// * `argument_value` - Current partial value of the argument
    ///
    /// # Returns
    ///
    /// List of completion suggestions matching the partial value
    async fn complete_prompt(
        &self,
        prompt_name: &str,
        argument_name: &str,
        argument_value: &str,
    ) -> Vec<CompletionValue> {
        let _ = (prompt_name, argument_name, argument_value);
        vec![]
    }

    /// Provide completions for a resource URI argument
    ///
    /// # Arguments
    ///
    /// * `uri` - URI pattern of the resource
    /// * `argument_name` - Name of the argument being completed
    /// * `argument_value` - Current partial value of the argument
    ///
    /// # Returns
    ///
    /// List of completion suggestions matching the partial value
    async fn complete_resource(
        &self,
        uri: &str,
        argument_name: &str,
        argument_value: &str,
    ) -> Vec<CompletionValue> {
        let _ = (uri, argument_name, argument_value);
        vec![]
    }

    /// Maximum completions to return (default: 100)
    fn max_completions(&self) -> usize {
        DEFAULT_MAX_COMPLETIONS
    }
}

/// Default completion provider that returns no suggestions
pub struct NoOpCompletionProvider;

#[async_trait]
impl CompletionProvider for NoOpCompletionProvider {}

/// Completion manager for handling completion requests
///
/// The manager routes completion requests to registered providers based on
/// the reference type (prompt or resource).
#[derive(Clone)]
pub struct CompletionManager {
    /// Prompt completion providers by prompt name
    prompt_providers: Arc<DashMap<String, Arc<dyn CompletionProvider>>>,
    /// Resource completion providers by URI pattern
    resource_providers: Arc<DashMap<String, Arc<dyn CompletionProvider>>>,
    /// Default provider for unregistered prompts/resources
    default_provider: Arc<dyn CompletionProvider>,
    /// Maximum completions to return
    max_completions: usize,
}

impl Default for CompletionManager {
    fn default() -> Self {
        Self::new()
    }
}

impl CompletionManager {
    /// Create a new completion manager
    pub fn new() -> Self {
        Self {
            prompt_providers: Arc::new(DashMap::new()),
            resource_providers: Arc::new(DashMap::new()),
            default_provider: Arc::new(NoOpCompletionProvider),
            max_completions: DEFAULT_MAX_COMPLETIONS,
        }
    }

    /// Create a completion manager with a default provider
    pub fn with_default_provider(provider: Arc<dyn CompletionProvider>) -> Self {
        Self {
            prompt_providers: Arc::new(DashMap::new()),
            resource_providers: Arc::new(DashMap::new()),
            default_provider: provider,
            max_completions: DEFAULT_MAX_COMPLETIONS,
        }
    }

    /// Set maximum completions to return
    pub fn set_max_completions(&mut self, max: usize) {
        self.max_completions = max;
    }

    /// Register a completion provider for a specific prompt
    pub fn register_prompt_provider(
        &self,
        prompt_name: impl Into<String>,
        provider: Arc<dyn CompletionProvider>,
    ) {
        self.prompt_providers.insert(prompt_name.into(), provider);
    }

    /// Register a completion provider for a specific resource URI pattern
    pub fn register_resource_provider(
        &self,
        uri_pattern: impl Into<String>,
        provider: Arc<dyn CompletionProvider>,
    ) {
        self.resource_providers.insert(uri_pattern.into(), provider);
    }

    /// Set the default provider for unregistered prompts/resources
    pub fn set_default_provider(&mut self, provider: Arc<dyn CompletionProvider>) {
        self.default_provider = provider;
    }

    /// Handle a completion request
    pub async fn complete(&self, request: CompleteRequest) -> Result<CompleteResult, CompletionError> {
        let values = match &request.reference {
            CompletionRef::Prompt { name } => {
                let provider = self
                    .prompt_providers
                    .get(name)
                    .map(|p| Arc::clone(&p))
                    .unwrap_or_else(|| Arc::clone(&self.default_provider));

                provider
                    .complete_prompt(name, &request.argument.name, &request.argument.value)
                    .await
            }
            CompletionRef::Resource { uri } => {
                // Try exact match first, then pattern matching
                let provider = self
                    .resource_providers
                    .get(uri)
                    .map(|p| Arc::clone(&p))
                    .or_else(|| self.find_matching_resource_provider(uri))
                    .unwrap_or_else(|| Arc::clone(&self.default_provider));

                provider
                    .complete_resource(uri, &request.argument.name, &request.argument.value)
                    .await
            }
        };

        // Apply max completions limit
        let total = values.len();
        let max = self.max_completions;
        let has_more = total > max;
        let values: Vec<_> = values.into_iter().take(max).collect();

        let completion = if has_more {
            CompletionInfo::with_pagination(values, total, true)
        } else {
            CompletionInfo::with_values(values)
        };

        Ok(CompleteResult { completion })
    }

    /// Find a resource provider by pattern matching
    fn find_matching_resource_provider(&self, uri: &str) -> Option<Arc<dyn CompletionProvider>> {
        // Simple prefix matching for now
        // Could be extended to support URI templates like "file:///{path}"
        for entry in self.resource_providers.iter() {
            let pattern = entry.key();
            if uri.starts_with(pattern) || pattern.starts_with(uri) {
                return Some(Arc::clone(entry.value()));
            }

            // Check for template pattern matching
            if let Some(provider) = self.match_template_pattern(pattern, uri, entry.value()) {
                return Some(provider);
            }
        }
        None
    }

    /// Match a URI against a template pattern
    fn match_template_pattern(
        &self,
        pattern: &str,
        uri: &str,
        provider: &Arc<dyn CompletionProvider>,
    ) -> Option<Arc<dyn CompletionProvider>> {
        // Extract scheme and path prefix before any template variables
        let pattern_prefix = pattern.split('{').next()?;
        if uri.starts_with(pattern_prefix) {
            return Some(Arc::clone(provider));
        }
        None
    }
}

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

    /// Test provider that returns predefined completions
    struct TestCompletionProvider {
        prompt_completions: Vec<CompletionValue>,
        resource_completions: Vec<CompletionValue>,
    }

    impl TestCompletionProvider {
        fn new(prompt: Vec<&str>, resource: Vec<&str>) -> Self {
            Self {
                prompt_completions: prompt.into_iter().map(CompletionValue::new).collect(),
                resource_completions: resource.into_iter().map(CompletionValue::new).collect(),
            }
        }
    }

    #[async_trait]
    impl CompletionProvider for TestCompletionProvider {
        async fn complete_prompt(
            &self,
            _prompt_name: &str,
            _argument_name: &str,
            argument_value: &str,
        ) -> Vec<CompletionValue> {
            self.prompt_completions
                .iter()
                .filter(|v| v.value.starts_with(argument_value))
                .cloned()
                .collect()
        }

        async fn complete_resource(
            &self,
            _uri: &str,
            _argument_name: &str,
            argument_value: &str,
        ) -> Vec<CompletionValue> {
            self.resource_completions
                .iter()
                .filter(|v| v.value.starts_with(argument_value))
                .cloned()
                .collect()
        }
    }

    #[tokio::test]
    async fn test_completion_manager_new() {
        let manager = CompletionManager::new();
        assert_eq!(manager.max_completions, DEFAULT_MAX_COMPLETIONS);
    }

    #[tokio::test]
    async fn test_prompt_completion() {
        let manager = CompletionManager::new();
        let provider = Arc::new(TestCompletionProvider::new(
            vec!["Alice", "Amy", "Bob"],
            vec![],
        ));
        manager.register_prompt_provider("greet", provider);

        let request = CompleteRequest {
            reference: CompletionRef::Prompt {
                name: "greet".to_string(),
            },
            argument: crate::protocol::types::CompletionArgument {
                name: "name".to_string(),
                value: "A".to_string(),
            },
        };

        let result = manager.complete(request).await.unwrap();
        assert_eq!(result.completion.values.len(), 2); // Alice, Amy
        assert!(result.completion.values.iter().all(|v| v.value.starts_with("A")));
    }

    #[tokio::test]
    async fn test_resource_completion() {
        let manager = CompletionManager::new();
        let provider = Arc::new(TestCompletionProvider::new(
            vec![],
            vec!["src/main.rs", "src/lib.rs", "tests/test.rs"],
        ));
        manager.register_resource_provider("file:///", provider);

        let request = CompleteRequest {
            reference: CompletionRef::Resource {
                uri: "file:///src".to_string(),
            },
            argument: crate::protocol::types::CompletionArgument {
                name: "path".to_string(),
                value: "src/".to_string(),
            },
        };

        let result = manager.complete(request).await.unwrap();
        assert_eq!(result.completion.values.len(), 2); // src/main.rs, src/lib.rs
    }

    #[tokio::test]
    async fn test_default_provider() {
        let default = Arc::new(TestCompletionProvider::new(
            vec!["default1", "default2"],
            vec![],
        ));
        let manager = CompletionManager::with_default_provider(default);

        // Request for unregistered prompt uses default provider
        let request = CompleteRequest {
            reference: CompletionRef::Prompt {
                name: "unknown_prompt".to_string(),
            },
            argument: crate::protocol::types::CompletionArgument {
                name: "arg".to_string(),
                value: "default".to_string(),
            },
        };

        let result = manager.complete(request).await.unwrap();
        assert_eq!(result.completion.values.len(), 2);
    }

    #[tokio::test]
    async fn test_max_completions_limit() {
        let mut manager = CompletionManager::new();
        manager.set_max_completions(2);

        let provider = Arc::new(TestCompletionProvider::new(
            vec!["a1", "a2", "a3", "a4", "a5"],
            vec![],
        ));
        manager.register_prompt_provider("test", provider);

        let request = CompleteRequest {
            reference: CompletionRef::Prompt {
                name: "test".to_string(),
            },
            argument: crate::protocol::types::CompletionArgument {
                name: "arg".to_string(),
                value: "a".to_string(),
            },
        };

        let result = manager.complete(request).await.unwrap();
        assert_eq!(result.completion.values.len(), 2);
        assert_eq!(result.completion.total, Some(5));
        assert_eq!(result.completion.has_more, Some(true));
    }

    #[tokio::test]
    async fn test_empty_completions() {
        let manager = CompletionManager::new();

        let request = CompleteRequest {
            reference: CompletionRef::Prompt {
                name: "nonexistent".to_string(),
            },
            argument: crate::protocol::types::CompletionArgument {
                name: "arg".to_string(),
                value: "x".to_string(),
            },
        };

        let result = manager.complete(request).await.unwrap();
        assert!(result.completion.values.is_empty());
    }

    #[tokio::test]
    async fn test_completion_value_constructors() {
        let simple = CompletionValue::new("value");
        assert_eq!(simple.value, "value");
        assert!(simple.label.is_none());
        assert!(simple.description.is_none());

        let with_label = CompletionValue::with_label("value", "Label");
        assert_eq!(with_label.value, "value");
        assert_eq!(with_label.label, Some("Label".to_string()));
        assert!(with_label.description.is_none());

        let with_desc = CompletionValue::with_description("value", "Description");
        assert_eq!(with_desc.value, "value");
        assert!(with_desc.label.is_none());
        assert_eq!(with_desc.description, Some("Description".to_string()));

        let full = CompletionValue::full("value", "Label", "Description");
        assert_eq!(full.value, "value");
        assert_eq!(full.label, Some("Label".to_string()));
        assert_eq!(full.description, Some("Description".to_string()));
    }

    #[tokio::test]
    async fn test_completion_info_constructors() {
        let empty = CompletionInfo::empty();
        assert!(empty.values.is_empty());
        assert!(empty.total.is_none());
        assert!(empty.has_more.is_none());

        let with_values = CompletionInfo::with_values(vec![
            CompletionValue::new("a"),
            CompletionValue::new("b"),
        ]);
        assert_eq!(with_values.values.len(), 2);
        assert!(with_values.total.is_none());
        assert!(with_values.has_more.is_none());

        let paginated = CompletionInfo::with_pagination(
            vec![CompletionValue::new("a")],
            10,
            true,
        );
        assert_eq!(paginated.values.len(), 1);
        assert_eq!(paginated.total, Some(10));
        assert_eq!(paginated.has_more, Some(true));
    }
}
