use super::{ActionApiContinuable, ActionApiData, ActionApiRunnable};
use crate::api::NamespaceID;
use std::collections::HashMap;

/// Internal data container for `list=logevents` parameters.
#[derive(Debug, Clone)]
pub struct ActionApiListLogeventsData {
    leprop: Option<Vec<String>>,
    letype: Option<String>,
    leaction: Option<String>,
    lestart: Option<String>,
    leend: Option<String>,
    ledir: Option<String>,
    leuser: Option<String>,
    letitle: Option<String>,
    lenamespace: Option<NamespaceID>,
    letag: Option<String>,
    lelimit: usize,
    lecontinue: Option<String>,
}

impl ActionApiData for ActionApiListLogeventsData {}

impl Default for ActionApiListLogeventsData {
    fn default() -> Self {
        Self {
            leprop: None,
            letype: None,
            leaction: None,
            lestart: None,
            leend: None,
            ledir: None,
            leuser: None,
            letitle: None,
            lenamespace: None,
            letag: None,
            lelimit: 10,
            lecontinue: None,
        }
    }
}

impl ActionApiListLogeventsData {
    pub(crate) fn params(&self) -> HashMap<String, String> {
        let mut params = HashMap::new();
        Self::add_vec(&self.leprop, "leprop", &mut params);
        Self::add_str(&self.letype, "letype", &mut params);
        Self::add_str(&self.leaction, "leaction", &mut params);
        Self::add_str(&self.lestart, "lestart", &mut params);
        Self::add_str(&self.leend, "leend", &mut params);
        Self::add_str(&self.ledir, "ledir", &mut params);
        Self::add_str(&self.leuser, "leuser", &mut params);
        Self::add_str(&self.letitle, "letitle", &mut params);
        if let Some(ns) = self.lenamespace {
            params.insert("lenamespace".to_string(), ns.to_string());
        }
        Self::add_str(&self.letag, "letag", &mut params);
        params.insert("lelimit".to_string(), self.lelimit.to_string());
        Self::add_str(&self.lecontinue, "lecontinue", &mut params);
        params
    }
}

/// Builder for the `list=logevents` API module; supports pagination via `ActionApiContinuable`.
#[derive(Debug, Clone)]
pub struct ActionApiListLogeventsBuilder {
    pub(crate) data: ActionApiListLogeventsData,
    pub(crate) continue_params: HashMap<String, String>,
}

impl ActionApiListLogeventsBuilder {
    /// Creates a new builder with default values.
    pub fn new() -> Self {
        Self {
            data: ActionApiListLogeventsData::default(),
            continue_params: HashMap::new(),
        }
    }

    /// Properties to retrieve for each log event (`leprop`).
    pub fn leprop<S: Into<String> + Clone>(mut self, leprop: &[S]) -> Self {
        self.data.leprop = Some(leprop.iter().map(|s| s.clone().into()).collect());
        self
    }

    /// Filter log events to this log type (`letype`).
    pub fn letype<S: AsRef<str>>(mut self, letype: S) -> Self {
        self.data.letype = Some(letype.as_ref().to_string());
        self
    }

    /// Filter log events to this specific action (`leaction`).
    pub fn leaction<S: AsRef<str>>(mut self, leaction: S) -> Self {
        self.data.leaction = Some(leaction.as_ref().to_string());
        self
    }

    /// Timestamp to start enumerating log events from (`lestart`).
    pub fn lestart<S: AsRef<str>>(mut self, lestart: S) -> Self {
        self.data.lestart = Some(lestart.as_ref().to_string());
        self
    }

    /// Timestamp to stop enumerating log events at (`leend`).
    pub fn leend<S: AsRef<str>>(mut self, leend: S) -> Self {
        self.data.leend = Some(leend.as_ref().to_string());
        self
    }

    /// Enumeration direction (`newer` or `older`) (`ledir`).
    pub fn ledir<S: AsRef<str>>(mut self, ledir: S) -> Self {
        self.data.ledir = Some(ledir.as_ref().to_string());
        self
    }

    /// Filter log events to those performed by this user (`leuser`).
    pub fn leuser<S: AsRef<str>>(mut self, leuser: S) -> Self {
        self.data.leuser = Some(leuser.as_ref().to_string());
        self
    }

    /// Filter log events to those affecting this page title (`letitle`).
    pub fn letitle<S: AsRef<str>>(mut self, letitle: S) -> Self {
        self.data.letitle = Some(letitle.as_ref().to_string());
        self
    }

