use chrono::prelude::*;
use quick_xml::events::{BytesEnd, BytesStart, BytesText, Event};
use quick_xml::Writer;
use serde::Deserialize;
use std::io::Cursor;

const NAME: &str = "saml2:Conditions";

#[derive(Clone, Debug, Deserialize, Hash, Eq, PartialEq, Ord, PartialOrd)]
pub struct Conditions {
    #[serde(rename = "@NotBefore")]
    pub not_before: Option<DateTime<Utc>>,
    #[serde(rename = "@NotOnOrAfter")]
    pub not_on_or_after: Option<DateTime<Utc>>,
    #[serde(rename = "AudienceRestriction", default)]
    pub audience_restrictions: Option<Vec<AudienceRestriction>>,
    #[serde(rename = "OneTimeUse")]
    pub one_time_use: Option<OneTimeUse>,
    #[serde(rename = "ProxyRestriction")]
    pub proxy_restriction: Option<ProxyRestriction>,
}

impl TryFrom<Conditions> for Event<'_> {
    type Error = Box<dyn std::error::Error>;

    fn try_from(value: Conditions) -> Result<Self, Self::Error> {
        (&value).try_into()
    }
}

impl TryFrom<&Conditions> for Event<'_> {
    type Error = Box<dyn std::error::Error>;

    fn try_from(value: &Conditions) -> Result<Self, Self::Error> {
        let mut write_buf = Vec::new();
        let mut writer = Writer::new(Cursor::new(&mut write_buf));
        let mut root = BytesStart::new(NAME);
        if let Some(not_before) = &value.not_before {
            root.push_attribute((
                "NotBefore",
                not_before
                    .to_rfc3339_opts(SecondsFormat::Secs, true)
                    .as_ref(),
            ));
        }
        if let Some(not_on_or_after) = &value.not_on_or_after {
            root.push_attribute((
                "NotOnOrAfter",
                not_on_or_after
                    .to_rfc3339_opts(SecondsFormat::Secs, true)
                    .as_ref(),
            ));
        }
        writer.write_event(Event::Start(root))?;
        if let Some(audience_restrictions) = &value.audience_restrictions {
            for restriction in audience_restrictions {
                let event: Event<'_> = restriction.try_into()?;
                writer.write_event(event)?;
            }
        }
        if let Some(proxy_restriction) = &value.proxy_restriction {
            let event: Event<'_> = proxy_restriction.try_into()?;
            writer.write_event(event)?;
        }
        writer.write_event(Event::End(BytesEnd::new(NAME)))?;
        Ok(Event::Text(BytesText::from_escaped(String::from_utf8(
            write_buf,
        )?)))
    }
}

const AUDIENCE_RESTRICTION_NAME: &str = "saml2:AudienceRestriction";
const AUDIENCE_NAME: &str = "saml2:Audience";

#[derive(Clone, Debug, Deserialize, Hash, Eq, PartialEq, Ord, PartialOrd)]
pub struct AudienceRestriction {
    #[serde(rename = "Audience")]
    pub audience: Vec<String>,
}

impl TryFrom<AudienceRestriction> for Event<'_> {
    type Error = Box<dyn std::error::Error>;

    fn try_from(value: AudienceRestriction) -> Result<Self, Self::Error> {
        (&value).try_into()
    }
}

impl TryFrom<&AudienceRestriction> for Event<'_> {
    type Error = Box<dyn std::error::Error>;

    fn try_from(value: &AudienceRestriction) -> Result<Self, Self::Error> {
        let mut write_buf = Vec::new();
        let mut writer = Writer::new(Cursor::new(&mut write_buf));
        let root = BytesStart::new(AUDIENCE_RESTRICTION_NAME);
        writer.write_event(Event::Start(root))?;
        for aud in &value.audience {
            writer.write_event(Event::Start(BytesStart::new(AUDIENCE_NAME)))?;
            writer.write_event(Event::Text(BytesText::from_escaped(aud)))?;
            writer.write_event(Event::End(BytesEnd::new(AUDIENCE_NAME)))?;
        }
        writer.write_event(Event::End(BytesEnd::new(AUDIENCE_RESTRICTION_NAME)))?;
        Ok(Event::Text(BytesText::from_escaped(String::from_utf8(
            write_buf,
        )?)))
    }
}

#[derive(Clone, Debug, Deserialize, Hash, Eq, PartialEq, Ord, PartialOrd)]
pub struct OneTimeUse {}

const PROXY_RESTRICTION_NAME: &str = "saml2:ProxyRestriction";

#[derive(Clone, Debug, Deserialize, Hash, Eq, PartialEq, Ord, PartialOrd)]
pub struct ProxyRestriction {
    #[serde(rename = "@Count")]
    pub count: Option<usize>,
    #[serde(rename = "Audience")]
    pub audiences: Option<Vec<String>>,
}

impl TryFrom<ProxyRestriction> for Event<'_> {
    type Error = Box<dyn std::error::Error>;

    fn try_from(value: ProxyRestriction) -> Result<Self, Self::Error> {
        (&value).try_into()
    }
}

impl TryFrom<&ProxyRestriction> for Event<'_> {
    type Error = Box<dyn std::error::Error>;

    fn try_from(value: &ProxyRestriction) -> Result<Self, Self::Error> {
        let mut write_buf = Vec::new();
        let mut writer = Writer::new(Cursor::new(&mut write_buf));
        let mut root = BytesStart::new(PROXY_RESTRICTION_NAME);
        if let Some(count) = &value.count {
            root.push_attribute(("Count", count.to_string().as_ref()));
        }
        writer.write_event(Event::Start(root))?;
        if let Some(audiences) = &value.audiences {
            for aud in audiences {
                writer.write_event(Event::Start(BytesStart::new(AUDIENCE_NAME)))?;
                writer.write_event(Event::Text(BytesText::from_escaped(aud)))?;
                writer.write_event(Event::End(BytesEnd::new(AUDIENCE_NAME)))?;
            }
        }
        writer.write_event(Event::End(BytesEnd::new(PROXY_RESTRICTION_NAME)))?;
        Ok(Event::Text(BytesText::from_escaped(String::from_utf8(
            write_buf,
        )?)))
    }
}
