use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::fs;
use std::path::Path;

/// YAML structure for loading actions
#[derive(Debug, Deserialize)]
struct ActionsYaml {
    actions: HashMap<String, ActionYaml>,
}

#[derive(Debug, Deserialize)]
struct ActionYaml {
    name: String,
    description: String,
    selectors: Vec<String>,
}

/// Represents an action that can be performed on Wasot content
/// matching specific selectors
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Action {
    /// Unique identifier for the action
    pub id: String,

    /// The name of the action
    pub name: String,

    /// A description of what the action does
    pub description: String,

    /// Selectors that define when this action is available
    /// Uses CSS-like selector syntax as defined in docs/selectors.md
    pub selectors: Vec<String>,
}

impl Action {
    /// Creates a new Action
    pub fn new(id: String, name: String, description: String, selectors: Vec<String>) -> Self {
        Self {
            id,
            name,
            description,
            selectors,
        }
    }

    /// Creates a new Action with a single selector
    pub fn with_selector(id: String, name: String, description: String, selector: String) -> Self {
        Self {
            id,
            name,
            description,
            selectors: vec![selector],
        }
    }

    /// Adds a selector to this action
    pub fn add_selector(&mut self, selector: String) {
        self.selectors.push(selector);
    }

    /// Load actions from a YAML file
    pub fn load_from_yaml<P: AsRef<Path>>(
        path: P,
    ) -> Result<Vec<Action>, Box<dyn std::error::Error>> {
        let contents = fs::read_to_string(path)?;
        let yaml: ActionsYaml = serde_yaml::from_str(&contents)?;

        let mut actions = Vec::new();
        for (id, action_yaml) in yaml.actions {
            actions.push(Action {
                id,
                name: action_yaml.name,
                description: action_yaml.description,
                selectors: action_yaml.selectors,
            });
        }

        Ok(actions)
    }

