//! Extended and legacy environment capability support.

#![allow(clippy::missing_errors_doc)]
#![allow(clippy::must_use_candidate)]
#![allow(clippy::needless_pass_by_value)]

use crate::agent_manager::EventRegistry;
use crate::{Error, GridClient};
use futures_channel::oneshot;
use futures_util::future::{Either, select};
use futures_util::pin_mut;
use libremetaverse_structured_data::{OSD, OSDMap, OSDParser};
use libremetaverse_types::compat::{CancellationToken, EventHandler, Subscription, Uri};
use std::collections::HashMap;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::{Arc, RwLock};

const MAX_ENVIRONMENT_BYTES: usize = 16 * 1024 * 1024;

fn read<T>(value: &RwLock<T>) -> std::sync::RwLockReadGuard<'_, T> {
    value
        .read()
        .unwrap_or_else(std::sync::PoisonError::into_inner)
}

fn write<T>(value: &RwLock<T>) -> std::sync::RwLockWriteGuard<'_, T> {
    value
        .write()
        .unwrap_or_else(std::sync::PoisonError::into_inner)
}

fn integer(map: &OSDMap, key: &str, default: i32) -> i32 {
    map.get(key)
        .and_then(|value| value.as_integer().ok())
        .unwrap_or(default)
}

fn boolean(map: &OSDMap, key: &str, default: bool) -> bool {
    map.get(key)
        .and_then(|value| value.as_boolean().ok())
        .unwrap_or(default)
}

fn string(map: &OSDMap, key: &str) -> String {
    map.get(key)
        .and_then(|value| value.as_string().ok())
        .unwrap_or_default()
}

#[derive(Clone, Debug, PartialEq)]
pub struct EnvironmentData {
    day_cycle: Option<OSD>,
    day_length: i32,
    day_offset: i32,
    flags: crate::messages::linden::EnvironmentFlags,
    is_default: bool,
}

impl EnvironmentData {
    pub fn new() -> Result<Self, Error> {
        Ok(Self {
            day_cycle: None,
            day_length: 14_400,
            day_offset: 57_600,
            flags: crate::messages::linden::EnvironmentFlags::NONE,
            is_default: false,
        })
    }

    pub fn deserialize(&mut self, map: OSDMap) -> Result<(), Error> {
        self.day_length = integer(&map, "day_length", 14_400);
        self.day_offset = integer(&map, "day_offset", 57_600);
        self.flags = crate::messages::linden::EnvironmentFlags(
            u32::try_from(integer(&map, "flags", 0)).unwrap_or_default(),
        );
        self.is_default = boolean(&map, "is_default", false);
        self.day_cycle = map
            .get("day_cycle")
            .filter(|value| !matches!(value, OSD::Undefined));
        Ok(())
    }

    pub fn serialize(&self) -> Result<OSDMap, Error> {
        let mut values = HashMap::from([
            ("day_length".into(), OSD::Integer(self.day_length)),
            ("day_offset".into(), OSD::Integer(self.day_offset)),
            (
                "flags".into(),
                OSD::Integer(i32::try_from(self.flags.0).unwrap_or(i32::MAX)),
            ),
        ]);
        if let Some(day_cycle) = self.day_cycle.clone() {
            values.insert("day_cycle".into(), day_cycle);
        }
        OSDMap::new_with_dictionary(values)
    }

    pub fn day_cycle(&self) -> Option<OSD> {
        self.day_cycle.clone()
    }
    pub fn set_day_cycle(&mut self, value: Option<OSD>) {
        self.day_cycle = value;
    }
    pub fn day_length(&self) -> i32 {
        self.day_length
    }
    pub fn set_day_length(&mut self, value: i32) {
        self.day_length = value;
    }
    pub fn day_offset(&self) -> i32 {
        self.day_offset
    }
    pub fn set_day_offset(&mut self, value: i32) {
        self.day_offset = value;
    }
    pub fn flags(&self) -> crate::messages::linden::EnvironmentFlags {
        self.flags
    }
    pub fn set_flags(&mut self, value: crate::messages::linden::EnvironmentFlags) {
        self.flags = value;
    }
    pub fn is_default(&self) -> bool {
        self.is_default
    }
    pub fn set_is_default(&mut self, value: bool) {
        self.is_default = value;
    }
}

impl crate::interfaces::IMessage for EnvironmentData {
    fn deserialize(&mut self, map: OSDMap) -> Result<(), Error> {
        self.deserialize(map)
    }
    fn serialize(&self) -> Result<OSDMap, Error> {
        self.serialize()
    }
}

