use std::io::Read;
use base64::{engine::general_purpose as base64_engine, Engine as _};
use xml::name::OwnedName;
use xml::reader::{EventReader, XmlEvent};
use crate::{crypt::calculate_sha256, error::DatabaseKeyError};
fn parse_xml_keyfile(xml: &[u8]) -> Result<Vec<u8>, DatabaseKeyError> {
    let parser = EventReader::new(xml);
    let mut tag_stack = Vec::new();
    for ev in parser {
        match ev? {
            XmlEvent::StartElement {
                name: OwnedName { ref local_name, .. },
                ..
            } => {
                tag_stack.push(local_name.clone());
            }
            XmlEvent::EndElement { .. } => {
                tag_stack.pop();
            }
            XmlEvent::Characters(s) => {
                if tag_stack == ["KeyFile", "Key", "Data"] {
                    let key_base64 = s.as_bytes().to_vec();
                    return if let Ok(key) = base64_engine::STANDARD.decode(&key_base64) {
                        Ok(key)
                    } else {
                        Ok(key_base64)
                    };
                }
            }
            _ => {}
        }
    }
    Err(DatabaseKeyError::InvalidKeyFile)
}
fn parse_keyfile(buffer: &[u8]) -> Vec<u8> {
    if let Ok(v) = parse_xml_keyfile(buffer) {
        v
    } else if buffer.len() == 32 {
        buffer.to_vec()
    } else {
        calculate_sha256(&[buffer]).as_slice().to_vec()
    }
}
#[derive(Debug, Clone, Default)]
pub struct DatabaseKey {
    password: Option<String>,
    keyfile: Option<Vec<u8>>,
}
impl DatabaseKey {
    pub fn with_password(mut self, password: &str) -> Self {
        self.password = Some(password.to_string());
        self
    }
    pub fn with_keyfile(mut self, keyfile: &mut dyn Read) -> Result<Self, std::io::Error> {
        let mut buf = Vec::new();
        keyfile.read_to_end(&mut buf)?;
        self.keyfile = Some(buf);
        Ok(self)
    }
    pub fn new() -> Self {
        DatabaseKey::default()
    }
    pub(crate) fn get_key_elements(self) -> Result<Vec<Vec<u8>>, DatabaseKeyError> {
        let mut out = Vec::new();
        if let Some(p) = self.password {
            out.push(calculate_sha256(&[p.as_bytes()]).to_vec());
        }
        if let Some(ref f) = self.keyfile {
            out.push(parse_keyfile(f));
        }
        if out.is_empty() {
            return Err(DatabaseKeyError::IncorrectKey);
        }
        Ok(out)
    }
}
#[cfg(test)]
mod key_tests {
    use crate::error::DatabaseKeyError;
    use super::DatabaseKey;
    #[test]
    fn test_key() -> Result<(), DatabaseKeyError> {
        let ke = DatabaseKey::new().with_password("asdf").get_key_elements()?;
        assert_eq!(ke.len(), 1);
        let ke = DatabaseKey::new()
            .with_keyfile(&mut "bare-key-file".as_bytes())?
            .get_key_elements()?;
        assert_eq!(ke.len(), 1);
        let ke = DatabaseKey::new()
            .with_keyfile(&mut "0123456789ABCDEF0123456789ABCDEF".as_bytes())?
            .get_key_elements()?;
        assert_eq!(ke.len(), 1);
        let ke = DatabaseKey::new()
            .with_password("asdf")
            .with_keyfile(&mut "bare-key-file".as_bytes())?
            .get_key_elements()?;
        assert_eq!(ke.len(), 2);
        let key = "<KeyFile><Key><Data>0!23456789ABCDEF0123456789ABCDEF</Data></Key></KeyFile>";
        let ke = DatabaseKey::new().with_keyfile(&mut key.as_bytes())?.get_key_elements()?;
        assert_eq!(ke.len(), 1);
        let key = "<KeyFile><Key><Data>NXyYiJMHg3ls+eBmjbAjWec9lcOToJiofbhNiFMTJMw=</Data></Key></KeyFile>";
        let ke = DatabaseKey::new().with_keyfile(&mut key.as_bytes())?.get_key_elements()?;
        assert_eq!(ke.len(), 1);
        let key = "<Not><A><KeyFile></KeyFile></A></Not>";
        let ke = DatabaseKey::new().with_keyfile(&mut key.as_bytes())?.get_key_elements()?;
        assert_eq!(ke.len(), 1);
        assert!(DatabaseKey {
            password: None,
            keyfile: None
        }
        .get_key_elements()
        .is_err());
        Ok(())
    }
}