    /// Load actions from default location (actions.yaml in current directory)
    /// or from a provided path
    pub fn load_actions(
        custom_path: Option<&str>,
    ) -> Result<Vec<Action>, Box<dyn std::error::Error>> {
        let path = custom_path.unwrap_or("actions.yaml");

        // Check if file exists
        if !Path::new(path).exists() {
            // If default path doesn't exist and no custom path was provided,
            // return empty vector (no actions available)
            if custom_path.is_none() {
                return Ok(Vec::new());
            }
            // If custom path was provided but doesn't exist, return error
            return Err(format!("Actions file not found: {path}").into());
        }

        Self::load_from_yaml(path)
    }
}

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

    #[test]
    fn test_create_action() {
        let action = Action::new(
            "format-code".to_string(),
            "Format Code".to_string(),
            "Formats code in the current block".to_string(),
            vec!["code".to_string(), "codeblock".to_string()],
        );

        assert_eq!(action.id, "format-code");
        assert_eq!(action.name, "Format Code");
        assert_eq!(action.description, "Formats code in the current block");
        assert_eq!(action.selectors.len(), 2);
    }

    #[test]
    fn test_create_action_with_single_selector() {
        let action = Action::with_selector(
            "wrap-quote".to_string(),
            "Wrap Quote".to_string(),
            "Wraps the current paragraph in a quote block".to_string(),
            "paragraph".to_string(),
        );

        assert_eq!(action.id, "wrap-quote");
        assert_eq!(action.name, "Wrap Quote");
        assert_eq!(action.selectors.len(), 1);
        assert_eq!(action.selectors[0], "paragraph");
    }

    #[test]
    fn test_add_selector() {
        let mut action = Action::with_selector(
            "insert-link".to_string(),
            "Insert Link".to_string(),
            "Inserts a link at the cursor position".to_string(),
            "paragraph".to_string(),
        );

        action.add_selector("quote".to_string());
        action.add_selector("list".to_string());

        assert_eq!(action.selectors.len(), 3);
        assert_eq!(action.selectors[1], "quote");
        assert_eq!(action.selectors[2], "list");
    }

    #[test]
    fn test_action_new_constructor() {
        // Test line 39 in function `new`
        let action = Action::new(
            "test-id".to_string(),
            "Test Name".to_string(),
            "Test Description".to_string(),
            vec!["selector1".to_string(), "selector2".to_string()],
        );

        assert_eq!(action.id, "test-id");
        assert_eq!(action.name, "Test Name");
        assert_eq!(action.description, "Test Description");
        assert_eq!(action.selectors.len(), 2);
        assert_eq!(action.selectors[0], "selector1");
        assert_eq!(action.selectors[1], "selector2");
    }

    #[test]
    fn test_action_with_selector_constructor() {
        // Test line 49 and 54 in function `with_selector`
        let action = Action::with_selector(
            "single-sel".to_string(),
            "Single Selector".to_string(),
            "Action with single selector".to_string(),
            "test-selector".to_string(),
        );

        assert_eq!(action.id, "single-sel");
        assert_eq!(action.name, "Single Selector");
        assert_eq!(action.description, "Action with single selector");
        assert_eq!(action.selectors.len(), 1);
        assert_eq!(action.selectors[0], "test-selector");
    }

    #[test]
    fn test_add_selector_functionality() {
        let mut action = Action::new(
            "test".to_string(),
            "Test".to_string(),
            "Test".to_string(),
            vec![],
        );

        // Test lines 59-60 in function `add_selector`
        action.add_selector("new-selector".to_string());

        assert_eq!(action.selectors.len(), 1);
        assert_eq!(action.selectors[0], "new-selector");

        // Add another to test the push functionality
        action.add_selector("second-selector".to_string());
        assert_eq!(action.selectors.len(), 2);
        assert_eq!(action.selectors[1], "second-selector");
    }

    #[test]
    fn test_load_actions_default_path() {
        // Test lines 85, 88, 91 in function `load_actions`
        // This test covers the case where no custom path is provided and uses default path
        let result = Action::load_actions(None);

        // Should return Ok - whether file exists or not, it should not error with None path
        assert!(result.is_ok());
        let actions = result.unwrap();
        // We don't assert on length since there may or may not be a default actions.yaml file
        // The key test is that it doesn't error when using the default path
        println!("Loaded {} actions from default path", actions.len());
    }

    #[test]
    fn test_load_actions_custom_path_not_found() {
        // Test line 98 in function `load_actions`
        // This test covers the case where a custom path is provided but file doesn't exist
        let result = Action::load_actions(Some("nonexistent_file.yaml"));

        // Should return error when custom path doesn't exist
        assert!(result.is_err());
        let error_msg = result.unwrap_err().to_string();
        assert!(error_msg.contains("Actions file not found"));
        assert!(error_msg.contains("nonexistent_file.yaml"));
    }

    #[test]
    fn test_load_from_yaml_success() {
        use std::io::Write;
        use tempfile::NamedTempFile;

        // Create a temporary YAML file for testing
        let mut temp_file = NamedTempFile::new().expect("Failed to create temp file");
        let yaml_content = r#"
actions:
  format-code:
    name: "Format Code"
    description: "Formats code blocks"
    selectors: ["code", "codeblock"]
  wrap-quote:
    name: "Wrap Quote" 
    description: "Wraps text in quotes"
    selectors: ["paragraph"]
"#;
        temp_file
            .write_all(yaml_content.as_bytes())
            .expect("Failed to write to temp file");

        // Test line 64: function signature and lines 67-68: file reading and YAML parsing
        let result = Action::load_from_yaml(temp_file.path());
        assert!(result.is_ok());

        let actions = result.unwrap();

        // Test lines 70-76: creating actions from YAML data
        assert_eq!(actions.len(), 2);

        // Find format-code action
        let format_action = actions
            .iter()
            .find(|a| a.id == "format-code")
            .expect("format-code action not found");
        assert_eq!(format_action.name, "Format Code");
        assert_eq!(format_action.description, "Formats code blocks");
        assert_eq!(format_action.selectors.len(), 2);
        assert!(format_action.selectors.contains(&"code".to_string()));
        assert!(format_action.selectors.contains(&"codeblock".to_string()));

        // Find wrap-quote action
        let quote_action = actions
            .iter()
            .find(|a| a.id == "wrap-quote")
            .expect("wrap-quote action not found");
        assert_eq!(quote_action.name, "Wrap Quote");
        assert_eq!(quote_action.description, "Wraps text in quotes");
        assert_eq!(quote_action.selectors.len(), 1);
        assert_eq!(quote_action.selectors[0], "paragraph");

        // Test line 80: Ok(actions) return
        // This is implicitly tested by the successful assertion above
    }

    #[test]
    fn test_load_from_yaml_file_not_found() {
        // Test error handling in load_from_yaml when file doesn't exist
        let result = Action::load_from_yaml("nonexistent_file.yaml");

        // Should return an error
        assert!(result.is_err());

        // Error should be related to file not found
        let error_msg = result.unwrap_err().to_string();
        assert!(
            error_msg.contains("No such file")
                || error_msg.contains("not found")
                || error_msg.contains("cannot find")
        );
    }

    #[test]
    fn test_load_from_yaml_invalid_yaml() {
        use std::io::Write;
        use tempfile::NamedTempFile;

        // Create a temporary file with invalid YAML content
        let mut temp_file = NamedTempFile::new().expect("Failed to create temp file");
        let invalid_yaml = "invalid: yaml: content: [unclosed";
        temp_file
            .write_all(invalid_yaml.as_bytes())
            .expect("Failed to write to temp file");

        // Test YAML parsing error handling (line 68)
        let result = Action::load_from_yaml(temp_file.path());
        assert!(result.is_err());

        // Should be a YAML parsing error - just check that we get an error
        // The specific error message may vary between serde_yaml versions
        let _error_msg = result.unwrap_err().to_string();
        // Just verify that we got an error, which confirms lines 67-68 error handling
    }

    #[test]
    fn test_load_from_yaml_empty_actions() {
        use std::io::Write;
        use tempfile::NamedTempFile;

        // Create a temporary YAML file with empty actions
        let mut temp_file = NamedTempFile::new().expect("Failed to create temp file");
        let yaml_content = "actions: {}";
        temp_file
            .write_all(yaml_content.as_bytes())
            .expect("Failed to write to temp file");

        let result = Action::load_from_yaml(temp_file.path());
        assert!(result.is_ok());

        let actions = result.unwrap();
        assert_eq!(actions.len(), 0);
    }

    #[test]
    fn test_load_actions_calls_load_from_yaml() {
        use std::io::Write;
        use tempfile::NamedTempFile;

        // Create a temporary YAML file for testing
        let mut temp_file = NamedTempFile::new().expect("Failed to create temp file");
        let yaml_content = r#"
actions:
  test-action:
    name: "Test Action"
    description: "Test description"
    selectors: ["test"]
"#;
        temp_file
            .write_all(yaml_content.as_bytes())
            .expect("Failed to write to temp file");

        // Test line 101: Self::load_from_yaml(path)
        let result = Action::load_actions(Some(temp_file.path().to_str().unwrap()));
        assert!(result.is_ok());

        let actions = result.unwrap();
        assert_eq!(actions.len(), 1);
        assert_eq!(actions[0].id, "test-action");
        assert_eq!(actions[0].name, "Test Action");
        assert_eq!(actions[0].description, "Test description");
        assert_eq!(actions[0].selectors, vec!["test".to_string()]);
    }

    #[test]
    fn test_action_clone() {
        // Test clone functionality for Action struct
        let action = Action::new(
            "clone-test".to_string(),
            "Clone Test".to_string(),
            "Testing clone functionality".to_string(),
            vec!["selector1".to_string(), "selector2".to_string()],
        );

        let cloned_action = action.clone();

        assert_eq!(action.id, cloned_action.id);
        assert_eq!(action.name, cloned_action.name);
        assert_eq!(action.description, cloned_action.description);
        assert_eq!(action.selectors, cloned_action.selectors);

        // Ensure it's a deep clone by modifying original
        // (This is implicit since String and Vec are not references)
        assert_eq!(cloned_action.id, "clone-test");
    }

    #[test]
    fn test_action_serialization_deserialization() {
        // Test serialization/deserialization of Action struct
        let action = Action::new(
            "serialize-test".to_string(),
            "Serialize Test".to_string(),
            "Testing serialization functionality".to_string(),
            vec!["selector1".to_string(), "selector2".to_string()],
        );

        // Test JSON serialization
        let json_str = serde_json::to_string(&action).expect("Failed to serialize to JSON");
        let deserialized_action: Action =
            serde_json::from_str(&json_str).expect("Failed to deserialize from JSON");

        assert_eq!(action.id, deserialized_action.id);
        assert_eq!(action.name, deserialized_action.name);
        assert_eq!(action.description, deserialized_action.description);
        assert_eq!(action.selectors, deserialized_action.selectors);
    }

    #[test]
    fn test_action_empty_selectors_handling() {
        // Test empty selectors vector handling
        let action = Action::new(
            "empty-selectors".to_string(),
            "Empty Selectors".to_string(),
            "Action with no selectors".to_string(),
            vec![], // Empty selectors
        );

        assert_eq!(action.selectors.len(), 0);
        assert!(action.selectors.is_empty());

        // Test adding selector to initially empty selectors
        let mut action = action;
        action.add_selector("new-selector".to_string());
        assert_eq!(action.selectors.len(), 1);
        assert_eq!(action.selectors[0], "new-selector");
    }

    #[test]
    fn test_action_duplicate_selector_addition() {
        // Test duplicate selector addition
        let mut action = Action::with_selector(
            "dup-test".to_string(),
            "Duplicate Test".to_string(),
            "Testing duplicate selector addition".to_string(),
            "original-selector".to_string(),
        );

        assert_eq!(action.selectors.len(), 1);

        // Add the same selector again - should be allowed (no deduplication)
        action.add_selector("original-selector".to_string());
        assert_eq!(action.selectors.len(), 2);
        assert_eq!(action.selectors[0], "original-selector");
        assert_eq!(action.selectors[1], "original-selector");

        // Add different selector
        action.add_selector("different-selector".to_string());
        assert_eq!(action.selectors.len(), 3);
        assert_eq!(action.selectors[2], "different-selector");
    }
}
