use serde::{Deserialize, Serialize, ser::SerializeMap};

use crate::bitwarden::{
    BitwardenEntryTypeCard, BitwardenEntryTypeData, BitwardenEntryTypeIdentity,
    BitwardenEntryTypeLogin, BitwardenEntryTypeSecureNote,
};

impl Serialize for super::BitwardenEntryTypeData {
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: serde::Serializer,
    {
        let mut map = serializer.serialize_map(Some(2))?;
        match self {
            Self::Login(login) => {
                map.serialize_entry("type", &BitwardenEntryType::Login)?;
                map.serialize_entry("login", login)?;
            }
            Self::Card(card) => {
                map.serialize_entry("type", &BitwardenEntryType::Card)?;
                map.serialize_entry("card", &card)?;
            }
            Self::SecureNote(secure_note) => {
                map.serialize_entry("type", &BitwardenEntryType::SecureNote)?;
                map.serialize_entry("secureNote", &secure_note)?;
            }
            Self::Identity(identity) => {
                map.serialize_entry("type", &BitwardenEntryType::Identity)?;
                map.serialize_entry("identity", &identity)?;
            }
        }
        map.end()
    }
}

impl<'de> Deserialize<'de> for BitwardenEntryTypeData {
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    where
        D: serde::Deserializer<'de>,
    {
        deserializer.deserialize_struct(
            "BitwardenEntryTypeData",
            &["type", "login", "card", "secureNote", "identity"],
            DeserializeVisitor,
        )
    }
}

struct DeserializeVisitor;

impl<'de> serde::de::Visitor<'de> for DeserializeVisitor {
    type Value = BitwardenEntryTypeData;
    fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
        formatter.write_str("an object with type and tagged type property")
    }

    fn visit_map<M>(self, mut map: M) -> Result<Self::Value, M::Error>
    where
        M: serde::de::MapAccess<'de>,
    {
        let mut entry_type: Option<BitwardenEntryType> = None;
        let mut login_data: Option<BitwardenEntryTypeLogin> = None;
        let mut secure_note_data: Option<BitwardenEntryTypeSecureNote> = None;
        let mut card_data: Option<BitwardenEntryTypeCard> = None;
        let mut identity_data: Option<BitwardenEntryTypeIdentity> = None;
        while let Some(key) = map.next_key::<&str>()? {
            match key {
                "type" => {
                    if entry_type.is_some() {
                        return Err(serde::de::Error::duplicate_field("type"));
                    }
                    entry_type = Some(map.next_value()?);
                }
                "login" => {
                    if login_data.is_some() {
                        return Err(serde::de::Error::duplicate_field("login"));
                    }
                    login_data = Some(map.next_value()?);
                }
                "card" => {
                    if card_data.is_some() {
                        return Err(serde::de::Error::duplicate_field("card"));
                    }
                    card_data = Some(map.next_value()?);
                }
                "identity" => {
                    if identity_data.is_some() {
                        return Err(serde::de::Error::duplicate_field("identity"));
                    }
                    identity_data = Some(map.next_value()?);
                }
                "secureNote" => {
                    if secure_note_data.is_some() {
                        return Err(serde::de::Error::duplicate_field("secureNote"));
                    }
                    secure_note_data = Some(map.next_value()?);
                }
                _ => {}
            }
        }
        match entry_type {
            Some(BitwardenEntryType::Login) => {
                let login = login_data.ok_or(serde::de::Error::missing_field("login"))?;
                Ok(BitwardenEntryTypeData::Login(login))
            }
            Some(BitwardenEntryType::Card) => {
                let card = card_data.ok_or(serde::de::Error::missing_field("card"))?;
                Ok(BitwardenEntryTypeData::Card(card))
            }
            Some(BitwardenEntryType::SecureNote) => {
                let secure_note =
                    secure_note_data.ok_or(serde::de::Error::missing_field("secure_note"))?;
                Ok(BitwardenEntryTypeData::SecureNote(secure_note))
            }
            Some(BitwardenEntryType::Identity) => {
                let identity = identity_data.ok_or(serde::de::Error::missing_field("identity"))?;
                Ok(BitwardenEntryTypeData::Identity(identity))
            }
            None => Err(serde::de::Error::missing_field("type")),
        }
    }
}

#[derive(Debug, serde_repr::Serialize_repr, serde_repr::Deserialize_repr)]
#[repr(u8)]
enum BitwardenEntryType {
    Login = 1,
    SecureNote = 2,
    Card = 3,
    Identity = 4,
}

#[cfg(test)]
mod tests {
    use crate::json;

    use super::*;

    #[test]
    pub fn test_simple_serializing() {
        let d = BitwardenEntryTypeData::Login(BitwardenEntryTypeLogin {
            username: None,
            password: None,
            totp: None,
            uris: None,
        });
        let json = json::to_string(&d).unwrap();
        assert_eq!(
            json,
            r#"{"type":1,"login":{"username":null,"password":null,"totp":null,"uris":null}}"#
        );
        match json::from_str(&json).unwrap() {
            BitwardenEntryTypeData::Login(BitwardenEntryTypeLogin {
                username: None,
                password: None,
                totp: None,
                uris: None,
            }) => {}
            _ => panic!("Could not deserialize json to itself"),
        }
    }
}