#[derive(Clone, Debug, PartialEq)]
pub struct ExtEnvironmentMessage {
    environment: Option<EnvironmentData>,
    message: String,
    parcel_id: i32,
    success: bool,
    version: i32,
}

impl ExtEnvironmentMessage {
    pub fn new() -> Result<Self, Error> {
        Ok(Self {
            environment: None,
            message: String::new(),
            parcel_id: -1,
            success: false,
            version: 0,
        })
    }
    pub fn deserialize(&mut self, map: OSDMap) -> Result<(), Error> {
        self.success = boolean(&map, "success", false);
        self.parcel_id = integer(&map, "parcel_id", -1);
        self.version = integer(&map, "version", 0);
        self.message = string(&map, "message");
        self.environment = match map.get("environment") {
            Some(OSD::Map(values)) => {
                let mut value = EnvironmentData::new()?;
                value.deserialize(OSDMap::new_with_dictionary(values)?)?;
                Some(value)
            }
            _ => None,
        };
        Ok(())
    }
    pub fn serialize(&self) -> Result<OSDMap, Error> {
        let mut values = HashMap::new();
        values.insert("parcel_id".into(), OSD::Integer(self.parcel_id));
        if let Some(environment) = &self.environment {
            values.insert(
                "environment".into(),
                OSD::Map(environment.serialize()?.snapshot()),
            );
        }
        if self.version != 0 {
            values.insert("version".into(), OSD::Integer(self.version));
        }
        OSDMap::new_with_dictionary(values)
    }
    pub fn environment(&self) -> Option<EnvironmentData> {
        self.environment.clone()
    }
    pub fn set_environment(&mut self, value: Option<EnvironmentData>) {
        self.environment = value;
    }
    pub fn message(&self) -> String {
        self.message.clone()
    }
    pub fn set_message(&mut self, value: String) {
        self.message = value;
    }
    pub fn parcel_id(&self) -> i32 {
        self.parcel_id
    }
    pub fn set_parcel_id(&mut self, value: i32) {
        self.parcel_id = value;
    }
    pub fn success(&self) -> bool {
        self.success
    }
    pub fn set_success(&mut self, value: bool) {
        self.success = value;
    }
    pub fn version(&self) -> i32 {
        self.version
    }
    pub fn set_version(&mut self, value: i32) {
        self.version = value;
    }
}

impl crate::interfaces::IMessage for ExtEnvironmentMessage {
    fn deserialize(&mut self, map: OSDMap) -> Result<(), Error> {
        self.deserialize(map)
    }
    fn serialize(&self) -> Result<OSDMap, Error> {
        self.serialize()
    }
}

#[derive(Clone, Debug, PartialEq)]
pub struct LegacyEnvironmentMessage {
    settings: Option<OSD>,
}
impl LegacyEnvironmentMessage {
    pub fn new() -> Result<Self, Error> {
        Ok(Self { settings: None })
    }
    pub fn deserialize(&mut self, map: OSDMap) -> Result<(), Error> {
        self.settings = Some(OSD::Map(map.snapshot()));
        Ok(())
    }
    pub fn serialize(&self) -> Result<OSDMap, Error> {
        match &self.settings {
            Some(OSD::Map(values)) => OSDMap::new_with_dictionary(values.clone()),
            _ => OSDMap::new_with_constructor(),
        }
    }
    pub fn settings(&self) -> Option<OSD> {
        self.settings.clone()
    }
    pub fn set_settings(&mut self, value: Option<OSD>) {
        self.settings = value;
    }
}
impl crate::interfaces::IMessage for LegacyEnvironmentMessage {
    fn deserialize(&mut self, map: OSDMap) -> Result<(), Error> {
        self.deserialize(map)
    }
    fn serialize(&self) -> Result<OSDMap, Error> {
        self.serialize()
    }
}

