use anyhow::{anyhow, Result};
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use crate::{condition::Condition, helpers::Connection, inject::Inject, Formalize};
#[derive(PartialEq, Debug, Serialize, Deserialize, Clone)]
pub struct Event {
    #[serde(default, alias = "Name", alias = "NAME")]
    pub name: Option<String>,
    #[serde(alias = "Time", alias = "TIME")]
    pub time: Option<f32>,
    #[serde(alias = "Conditions", alias = "CONDITIONS")]
    pub conditions: Option<Vec<String>>,
    #[serde(alias = "Injects", alias = "INJECTS")]
    pub injects: Vec<String>,
    #[serde(alias = "Description", alias = "DESCRIPTION")]
    pub description: Option<String>,
}
pub type Events = HashMap<String, Event>;
impl Formalize for Event {
    fn formalize(&mut self) -> Result<()> {
        if self.injects.is_empty() {
            return Err(anyhow!("An Event must have have at least one Inject"));
        }
        if let Some(time) = self.time {
            if !(0.0..=1.0).contains(&time) {
                return Err(anyhow!("Events Time field must have a float value between 0 and 1"));
            }
        }
        Ok(())
    }
}
impl Connection<Condition> for (&String, &Event) {
    fn validate_connections(&self, potential_condition_names: &Option<Vec<String>>) -> Result<()> {
        if self.1.conditions.is_some() && potential_condition_names.is_none() {
            return Err(anyhow!(
                "Event \"{event_name}\" has Conditions but none found under Scenario",
                event_name = self.0
            ));
        }
        if let Some(required_conditions) = &self.1.conditions {
            if let Some(condition_names) = potential_condition_names {
                for event_condition_name in required_conditions {
                    if !condition_names.contains(event_condition_name) {
                        return Err(anyhow!(
                            "Condition \"{event_condition_name}\" not found under Scenario"
                        ));
                    }
                }
            }
        }
        Ok(())
    }
}
impl Connection<Inject> for (&String, &Event) {
    fn validate_connections(&self, potential_inject_names: &Option<Vec<String>>) -> Result<()> {
        if let Some(inject_names) = potential_inject_names {
            for event_inject_name in &self.1.injects {
                if !inject_names.contains(event_inject_name) {
                    return Err(anyhow!(
                        "Event \"{event_name}\" Inject \"{event_inject_name}\" not found under Scenario", 
                        event_name = self.0 
                    ));
                }
            }
        } else {
            return Err(anyhow!(
                "An Event requires at least one Inject but none are defined under Scenario"
            ));
        }
        Ok(())
    }
}
#[cfg(test)]
mod tests {
    use super::*;
    use crate::parse_sdl;
    #[test]
    fn parses_sdl_with_events() {
        let sdl = r#"
            name: test-scenario
            description: some description
            start: 2022-01-20T13:00:00Z
            end: 2022-01-20T23:00:00Z
            conditions:
                condition-1:
                    command: executable/path.sh
                    interval: 30
            capabilities:
                capability-1:
                    description: "Can defend against Dirty Cow"
                    condition: condition-1
                capability-2:
                    description: "Can defend against Dirty Cow"
                    condition: condition-1
            injects:
                my-cool-inject:
                    source: inject-package
                    capabilities:
                        executive: capability-1
            events:
                my-cool-event:
                    time: 0.2345678
                    conditions:
                        - condition-1
                    injects:
                        - my-cool-inject
        "#;
        let schema = parse_sdl(sdl).unwrap();
        insta::with_settings!({sort_maps => true}, {
                insta::assert_yaml_snapshot!(schema);
        });
    }
    #[test]
    fn parses_single_event() {
        let event = r#"
            time: 0.2345678
            conditions:
                - condition-1
            injects:
                - my-cool-inject
      "#;
        serde_yaml::from_str::<Event>(event).unwrap();
    }
    #[test]
    #[should_panic(expected = "Events Time field must have a float value between 0 and 1")]
    fn fails_on_incorrect_time_value() {
        let event = r#"
            time: 600
            conditions:
                - condition-1
            injects:
                - my-cool-inject
      "#;
        serde_yaml::from_str::<Event>(event)
            .unwrap()
            .formalize()
            .unwrap();
    }
    #[test]
    #[should_panic(expected = "Condition \"condition-1\" not found under Scenario Conditions")]
    fn fails_on_missing_scenario_condition() {
        let sdl = r#"
                name: test-scenario
                description: some description
                start: 2022-01-20T13:00:00Z
                end: 2022-01-20T23:00:00Z
                conditions:
                    condition-3000:
                        source: digital-library-package
                capabilities:
                    capability-1:
                        description: "Can defend against Dirty Cow"
                        condition: condition-1
                    capability-2:
                        description: "Can defend against Dirty Cow"
                        condition: condition-1
                injects:
                    my-cool-inject:
                        source: inject-package
                        capabilities:
                            executive: capability-1
                events:
                    my-cool-event:
                        time: 0.2345678
                        conditions:
                            - condition-1
                        injects:
                            - my-cool-inject
            "#;
        parse_sdl(sdl).unwrap();
    }
    #[test]
    #[should_panic(expected = "Capability requires at least one Condition but none found under Scenario")]
    fn fails_on_missing_conditions() {
        let sdl = r#"
                name: test-scenario
                description: some description
                start: 2022-01-20T13:00:00Z
                end: 2022-01-20T23:00:00Z
                capabilities:
                    capability-1:
                        description: "Can defend against Dirty Cow"
                        condition: condition-1
                    capability-2:
                        description: "Can defend against Dirty Cow"
                        condition: condition-1
                injects:
                    my-cool-inject:
                        source: inject-package
                        capabilities:
                            executive: capability-1
                events:
                    my-cool-event:
                        time: 0.2345678
                        conditions:
                            - condition-1
                        injects:
                            - my-cool-inject
            "#;
        parse_sdl(sdl).unwrap();
    }
}