//! Query parameters
//!
//! This module provides the `Query` type for storing URL query parameters
//! parsed from the request URI. It mirrors the `Params` newtype so both
//! path parameters and query parameters have the same ergonomic surface and
//! the same compile-time type safety.
//!
//! # Examples
//!
//! ```rust,ignore
//! use ruxno::Query;
//!
//! let query = Query::from(vec![
//!     ("page".to_string(), "1".to_string()),
//!     ("limit".to_string(), "10".to_string()),
//! ]);
//!
//! assert_eq!(query.get("page"), Some("1"));
//! assert_eq!(query.len(), 2);
//!
//! for (key, value) in query.iter() {
//!     println!("{} = {}", key, value);
//! }
//! ```

use std::collections::HashMap;

/// URL query parameters parsed from the request URI.
///
/// Stores key-value pairs of query parameters. For example, the URI
/// `/users?page=1&limit=10` would produce `page=1` and `limit=10`.
///
/// This is the typed counterpart to `Params` (path parameters). Using
/// distinct types prevents accidentally passing a query map where a params
/// map is expected and vice versa.
///
/// # Examples
///
/// ```rust,ignore
/// use ruxno::Query;
///
/// let query = Query::from(vec![
///     ("page".to_string(), "1".to_string()),
///     ("limit".to_string(), "10".to_string()),
/// ]);
///
/// assert_eq!(query.get("page"), Some("1"));
/// assert_eq!(query.len(), 2);
/// ```
#[derive(Debug, Clone, Default, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub struct Query {
    inner: HashMap<String, String>,
}

impl Query {
    /// Create new empty query parameters.
    pub fn new() -> Self {
        Self {
            inner: HashMap::new(),
        }
    }

    /// Get a query parameter value by key.
    ///
    /// Returns `None` if the key doesn't exist.
    ///
    /// # Examples
    ///
    /// ```rust,ignore
    /// let query = Query::from(vec![("page".to_string(), "1".to_string())]);
    /// assert_eq!(query.get("page"), Some("1"));
    /// assert_eq!(query.get("limit"), None);
    /// ```
    pub fn get(&self, key: &str) -> Option<&str> {
        self.inner.get(key).map(|s| s.as_str())
    }

    /// Insert a query parameter.
    ///
    /// If the key already exists, the value is updated.
    pub fn insert(&mut self, key: String, value: String) {
        self.inner.insert(key, value);
    }

    /// Check if a query parameter exists.
    pub fn contains_key(&self, key: &str) -> bool {
        self.inner.contains_key(key)
    }

    /// Number of query parameters.
    pub fn len(&self) -> usize {
        self.inner.len()
    }

    /// Returns `true` if there are no query parameters.
    pub fn is_empty(&self) -> bool {
        self.inner.is_empty()
    }

    /// Iterate over `(&String, &String)` pairs.
    pub fn iter(&self) -> impl Iterator<Item = (&String, &String)> {
        self.inner.iter()
    }

    /// Iterate over keys.
    pub fn keys(&self) -> impl Iterator<Item = &String> {
        self.inner.keys()
    }

    /// Iterate over values.
    pub fn values(&self) -> impl Iterator<Item = &String> {
        self.inner.values()
    }

    /// Consume and return the inner `HashMap`.
    pub fn into_map(self) -> HashMap<String, String> {
        self.inner
    }

    /// Deserialize query parameters into a typed struct.
    ///
    /// This uses serde_urlencoded to parse the query parameters into
    /// a struct that implements `Deserialize`.
    ///
    /// # Examples
    ///
    /// ```rust,ignore
    /// #[derive(Deserialize)]
    /// struct Pagination {
    ///     page: Option<u32>,
    ///     page_size: Option<u32>,
    /// }
    ///
    /// let query = Query::from(vec![
    ///     ("page".to_string(), "1".to_string()),
    ///     ("page_size".to_string(), "20".to_string()),
    /// ]);
    ///
    /// let pagination: Pagination = query.parse()?;
    /// assert_eq!(pagination.page, Some(1));
    /// ```
    pub fn parse<T>(&self) -> Result<T, serde_urlencoded::de::Error>
    where
        T: serde::de::DeserializeOwned,
    {
        // Convert query parameters to URL-encoded string and deserialize
        serde_urlencoded::from_str(&self.to_query_string())
    }

    /// Convert query parameters to URL query string format.
    ///
    /// Uses proper percent-encoding for keys and values.
    ///
    /// # Examples
    ///
    /// ```rust,ignore
    /// let query = Query::from(vec![
    ///     ("page".to_string(), "1".to_string()),
    ///     ("limit".to_string(), "10".to_string()),
    /// ]);
    /// let qs = query.to_query_string();
    /// // Result: "page=1&limit=10" (order may vary)
    /// ```
    pub fn to_query_string(&self) -> String {
        serde_urlencoded::to_string(&self.inner)
            .expect("Failed to serialize query parameters to URL-encoded string")
    }
}

// ── Conversion traits ────────────────────────────────────────────────────────

impl From<HashMap<String, String>> for Query {
    fn from(map: HashMap<String, String>) -> Self {
        Self { inner: map }
    }
}

impl From<Vec<(String, String)>> for Query {
    fn from(vec: Vec<(String, String)>) -> Self {
        Self {
            inner: vec.into_iter().collect(),
        }
    }
}

impl From<Query> for HashMap<String, String> {
    fn from(q: Query) -> Self {
        q.inner
    }
}

impl FromIterator<(String, String)> for Query {
    fn from_iter<I: IntoIterator<Item = (String, String)>>(iter: I) -> Self {
        Self {
            inner: iter.into_iter().collect(),
        }
    }
}

