use libremetaverse_structured_data::{OSD, OSDMap};

use crate::Error;
use crate::message_codec::MessageValue;

fn empty_map() -> OSDMap {
    OSDMap::new_with_constructor().expect("empty OSD map construction is infallible")
}

fn map_value(map: &OSDMap, key: &str) -> Option<OSD> {
    map.get(key)
}

/// Payload-preserving representation of the C# abstract upload response block.
pub struct AssetUploaderBlock {
    pub state: String,
    payload: OSDMap,
}

impl AssetUploaderBlock {
    pub fn deserialize(&mut self, map: OSDMap) -> Result<(), Error> {
        self.state = map_value(&map, "state")
            .map(|value| value.as_string())
            .transpose()?
            .unwrap_or_default();
        self.payload = map;
        Ok(())
    }

    pub fn serialize(&self) -> Result<OSDMap, Error> {
        if self.payload.count() == 0 {
            let map = empty_map();
            if !self.state.is_empty() {
                map.add_with_string_osd("state".into(), OSD::from_string(self.state.clone())?)?;
            }
            Ok(map)
        } else {
            Ok(self.payload.clone())
        }
    }
}

impl MessageValue for AssetUploaderBlock {
    fn message_default() -> Self {
        Self {
            state: String::new(),
            payload: empty_map(),
        }
    }

    fn to_message_osd(&self) -> Result<OSD, Error> {
        self.serialize()?.copy()
    }

    fn from_message_osd(value: &OSD) -> Result<Self, Error> {
        let OSD::Map(values) = value else {
            return Err(Error::Argument);
        };
        let map = OSDMap::new_with_dictionary(values.clone())?;
        let mut block = Self::message_default();
        block.deserialize(map)?;
        Ok(block)
    }
}

pub struct ChatSessionRequestBlock {
    pub method: String,
    payload: OSDMap,
}

impl ChatSessionRequestBlock {
    pub fn deserialize(&mut self, map: OSDMap) -> Result<(), Error> {
        self.method = map_value(&map, "method")
            .ok_or(Error::Argument)?
            .as_string()?;
        self.payload = map;
        Ok(())
    }

    pub fn serialize(&self) -> Result<OSDMap, Error> {
        if self.payload.count() == 0 {
            let map = empty_map();
            map.add_with_string_osd("method".into(), OSD::from_string(self.method.clone())?)?;
            Ok(map)
        } else {
            Ok(self.payload.clone())
        }
    }
}

impl MessageValue for ChatSessionRequestBlock {
    fn message_default() -> Self {
        Self {
            method: String::new(),
            payload: empty_map(),
        }
    }

    fn to_message_osd(&self) -> Result<OSD, Error> {
        self.serialize()?.copy()
    }

    fn from_message_osd(value: &OSD) -> Result<Self, Error> {
        let OSD::Map(values) = value else {
            return Err(Error::Argument);
        };
        let mut block = Self::message_default();
        block.deserialize(OSDMap::new_with_dictionary(values.clone())?)?;
        Ok(block)
    }
}

pub struct MapLayerMessageBase {
    pub flags: i32,
    payload: OSDMap,
}

impl MapLayerMessageBase {
    pub fn deserialize(&mut self, map: OSDMap) -> Result<(), Error> {
        self.flags = if let Some(value) = map.get("Flags") {
            value.as_integer()?
        } else if let Some(OSD::Map(agent)) = map.get("AgentData") {
            agent.get("Flags").ok_or(Error::Argument)?.as_integer()?
        } else {
            return Err(Error::Argument);
        };
        self.payload = map;
        Ok(())
    }

    pub fn serialize(&self) -> Result<OSDMap, Error> {
        if self.payload.count() == 0 {
            let map = empty_map();
            map.add_with_string_osd("Flags".into(), OSD::from_integer_with_int32(self.flags)?)?;
            Ok(map)
        } else {
            Ok(self.payload.clone())
        }
    }
}

impl MessageValue for MapLayerMessageBase {
    fn message_default() -> Self {
        Self {
            flags: 0,
            payload: empty_map(),
        }
    }