#[derive(Clone)]
pub struct RegionEnvironmentEventArgs {
    environment: ExtEnvironmentMessage,
}
impl RegionEnvironmentEventArgs {
    pub fn new(environment: ExtEnvironmentMessage) -> Result<Self, Error> {
        Ok(Self { environment })
    }
    pub fn environment(&self) -> ExtEnvironmentMessage {
        self.environment.clone()
    }
}
#[derive(Clone)]
pub struct ParcelEnvironmentEventArgs {
    parcel_id: i32,
    environment: ExtEnvironmentMessage,
}
impl ParcelEnvironmentEventArgs {
    pub fn new(parcel_id: i32, environment: ExtEnvironmentMessage) -> Result<Self, Error> {
        Ok(Self {
            parcel_id,
            environment,
        })
    }
    pub fn parcel_id(&self) -> i32 {
        self.parcel_id
    }
    pub fn environment(&self) -> ExtEnvironmentMessage {
        self.environment.clone()
    }
}
#[derive(Clone)]
pub struct LegacyEnvironmentEventArgs {
    environment: LegacyEnvironmentMessage,
}
impl LegacyEnvironmentEventArgs {
    pub fn new(environment: LegacyEnvironmentMessage) -> Result<Self, Error> {
        Ok(Self { environment })
    }
    pub fn environment(&self) -> LegacyEnvironmentMessage {
        self.environment.clone()
    }
}

pub(crate) struct EnvironmentManagerInner {
    client: Arc<GridClient>,
    disposed: AtomicBool,
    region: RwLock<Option<ExtEnvironmentMessage>>,
    legacy: RwLock<Option<LegacyEnvironmentMessage>>,
    region_events: EventRegistry<RegionEnvironmentEventArgs>,
    parcel_events: EventRegistry<ParcelEnvironmentEventArgs>,
    legacy_events: EventRegistry<LegacyEnvironmentEventArgs>,
}

#[derive(Clone)]
pub struct EnvironmentManager {
    pub(crate) inner: Arc<EnvironmentManagerInner>,
}