impl IntoIterator for Query {
    type Item = (String, String);
    type IntoIter = std::collections::hash_map::IntoIter<String, String>;

    fn into_iter(self) -> Self::IntoIter {
        self.inner.into_iter()
    }
}

impl<'a> IntoIterator for &'a Query {
    type Item = (&'a String, &'a String);
    type IntoIter = std::collections::hash_map::Iter<'a, String, String>;

    fn into_iter(self) -> Self::IntoIter {
        self.inner.iter()
    }
}

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

    #[test]
    fn test_query_new() {
        let q = Query::new();
        assert!(q.is_empty());
        assert_eq!(q.len(), 0);
    }

    #[test]
    fn test_query_insert_and_get() {
        let mut q = Query::new();
        q.insert("page".to_string(), "1".to_string());
        q.insert("limit".to_string(), "10".to_string());

        assert_eq!(q.get("page"), Some("1"));
        assert_eq!(q.get("limit"), Some("10"));
        assert_eq!(q.get("sort"), None);
    }

    #[test]
    fn test_query_insert_overwrites() {
        let mut q = Query::new();
        q.insert("page".to_string(), "1".to_string());
        q.insert("page".to_string(), "2".to_string());

        assert_eq!(q.get("page"), Some("2"));
        assert_eq!(q.len(), 1);
    }

    #[test]
    fn test_query_contains_key() {
        let q = Query::from(vec![("page".to_string(), "1".to_string())]);
        assert!(q.contains_key("page"));
        assert!(!q.contains_key("limit"));
    }

    #[test]
    fn test_query_from_hashmap() {
        let mut map = HashMap::new();
        map.insert("page".to_string(), "1".to_string());
        let q = Query::from(map);
        assert_eq!(q.get("page"), Some("1"));
    }

    #[test]
    fn test_query_from_vec() {
        let q = Query::from(vec![
            ("page".to_string(), "1".to_string()),
            ("limit".to_string(), "10".to_string()),
        ]);
        assert_eq!(q.get("page"), Some("1"));
        assert_eq!(q.get("limit"), Some("10"));
        assert_eq!(q.len(), 2);
    }

    #[test]
    fn test_query_into_map() {
        let q = Query::from(vec![("page".to_string(), "1".to_string())]);
        let map: HashMap<String, String> = q.into_map();
        assert_eq!(map.get("page"), Some(&"1".to_string()));
    }

    #[test]
    fn test_query_from_into_hashmap() {
        let q = Query::from(vec![("page".to_string(), "1".to_string())]);
        let map: HashMap<String, String> = q.into();
        assert_eq!(map.get("page"), Some(&"1".to_string()));
    }

    #[test]
    fn test_query_iter() {
        let q = Query::from(vec![
            ("page".to_string(), "1".to_string()),
            ("limit".to_string(), "10".to_string()),
        ]);
        let count = q.iter().count();
        assert_eq!(count, 2);
    }

    #[test]
    fn test_query_into_iter() {
        let q = Query::from(vec![
            ("page".to_string(), "1".to_string()),
            ("limit".to_string(), "10".to_string()),
        ]);
        let count = q.into_iter().count();
        assert_eq!(count, 2);
    }

    #[test]
    fn test_query_ref_into_iter() {
        let q = Query::from(vec![("page".to_string(), "1".to_string())]);
        let count = (&q).into_iter().count();
        assert_eq!(count, 1);
        // q still usable
        assert_eq!(q.len(), 1);
    }

    #[test]
    fn test_query_collect() {
        let pairs = vec![
            ("page".to_string(), "1".to_string()),
            ("limit".to_string(), "10".to_string()),
        ];
        let q: Query = pairs.into_iter().collect();
        assert_eq!(q.len(), 2);
    }

    #[test]
    fn test_query_clone() {
        let q1 = Query::from(vec![("page".to_string(), "1".to_string())]);
        let q2 = q1.clone();
        assert_eq!(q1, q2);
    }

    #[test]
    fn test_query_default() {
        let q = Query::default();
        assert!(q.is_empty());
    }

    #[test]
    fn test_query_equality() {
        let q1 = Query::from(vec![
            ("page".to_string(), "1".to_string()),
            ("limit".to_string(), "10".to_string()),
        ]);
        let q2 = Query::from(vec![
            ("limit".to_string(), "10".to_string()),
            ("page".to_string(), "1".to_string()),
        ]);
        assert_eq!(q1, q2);
    }

    #[test]
    fn test_query_to_string_encoding() {
        let q = Query::from(vec![
            ("search".to_string(), "hello world".to_string()),
            ("filter".to_string(), "a&b=c".to_string()),
        ]);
        let qs = q.to_query_string();

        // Should be percent-encoded
        assert!(qs.contains("hello+world") || qs.contains("hello%20world"));
        assert!(qs.contains("%26") || qs.contains("&amp;")); // & encoded
        assert!(qs.contains("%3D")); // = encoded
    }

    #[test]
    fn test_query_parse_roundtrip() {
        let original = Query::from(vec![
            ("page".to_string(), "1".to_string()),
            ("search".to_string(), "hello world".to_string()),
            ("special".to_string(), "a&b=c".to_string()),
        ]);

        let qs = original.to_query_string();

        // Parse back using serde_urlencoded directly to HashMap
        let parsed: std::collections::HashMap<String, String> =
            serde_urlencoded::from_str(&qs).unwrap();

        // Values should match after round-trip
        assert_eq!(parsed.get("page"), Some(&"1".to_string()));
        assert_eq!(parsed.get("search"), Some(&"hello world".to_string()));
        assert_eq!(parsed.get("special"), Some(&"a&b=c".to_string()));
    }
}