    fn to_message_osd(&self) -> Result<OSD, Error> {
        self.serialize()?.copy()
    }

    fn from_message_osd(value: &OSD) -> Result<Self, Error> {
        let OSD::Map(values) = value else {
            return Err(Error::Argument);
        };
        let mut block = Self::message_default();
        block.deserialize(OSDMap::new_with_dictionary(values.clone())?)?;
        Ok(block)
    }
}

pub struct MapLayerRequestVariant {
    pub flags: i32,
}

impl MapLayerRequestVariant {
    pub fn new() -> Result<Self, Error> {
        Ok(Self { flags: 0 })
    }

    pub fn deserialize(&mut self, map: OSDMap) -> Result<(), Error> {
        self.flags = map.get("Flags").ok_or(Error::Argument)?.as_integer()?;
        Ok(())
    }

    pub fn serialize(&self) -> Result<OSDMap, Error> {
        let map = empty_map();
        map.add_with_string_osd("Flags".into(), OSD::from_integer_with_int32(self.flags)?)?;
        Ok(map)
    }
}

impl MessageValue for MapLayerRequestVariant {
    fn message_default() -> Self {
        Self { flags: 0 }
    }

    fn to_message_osd(&self) -> Result<OSD, Error> {
        self.serialize()?.copy()
    }

    fn from_message_osd(value: &OSD) -> Result<Self, Error> {
        let OSD::Map(values) = value else {
            return Err(Error::Argument);
        };
        let mut block = Self::message_default();
        block.deserialize(OSDMap::new_with_dictionary(values.clone())?)?;
        Ok(block)
    }
}

macro_rules! opaque_block {
    ($name:ident) => {
        pub struct $name {
            payload: OSDMap,
        }

        impl $name {
            pub fn deserialize(&mut self, map: OSDMap) -> Result<(), Error> {
                self.payload = map;
                Ok(())
            }

            pub fn serialize(&self) -> Result<OSDMap, Error> {
                Ok(self.payload.clone())
            }
        }

        impl MessageValue for $name {
            fn message_default() -> Self {
                Self {
                    payload: empty_map(),
                }
            }

            fn to_message_osd(&self) -> Result<OSD, Error> {
                self.serialize()?.copy()
            }

            fn from_message_osd(value: &OSD) -> Result<Self, Error> {
                let OSD::Map(values) = value else {
                    return Err(Error::Argument);
                };
                Ok(Self {
                    payload: OSDMap::new_with_dictionary(values.clone())?,
                })
            }
        }
    };
}

opaque_block!(ObjectMediaBlock);
opaque_block!(RemoteParcelRequestBlock);
opaque_block!(SearchStatRequestBlock);

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

    #[test]
    fn dynamic_upload_block_preserves_unmodeled_fields() {
        let map = empty_map();
        map.add_with_string_osd("state".into(), OSD::from_string("complete".into()).unwrap())
            .unwrap();
        map.add_with_string_osd(
            "new_asset".into(),
            OSD::from_string("asset".into()).unwrap(),
        )
        .unwrap();
        let mut block = AssetUploaderBlock::message_default();
        block.deserialize(map).unwrap();
        assert_eq!(block.state, "complete");
        assert_eq!(
            block.serialize().unwrap().get("new_asset"),
            Some(OSD::String("asset".into()))
        );
    }

    #[test]
    fn map_layer_base_reads_both_request_and_reply_shapes() {
        let request = empty_map();
        request
            .add_with_string_osd("Flags".into(), OSD::from_integer_with_int32(7).unwrap())
            .unwrap();
        let mut block = MapLayerMessageBase::message_default();
        block.deserialize(request).unwrap();
        assert_eq!(block.flags, 7);

        let reply = empty_map();
        reply
            .add_with_string_osd(
                "AgentData".into(),
                OSD::Map(std::collections::HashMap::from([(
                    "Flags".into(),
                    OSD::Integer(9),
                )])),
            )
            .unwrap();
        block.deserialize(reply).unwrap();
        assert_eq!(block.flags, 9);
    }
}