    /// Filter log events to those affecting pages in this namespace (`lenamespace`).
    pub fn lenamespace(mut self, lenamespace: NamespaceID) -> Self {
        self.data.lenamespace = Some(lenamespace);
        self
    }

    /// Filter log events to those tagged with this tag (`letag`).
    pub fn letag<S: AsRef<str>>(mut self, letag: S) -> Self {
        self.data.letag = Some(letag.as_ref().to_string());
        self
    }

    /// Maximum number of log events to return (`lelimit`).
    pub fn lelimit(mut self, lelimit: usize) -> Self {
        self.data.lelimit = lelimit;
        self
    }
}

impl ActionApiRunnable for ActionApiListLogeventsBuilder {
    fn params(&self) -> HashMap<String, String> {
        let mut ret = self.data.params();
        ret.insert("action".to_string(), "query".to_string());
        ret.insert("list".to_string(), "logevents".to_string());
        ret.extend(self.continue_params.clone());
        ret
    }
}

impl ActionApiContinuable for ActionApiListLogeventsBuilder {
    fn continue_params_mut(&mut self) -> &mut HashMap<String, String> {
        &mut self.continue_params
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::{Api, action_api::ActionApiList};

    fn new_builder() -> ActionApiListLogeventsBuilder {
        ActionApiListLogeventsBuilder::new()
    }

    #[test]
    fn default_lelimit_is_10() {
        let params = new_builder().data.params();
        assert_eq!(params["lelimit"], "10");
    }

    #[test]
    fn default_letype_absent() {
        let params = new_builder().data.params();
        assert!(!params.contains_key("letype"));
    }

    #[test]
    fn leprop_set() {
        let params = new_builder()
            .leprop(&["ids", "title", "type", "user"])
            .data
            .params();
        assert_eq!(params["leprop"], "ids|title|type|user");
    }

    #[test]
    fn letype_set() {
        let params = new_builder().letype("move").data.params();
        assert_eq!(params["letype"], "move");
    }

    #[test]
    fn leaction_set() {
        let params = new_builder().leaction("move/move").data.params();
        assert_eq!(params["leaction"], "move/move");
    }

    #[test]
    fn leuser_set() {
        let params = new_builder().leuser("ExampleUser").data.params();
        assert_eq!(params["leuser"], "ExampleUser");
    }

    #[test]
    fn letitle_set() {
        let params = new_builder().letitle("Albert Einstein").data.params();
        assert_eq!(params["letitle"], "Albert Einstein");
    }

    #[test]
    fn lenamespace_set() {
        let params = new_builder().lenamespace(0).data.params();
        assert_eq!(params["lenamespace"], "0");
    }

    #[test]
    fn lelimit_set() {
        let params = new_builder().lelimit(50).data.params();
        assert_eq!(params["lelimit"], "50");
    }

    #[test]
    fn ledir_newer() {
        let params = new_builder().ledir("newer").data.params();
        assert_eq!(params["ledir"], "newer");
    }

    #[test]
    fn runnable_params_contain_action_list() {
        let params = ActionApiRunnable::params(&new_builder());
        assert_eq!(params["action"], "query");
        assert_eq!(params["list"], "logevents");
    }

    #[tokio::test]
    async fn test_logevents() {
        use wiremock::matchers::query_param;
        use wiremock::{Mock, ResponseTemplate};
        let server = crate::test_helpers::test_helpers_mod::start_enwiki_mock().await;
        Mock::given(query_param("list", "logevents"))
            .respond_with(ResponseTemplate::new(200).set_body_json(json!({
                "batchcomplete": "",
                "query": {
                    "logevents": [
                        {"logid": 1, "ns": 0, "title": "Page A", "type": "move", "action": "move",
                         "user": "Editor1", "timestamp": "2024-01-01T00:00:00Z"},
                        {"logid": 2, "ns": 0, "title": "Page B", "type": "move", "action": "move",
                         "user": "Editor2", "timestamp": "2024-01-02T00:00:00Z"}
                    ]
                }
            })))
            .mount(&server)
            .await;
        let api = Api::new(&server.uri()).await.unwrap();
        let result = ActionApiList::logevents()
            .letype("move")
            .lelimit(5)
            .run(&api)
            .await
            .unwrap();
        assert!(result["query"]["logevents"].is_array());
    }
}
