use std::ops::Deref;

use gtk::{glib, prelude::*, subclass::prelude::*};
use matrix_sdk::deserialized_responses::TimelineEvent;
use ruma::{
    events::{
        room::message::{MessageType, OriginalSyncRoomMessageEvent, Relation},
        AnySyncMessageLikeEvent, AnyTimelineEvent, SyncMessageLikeEvent,
    },
    OwnedEventId,
};

use crate::{session::model::Room, utils::matrix::get_media_content};

/// The types of events that can be displayer in the history viewers.
#[derive(Default, Debug, Copy, Clone, PartialEq, Eq, glib::Enum)]
#[enum_type(name = "HistoryViewerEventType")]
pub enum HistoryViewerEventType {
    #[default]
    File,
    Media,
    Audio,
}

impl HistoryViewerEventType {
    fn with_msgtype(msgtype: &MessageType) -> Option<Self> {
        let event_type = match msgtype {
            MessageType::Audio(_) => Self::Audio,
            MessageType::File(_) => Self::File,
            MessageType::Image(_) => Self::Media,
            MessageType::Video(_) => Self::Media,
            _ => return None,
        };

        Some(event_type)
    }
}

#[derive(Clone, Debug, glib::Boxed)]
#[boxed_type(name = "BoxedSyncRoomMessageEvent")]
pub struct BoxedSyncRoomMessageEvent(pub OriginalSyncRoomMessageEvent);

impl Deref for BoxedSyncRoomMessageEvent {
    type Target = OriginalSyncRoomMessageEvent;

    fn deref(&self) -> &Self::Target {
        &self.0
    }
}

mod imp {
    use std::cell::{Cell, OnceCell};

    use super::*;

    #[derive(Debug, Default, glib::Properties)]
    #[properties(wrapper_type = super::HistoryViewerEvent)]
    pub struct HistoryViewerEvent {
        /// The room containing this event.
        #[property(get, construct_only)]
        pub room: glib::WeakRef<Room>,
        /// The Matrix event.
        #[property(construct_only)]
        pub matrix_event: OnceCell<BoxedSyncRoomMessageEvent>,
        /// The type of the event.
        #[property(get, construct_only, builder(HistoryViewerEventType::default()))]
        pub event_type: Cell<HistoryViewerEventType>,
    }

    #[glib::object_subclass]
    impl ObjectSubclass for HistoryViewerEvent {
        const NAME: &'static str = "HistoryViewerEvent";
        type Type = super::HistoryViewerEvent;
    }

    #[glib::derived_properties]
    impl ObjectImpl for HistoryViewerEvent {}
}

glib::wrapper! {
    /// An event in the history viewer's timeline.
    pub struct HistoryViewerEvent(ObjectSubclass<imp::HistoryViewerEvent>);
}

impl HistoryViewerEvent {
    /// Constructs a new `HistoryViewerEvent` with the given event, if it is
    /// viewable in one of the history viewers.
    pub fn try_new(room: &Room, event: TimelineEvent) -> Option<Self> {
        let Ok(AnyTimelineEvent::MessageLike(message_like_event)) = event.event.deserialize()
        else {
            return None;
        };
        let AnySyncMessageLikeEvent::RoomMessage(SyncMessageLikeEvent::Original(mut message_event)) =
            message_like_event.into()
        else {
            return None;
        };

        // Filter out edits, they should be bundled with the original event.
        if matches!(
            message_event.content.relates_to,
            Some(Relation::Replacement(_))
        ) {
            return None;
        }

        // Apply bundled edit.
        if let Some(Relation::Replacement(replacement)) = message_event
            .unsigned
            .relations
            .replace
            .as_ref()
            .and_then(|e| e.content.relates_to.as_ref())
        {
            message_event
                .content
                .apply_replacement(replacement.new_content.clone());
        }

        let event_type = HistoryViewerEventType::with_msgtype(&message_event.content.msgtype)?;

        let obj: Self = glib::Object::builder()
            .property("room", room)
            .property("matrix-event", BoxedSyncRoomMessageEvent(message_event))
            .property("event-type", event_type)
            .build();

        Some(obj)
    }

    /// The Matrix event.
    fn matrix_event(&self) -> &OriginalSyncRoomMessageEvent {
        self.imp().matrix_event.get().unwrap()
    }

    /// The event ID of the inner event.
    pub fn event_id(&self) -> OwnedEventId {
        self.matrix_event().event_id.clone()
    }

    /// The message content of the inner event.
    pub fn message_content(&self) -> MessageType {
        self.matrix_event().content.msgtype.clone()
    }

    /// Get the binary content of this event.
    ///
    /// Returns `Ok((filename, binary_content))` on success.
    pub async fn get_file_content(&self) -> Result<(String, Vec<u8>), matrix_sdk::Error> {
        let Some(room) = self.room() else {
            return Err(matrix_sdk::Error::UnknownError(
                "Could not upgrade Room".into(),
            ));
        };
        let Some(session) = room.session() else {
            return Err(matrix_sdk::Error::UnknownError(
                "Could not upgrade Session".into(),
            ));
        };
        let client = session.client();
        let message_content = self.message_content();

        get_media_content(client, message_content).await
    }
}