impl EnvironmentManager {
    pub fn new(client: GridClient) -> Result<Self, Error> {
        Self::native_new(Arc::new(client))
    }
    #[allow(clippy::unnecessary_wraps)] // The mapped public constructor has a fixed fallible shape.
    pub(crate) fn native_new(client: Arc<GridClient>) -> Result<Self, Error> {
        let candidate = Arc::new(EnvironmentManagerInner {
            client: Arc::clone(&client),
            disposed: AtomicBool::new(false),
            region: RwLock::new(None),
            legacy: RwLock::new(None),
            region_events: EventRegistry::default(),
            parcel_events: EventRegistry::default(),
            legacy_events: EventRegistry::default(),
        });
        Ok(Self {
            inner: client.install_environment_manager_inner(candidate),
        })
    }
    async fn capability(
        &self,
        name: &str,
        token: &CancellationToken,
    ) -> Result<Option<Uri>, Error> {
        if self.inner.disposed.load(Ordering::Acquire) {
            return Err(Error::InvalidOperation);
        }
        let Some(caps) = self
            .inner
            .client
            .network()
            .current_sim()
            .and_then(|sim| sim.native_caps())
        else {
            return Ok(None);
        };
        if let Some(uri) = caps.capability_uri(name.to_owned())? {
            return Ok(Some(uri));
        }
        if caps.seed_request_finished() {
            return Ok(None);
        }
        let (sender, receiver) = oneshot::channel();
        let sender = Arc::new(std::sync::Mutex::new(Some(sender)));
        let wake = Arc::clone(&sender);
        let _subscription = caps.subscribe_capabilities_received(Some(Arc::new(move |_| {
            if let Some(sender) = wake
                .lock()
                .unwrap_or_else(std::sync::PoisonError::into_inner)
                .take()
            {
                let _ = sender.send(());
            }
        })));
        if let Some(uri) = caps.capability_uri(name.to_owned())? {
            return Ok(Some(uri));
        }
        let cancellation = token.cancelled();
        pin_mut!(receiver, cancellation);
        match select(receiver, cancellation).await {
            Either::Left(_) => caps.capability_uri(name.to_owned()),
            Either::Right(_) => Err(Error::Cancelled),
        }
    }
    async fn request(
        &self,
        method: &str,
        uri: Uri,
        body: Vec<u8>,
        token: Option<CancellationToken>,
    ) -> Result<Option<Vec<u8>>, Error> {
        if body.len() > MAX_ENVIRONMENT_BYTES {
            return Err(Error::Argument);
        }
        let http = self.inner.client.native_http_caps_client();
        let token = token.unwrap_or_default();
        let (response, bytes) = match method {
            "GET" => http.get(uri, token, None).await?,
            "POST" => {
                http.post_with_uri_string_bytes_cancellation_token_i_progress(
                    uri,
                    "application/llsd+xml".into(),
                    body,
                    token,
                    None,
                )
                .await?
            }
            "DELETE" => {
                http.delete_with_uri_string_bytes_cancellation_token_i_progress(
                    uri,
                    "application/llsd+xml".into(),
                    body,
                    token,
                    None,
                )
                .await?
            }
            _ => return Err(Error::Argument),
        };
        if bytes.len() > MAX_ENVIRONMENT_BYTES {
            return Err(Error::Argument);
        }
        Ok((200..300).contains(&response.status_code).then_some(bytes))
    }
    fn parcel_uri(uri: Uri, parcel_id: i32) -> Uri {
        let separator = if uri.0.contains('?') { '&' } else { '?' };
        Uri(format!("{}{separator}parcelid={parcel_id}", uri.0))
    }
    fn decode_ext(bytes: Vec<u8>) -> Result<ExtEnvironmentMessage, Error> {
        let OSD::Map(values) = OSDParser::deserialize_with_bytes(bytes)? else {
            return Err(Error::Argument);
        };
        let mut message = ExtEnvironmentMessage::new()?;
        message.deserialize(OSDMap::new_with_dictionary(values)?)?;
        Ok(message)
    }
    pub async fn get_region_environment(
        &self,
        token: Option<CancellationToken>,
    ) -> Result<Option<ExtEnvironmentMessage>, Error> {
        let resolved = token.clone().unwrap_or_default();
        let Some(uri) = self.capability("ExtEnvironment", &resolved).await? else {
            return Ok(None);
        };
        let Some(bytes) = self.request("GET", uri, Vec::new(), token).await? else {
            return Ok(None);
        };
        let message = Self::decode_ext(bytes)?;
        *write(&self.inner.region) = Some(message.clone());
        self.inner
            .region_events
            .emit(RegionEnvironmentEventArgs::new(message.clone())?);
        Ok(Some(message))
    }
    pub async fn get_parcel_environment(
        &self,
        parcel_id: i32,
        token: Option<CancellationToken>,
    ) -> Result<Option<ExtEnvironmentMessage>, Error> {
        let resolved = token.clone().unwrap_or_default();
        let Some(uri) = self.capability("ExtEnvironment", &resolved).await? else {
            return Ok(None);
        };
        let Some(bytes) = self
            .request("GET", Self::parcel_uri(uri, parcel_id), Vec::new(), token)
            .await?
        else {
            return Ok(None);
        };
        let message = Self::decode_ext(bytes)?;
        self.inner
            .parcel_events
            .emit(ParcelEnvironmentEventArgs::new(parcel_id, message.clone())?);
        Ok(Some(message))
    }
    async fn set_environment(
        &self,
        parcel_id: Option<i32>,
        environment: EnvironmentData,
        token: Option<CancellationToken>,
    ) -> Result<Option<ExtEnvironmentMessage>, Error> {
        let resolved = token.clone().unwrap_or_default();
        let Some(mut uri) = self.capability("ExtEnvironment", &resolved).await? else {
            return Ok(None);
        };
        if let Some(parcel_id) = parcel_id {
            uri = Self::parcel_uri(uri, parcel_id);
        }
        let body = OSDParser::serialize_llsd_xml_bytes(OSD::Map(HashMap::from([(
            "environment".into(),
            OSD::Map(environment.serialize()?.snapshot()),
        )])))?;
        let Some(bytes) = self.request("POST", uri, body, token).await? else {
            return Ok(None);
        };
        Self::decode_ext(bytes).map(Some)
    }
    pub async fn set_parcel_environment(
        &self,
        parcel_id: i32,
        environment: EnvironmentData,
        token: Option<CancellationToken>,
    ) -> Result<Option<ExtEnvironmentMessage>, Error> {
        self.set_environment(Some(parcel_id), environment, token)
            .await
    }
    pub async fn set_region_environment_with_environment_data_cancellation_token(
        &self,
        environment: EnvironmentData,
        token: Option<CancellationToken>,
    ) -> Result<Option<ExtEnvironmentMessage>, Error> {
        self.set_environment(None, environment, token).await
    }
    async fn reset_environment(
        &self,
        parcel_id: Option<i32>,
        token: Option<CancellationToken>,
    ) -> Result<bool, Error> {
        let resolved = token.clone().unwrap_or_default();
        let Some(mut uri) = self.capability("ExtEnvironment", &resolved).await? else {
            return Ok(false);
        };
        if let Some(parcel_id) = parcel_id {
            uri = Self::parcel_uri(uri, parcel_id);
        }
        let Some(bytes) = self.request("DELETE", uri, Vec::new(), token).await? else {
            return Ok(false);
        };
        Ok(Self::decode_ext(bytes)?.success())
    }
    pub async fn reset_parcel_environment(
        &self,
        parcel_id: i32,
        token: Option<CancellationToken>,
    ) -> Result<bool, Error> {
        self.reset_environment(Some(parcel_id), token).await
    }
    pub async fn reset_region_environment(
        &self,
        token: Option<CancellationToken>,
    ) -> Result<bool, Error> {
        self.reset_environment(None, token).await
    }
    pub async fn get_legacy_environment(
        &self,
        token: Option<CancellationToken>,
    ) -> Result<Option<LegacyEnvironmentMessage>, Error> {
        let resolved = token.clone().unwrap_or_default();
        let Some(uri) = self.capability("EnvironmentSettings", &resolved).await? else {
            return Ok(None);
        };
        let Some(bytes) = self.request("GET", uri, Vec::new(), token).await? else {
            return Ok(None);
        };
        let OSD::Map(values) = OSDParser::deserialize_with_bytes(bytes)? else {
            return Err(Error::Argument);
        };
        let mut message = LegacyEnvironmentMessage::new()?;
        message.deserialize(OSDMap::new_with_dictionary(values)?)?;
        *write(&self.inner.legacy) = Some(message.clone());
        self.inner
            .legacy_events
            .emit(LegacyEnvironmentEventArgs::new(message.clone())?);
        Ok(Some(message))
    }
    pub async fn set_legacy_environment_with_osd_cancellation_token(
        &self,
        settings: OSD,
        token: Option<CancellationToken>,
    ) -> Result<bool, Error> {
        let resolved = token.clone().unwrap_or_default();
        let Some(uri) = self.capability("EnvironmentSettings", &resolved).await? else {
            return Ok(false);
        };
        let body = OSDParser::serialize_llsd_xml_bytes(settings)?;
        let Some(bytes) = self.request("POST", uri, body, token).await? else {
            return Ok(false);
        };
        let OSD::Map(values) = OSDParser::deserialize_with_bytes(bytes)? else {
            return Ok(false);
        };
        Ok(values
            .get("success")
            .and_then(|value| value.as_boolean().ok())
            .unwrap_or(true))
    }
    pub fn region_environment(&self) -> Option<ExtEnvironmentMessage> {
        read(&self.inner.region).clone()
    }
    pub fn set_region_environment(&mut self, value: Option<ExtEnvironmentMessage>) {
        *write(&self.inner.region) = value;
    }
    pub fn legacy_environment(&self) -> Option<LegacyEnvironmentMessage> {
        read(&self.inner.legacy).clone()
    }
    pub fn set_legacy_environment(&mut self, value: Option<LegacyEnvironmentMessage>) {
        *write(&self.inner.legacy) = value;
    }
    pub fn subscribe_region_environment_updated(
        &self,
        handler: EventHandler<RegionEnvironmentEventArgs>,
    ) -> Subscription {
        self.inner.region_events.subscribe(handler)
    }
    pub fn subscribe_parcel_environment_updated(
        &self,
        handler: EventHandler<ParcelEnvironmentEventArgs>,
    ) -> Subscription {
        self.inner.parcel_events.subscribe(handler)
    }
    pub fn subscribe_legacy_environment_updated(
        &self,
        handler: EventHandler<LegacyEnvironmentEventArgs>,
    ) -> Subscription {
        self.inner.legacy_events.subscribe(handler)
    }
    pub fn dispose(&self) -> Result<(), Error> {
        self.inner.disposed.store(true, Ordering::Release);
        Ok(())
    }
}

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

    #[test]
    fn environment_defaults_and_wire_shape_match_eep() {
        let mut value = EnvironmentData::new().unwrap();
        assert_eq!(value.day_length(), 14_400);
        assert_eq!(value.day_offset(), 57_600);
        value.set_is_default(true);
        let map = value.serialize().unwrap();
        assert!(!map.contains_key("is_default".into()).unwrap());
        assert!(!map.contains_key("day_cycle".into()).unwrap());
    }

    #[test]
    fn ext_environment_round_trip_preserves_correlation_fields() {
        let mut value = ExtEnvironmentMessage::new().unwrap();
        value.set_parcel_id(17);
        value.set_version(9);
        value.set_environment(Some(EnvironmentData::new().unwrap()));
        let mut decoded = ExtEnvironmentMessage::new().unwrap();
        decoded.deserialize(value.serialize().unwrap()).unwrap();
        assert_eq!(decoded.parcel_id(), 17);
        assert_eq!(decoded.version(), 9);
        assert!(decoded.environment().is_some());
    }
}
