use core::any::{Any, TypeId};

use rootcause_internals::{
    RawAttachment,
    handlers::{AttachmentFormattingStyle, FormattingFunction},
};

use crate::{
    handlers::{self, AttachmentHandler},
    markers::{self, Local, ObjectMarker, SendSync},
    report_attachment::ReportAttachmentRef,
    util::format_helper,
};

/// FIXME: Once rust-lang/rust#132922 gets resolved, we can make the `raw` field
/// an unsafe field and remove this module.
mod limit_field_access {
    use core::{any::Any, marker::PhantomData};

    use rootcause_internals::{RawAttachment, RawAttachmentRef};

    use crate::markers::{self, SendSync};

    /// An attachment to be attached to a [`Report`](crate::Report).
    ///
    /// Attachments can hold any type of data, and can be formatted using custom
    /// handlers. The attachment can be marked as either [`SendSync`] or
    /// [`Local`], indicating whether it is safe to send the attachment
    /// across threads or not.
    ///
    /// # Type Parameters
    /// - `Attachment`: The type of the attachment. This can either be a
    ///   concrete type, or `dyn Any`.
    /// - `ThreadSafety`: The thread safety marker for the attachment. This can
    ///   either be [`SendSync`] or [`Local`].
    ///
    /// [`SendSync`]: crate::markers::SendSync
    /// [`Local`]: crate::markers::Local
    #[repr(transparent)]
    pub struct ReportAttachment<Attachment = dyn Any, ThreadSafety = SendSync>
    where
        Attachment: markers::ObjectMarker + ?Sized,
        ThreadSafety: markers::ThreadSafetyMarker,
    {
        /// # Safety
        ///
        /// The following safety invariants are guaranteed to be upheld as long
        /// as this struct exists:
        ///
        /// 1. If `A` is a concrete type: The attachment embedded in the
        ///    [`RawAttachment`] must be of type `A`.
        /// 2. If `T = SendSync`: The attachment embedded in the
        ///    [`RawAttachment`] must be `Send + Sync`.
        raw: RawAttachment,
        _attachment: PhantomData<Attachment>,
        _thread_safety: PhantomData<ThreadSafety>,
    }

    impl<A, T> ReportAttachment<A, T>
    where
        A: markers::ObjectMarker + ?Sized,
        T: markers::ThreadSafetyMarker,
    {
        /// Creates a new Attachment from a raw attachment
        ///
        /// # Safety
        ///
        /// The caller must ensure:
        ///
        /// 1. If `A` is a concrete type: The attachment embedded in the
        ///    [`RawAttachment`] must be of type `A`.
        /// 2. If `T = SendSync`: The attachment embedded in the
        ///    [`RawAttachment`] must be `Send + Sync`.
        #[must_use]
        pub(crate) unsafe fn from_raw(raw: RawAttachment) -> Self {
            // SAFETY: We must uphold the safety invariants of the raw field:
            // 1. Guaranteed by caller
            // 2. Guaranteed by caller
            ReportAttachment {
                raw,
                _attachment: PhantomData,
                _thread_safety: PhantomData,
            }
        }

        /// Consumes the [`ReportAttachment`] and returns the inner
        /// [`RawAttachment`].
        #[must_use]
        pub(crate) fn into_raw(self) -> RawAttachment {
            // SAFETY: We are destroying `self`, so we no longer
            // need to uphold any safety invariants.
            self.raw
        }

        /// Creates a lifetime-bound [`RawAttachmentRef`] from the inner
        /// [`RawAttachment`].
        #[must_use]
        pub(crate) fn as_raw_ref(&self) -> RawAttachmentRef<'_> {
            // SAFETY: We must uphold the safety invariants of the raw field:
            // 1. No mutation is possible through the `RawAttachmentRef`
            // 2. No mutation is possible through the `RawAttachmentRef`
            let raw = &self.raw;

            raw.as_ref()
        }
    }
}
pub use limit_field_access::ReportAttachment;

impl<A, T> ReportAttachment<A, T>
where
    A: markers::ObjectMarker + ?Sized,
    T: markers::ThreadSafetyMarker,
{
    /// Allocates a new [`ReportAttachment`] with the given attachment as the
    /// data.
    ///
    /// The new attachment will use the [`handlers::Display`] handler to format
    /// the attachment. See [`ReportAttachment::new_custom`] if you want to
    /// control the handler used.
    ///
    /// # Examples
    /// ```
    /// use rootcause::{prelude::*, report_attachment::ReportAttachment};
    ///
    /// let attachment = ReportAttachment::new("This is an attachment");
    /// let mut report = report!("An error occurred");
    /// report.attachments_mut().push(attachment.into_dyn_any());
    /// ```
    #[must_use]
    pub fn new(attachment: A) -> Self
    where
        A: markers::ObjectMarkerFor<T> + core::fmt::Display + core::fmt::Debug + Sized,
    {
        Self::new_custom::<handlers::Display>(attachment)
    }

    /// Allocates a new [`ReportAttachment`] with the given attachment as the
    /// data and the given handler to format it.
    ///
    /// # Examples
    /// ```
    /// use rootcause::{prelude::*, report_attachment::ReportAttachment};
    ///
    /// #[derive(Debug)]
    /// struct MyAttachmentType {
    ///     data: String,
    /// }
    /// let attachment = ReportAttachment::new_custom::<handlers::Debug>(MyAttachmentType {
    ///     data: "Important data".to_string(),
    /// });
    /// let mut report = report!("An error occurred");
    /// report.attachments_mut().push(attachment.into_dyn_any());
    /// ```
    #[must_use]
    pub fn new_custom<H>(attachment: A) -> Self
    where
        A: markers::ObjectMarkerFor<T> + Sized,
        H: AttachmentHandler<A>,
    {
        let raw = RawAttachment::new::<A, H>(attachment);

        // SAFETY:
        // 1. We just created the `RawAttachment` and it does indeed have an attachment
        //    of type `A`.
        // 2. If `T=Local`, then this is trivially true. If `T=SendSync`, then the bound
        //    `A: ObjectMarkerFor<SendSync>` guarantees that the attachment is
        //    `Send+Sync`.
        unsafe {
            // @add-unsafe-context: markers::ObjectMarkerFor
            ReportAttachment::from_raw(raw)
        }
    }

    /// Changes the inner attachment type of the [`ReportAttachment`] to [`dyn
    /// Any`].
    ///
    /// Calling this method is equivalent to calling `attachment.into()`,
    /// however this method has been restricted to only change the
    /// attachment type to `dyn Any`.
    ///
    /// This method can be useful to help with type inference or to improve code
    /// readability, as it more clearly communicates intent.
    ///
    /// This method does not actually modify the attachment in any way. It only
    /// has the effect of "forgetting" that the inner attachment actually has
    /// the type `A`.
    ///
    /// To get back the attachment with a concrete `A` you can use the method
    /// [`ReportAttachment::downcast_attachment`].
    #[must_use]
    pub fn into_dyn_any(self) -> ReportAttachment<dyn Any, T> {
        let raw = self.into_raw();

        // SAFETY:
        // 1. `A=dyn Any`, so this is trivially true.
        // 2. Guaranteed by the invariants of this type.
        unsafe { ReportAttachment::<dyn Any, T>::from_raw(raw) }
    }

    /// Changes the thread safety mode of the [`ReportAttachment`] to [`Local`].
    ///
    /// Calling this method is equivalent to calling `attachment.into()`,
    /// however this method has been restricted to only change the thread
    /// safety mode to [`Local`].
    ///
    /// This method can be useful to help with type inference or to improve code
    /// readability, as it more clearly communicates intent.
    ///
    /// This method does not actually modify the attachment in any way. It only
    /// has the effect of "forgetting" that the object in the
    /// [`ReportAttachment`] might actually be [`Send`] and [`Sync`].
    #[must_use]
    pub fn into_local(self) -> ReportAttachment<A, Local> {
        let raw = self.into_raw();

        // SAFETY:
        // 1. Guaranteed by the invariants of this type.
        // 2. `T=Local`, so this is trivially true.
        unsafe { ReportAttachment::from_raw(raw) }
    }

    /// Returns a reference to the inner attachment.
    ///
    /// This method is only available when the attachment type is a specific
    /// type, and not `dyn Any`.
    #[must_use]
    pub fn inner(&self) -> &A
    where
        A: Sized,
    {
        self.as_ref().inner()
    }

    /// Returns the [`TypeId`] of the inner attachment.
    #[must_use]
    pub fn inner_type_id(&self) -> TypeId {
        self.as_raw_ref().attachment_type_id()
    }

    /// Returns the [`TypeId`] of the handler used when creating this
    /// attachment.
    #[must_use]
    pub fn inner_handler_type_id(&self) -> TypeId {
        self.as_raw_ref().attachment_handler_type_id()
    }

    /// Formats the attachment with hook processing.
    #[must_use]
    pub fn format_inner(&self) -> impl core::fmt::Display + core::fmt::Debug {
        let attachment: ReportAttachmentRef<'_, dyn Any> = self.as_ref().into_dyn_any();
        format_helper(
            attachment,
            |attachment, formatter| {
                crate::hooks::formatting_overrides::attachment::display_attachment(
                    attachment, None, formatter,
                )
            },
            |attachment, formatter| {
                crate::hooks::formatting_overrides::attachment::debug_attachment(
                    attachment, None, formatter,
                )
            },
        )
    }

    /// Formats the attachment without hook processing.
    #[must_use]
    pub fn format_inner_unhooked(&self) -> impl core::fmt::Display + core::fmt::Debug {
        format_helper(
            self.as_raw_ref(),
            |attachment, formatter| attachment.attachment_display(formatter),
            |attachment, formatter| attachment.attachment_debug(formatter),
        )
    }

    /// Gets the preferred formatting style for the attachment with hook
    /// processing.
    ///
    /// # Arguments
    ///
    /// - `report_formatting_function`: Whether the report in which this
    ///   attachment will be embedded is being formatted using [`Display`]
    ///   formatting or [`Debug`]
    ///
    /// [`Display`]: core::fmt::Display
    /// [`Debug`]: core::fmt::Debug
    #[must_use]
    pub fn preferred_formatting_style(
        &self,
        report_formatting_function: FormattingFunction,
    ) -> AttachmentFormattingStyle {
        crate::hooks::formatting_overrides::attachment::get_preferred_formatting_style(
            self.as_ref().into_dyn_any(),
            report_formatting_function,
        )
    }

    /// Gets the preferred formatting style for the attachment without hook
    /// processing.
    ///
    /// # Arguments
    ///
    /// - `report_formatting_function`: Whether the report in which this
    ///   attachment will be embedded is being formatted using [`Display`]
    ///   formatting or [`Debug`]
    ///
    /// [`Display`]: core::fmt::Display
    /// [`Debug`]: core::fmt::Debug
    #[must_use]
    pub fn preferred_formatting_style_unhooked(
        &self,
        report_formatting_function: FormattingFunction,
    ) -> AttachmentFormattingStyle {
        self.as_raw_ref()
            .preferred_formatting_style(report_formatting_function)
    }

    /// Returns a reference to the attachment.
    #[must_use]
    pub fn as_ref(&self) -> ReportAttachmentRef<'_, A> {
        let raw = self.as_raw_ref();

        // SAFETY:
        // 1. Guaranteed by the invariants of this type.
        unsafe { ReportAttachmentRef::from_raw(raw) }
    }
}

impl<A> ReportAttachment<A, SendSync>
where
    A: markers::ObjectMarker,
{
    /// Creates a new [`ReportAttachment`] with [`SendSync`] thread safety.
    ///
    /// This is a convenience method that calls [`ReportAttachment::new`] with
    /// explicit [`SendSync`] thread safety. Use this method when you're
    /// having trouble with type inference for the thread safety parameter.
    ///
    /// The attachment will use the [`handlers::Display`] handler to format the
    /// attachment.
    #[must_use]
    pub fn new_sendsync(attachment: A) -> Self
    where
        A: core::fmt::Display + core::fmt::Debug + Send + Sync,
    {
        Self::new(attachment)
    }

    /// Creates a new [`ReportAttachment`] with [`SendSync`] thread safety and
    /// the given handler.
    ///
    /// This is a convenience method that calls [`ReportAttachment::new_custom`]
    /// with explicit [`SendSync`] thread safety. Use this method when
    /// you're having trouble with type inference for the thread safety
    /// parameter.
    #[must_use]
    pub fn new_sendsync_custom<H>(attachment: A) -> Self
    where
        A: Send + Sync + 'static,
        H: AttachmentHandler<A>,
    {
        Self::new_custom::<H>(attachment)
    }
}

impl<A> ReportAttachment<A, Local>
where
    A: markers::ObjectMarker,
{
    /// Creates a new [`ReportAttachment`] with [`Local`] thread safety.
    ///
    /// This is a convenience method that calls [`ReportAttachment::new`] with
    /// explicit [`Local`] thread safety. Use this method when you're having
    /// trouble with type inference for the thread safety parameter.
    ///
    /// The attachment will use the [`handlers::Display`] handler to format the
    /// attachment.
    #[must_use]
    pub fn new_local(attachment: A) -> Self
    where
        A: core::fmt::Display + core::fmt::Debug,
    {
        Self::new_custom::<handlers::Display>(attachment)
    }

    /// Creates a new [`ReportAttachment`] with [`Local`] thread safety and the
    /// given handler.
    ///
    /// This is a convenience method that calls [`ReportAttachment::new_custom`]
    /// with explicit [`Local`] thread safety. Use this method when you're
    /// having trouble with type inference for the thread safety parameter.
    #[must_use]
    pub fn new_local_custom<H>(attachment: A) -> Self
    where
        H: AttachmentHandler<A>,
    {
        Self::new_custom::<H>(attachment)
    }
}

impl<T> ReportAttachment<dyn Any, T>
where
    T: markers::ThreadSafetyMarker,
{
    /// Attempts to downcast the inner attachment to a specific type.
    ///
    /// Returns `Some(&A)` if the inner attachment is of type `A`, otherwise
    /// returns `None`.
    #[must_use]
    pub fn downcast_inner<A>(&self) -> Option<&A>
    where
        A: ObjectMarker,
    {
        self.as_ref().downcast_inner()
    }

    /// Downcasts the inner attachment to a specific type without checking.
    ///
    /// # Safety
    ///
    /// The caller must ensure:
    ///
    /// 1. The inner attachment is actually of type `A` (can be verified by
    ///    calling [`inner_type_id()`] first)
    ///
    /// [`inner_type_id()`]: ReportAttachment::inner_type_id
    #[must_use]
    pub unsafe fn downcast_inner_unchecked<A>(&self) -> &A
    where
        A: ObjectMarker,
    {
        let raw = self.as_raw_ref();

        // SAFETY:
        // 1. Guaranteed by the caller
        unsafe { raw.attachment_downcast_unchecked() }
    }

    /// Attempts to downcast the [`ReportAttachment`] to a specific attachment
    /// type.
    ///
    /// Returns `Ok(attachment)` if the inner attachment is of type `A`,
    /// otherwise returns `Err(self)` with the original [`ReportAttachment`].
    pub fn downcast_attachment<A>(self) -> Result<ReportAttachment<A, T>, Self>
    where
        A: markers::ObjectMarker,
    {
        if TypeId::of::<A>() == self.inner_type_id() {
            // SAFETY:
            // 1. We just checked that the type IDs match
            let attachment = unsafe { self.downcast_unchecked() };

            Ok(attachment)
        } else {
            Err(self)
        }
    }

    /// Downcasts the [`ReportAttachment`] to a specific attachment type without
    /// checking.
    ///
    /// # Safety
    ///
    /// The caller must ensure:
    ///
    /// 1. The inner attachment is actually of type `A` (can be verified by
    ///    calling [`inner_type_id()`] first)
    ///
    /// [`inner_type_id()`]: ReportAttachment::inner_type_id
    #[must_use]
    pub unsafe fn downcast_unchecked<A>(self) -> ReportAttachment<A, T>
    where
        A: markers::ObjectMarker,
    {
        let raw = self.into_raw();

        // SAFETY:
        // 1. Guaranteed by the caller
        // 2. Guaranteed by the invariants of this type.
        unsafe { ReportAttachment::from_raw(raw) }
    }
}

impl<A, T> From<A> for ReportAttachment<A, T>
where
    A: markers::ObjectMarkerFor<T> + core::fmt::Display + core::fmt::Debug,
    T: markers::ThreadSafetyMarker,
{
    fn from(attachment: A) -> Self {
        ReportAttachment::new_custom::<handlers::Display>(attachment)
    }
}

impl<A, T> From<A> for ReportAttachment<dyn Any, T>
where
    A: markers::ObjectMarkerFor<T> + core::fmt::Display + core::fmt::Debug,
    T: markers::ThreadSafetyMarker,
{
    fn from(attachment: A) -> Self {
        ReportAttachment::new_custom::<handlers::Display>(attachment).into_dyn_any()
    }
}

// SAFETY: The `SendSync` marker indicates that the inner attachment
// is `Send`+`Sync`. Therefore it is safe to implement `Send`+`Sync` for the
// attachment itself.
unsafe impl<A> Send for ReportAttachment<A, SendSync> where A: markers::ObjectMarker + ?Sized {}

// SAFETY: The `SendSync` marker indicates that the inner attachment
// is `Send`+`Sync`. Therefore it is safe to implement `Send`+`Sync` for the
// attachment itself.
unsafe impl<A> Sync for ReportAttachment<A, SendSync> where A: markers::ObjectMarker + ?Sized {}

macro_rules! from_impls {
    ($(
        <
            $($param:ident),*
        >:
        $context1:ty => $context2:ty,
        $thread_safety1:ty => $thread_safety2:ty,
        [$($op:ident),*]
    ),* $(,)?) => {
        $(
            impl<$($param),*> From<ReportAttachment<$context1, $thread_safety1>> for ReportAttachment<$context2, $thread_safety2>
                where $(
                    $param: markers::ObjectMarker,
                )*
            {
                fn from(attachment: ReportAttachment<$context1, $thread_safety1>) -> Self {
                    attachment
                        $(
                            .$op()
                        )*
                }
            }
        )*
    };
}

from_impls!(
    <C>: C => C, SendSync => Local, [into_local],
    <C>: C => dyn Any, SendSync => SendSync, [into_dyn_any],
    <C>: C => dyn Any, SendSync => Local, [into_dyn_any, into_local],
    <C>: C => dyn Any, Local => Local, [into_dyn_any],
    <>:  dyn Any => dyn Any, SendSync => Local, [into_local],
);

#[cfg(test)]
mod tests {
    use alloc::string::String;

    use super::*;

    #[allow(dead_code)]
    struct NonSend(*const ());
    static_assertions::assert_not_impl_any!(NonSend: Send, Sync);

    #[test]
    fn test_attachment_send_sync() {
        static_assertions::assert_impl_all!(ReportAttachment<(), SendSync>: Send, Sync);
        static_assertions::assert_impl_all!(ReportAttachment<String, SendSync>: Send, Sync);
        static_assertions::assert_impl_all!(ReportAttachment<NonSend, SendSync>: Send, Sync);
        static_assertions::assert_impl_all!(ReportAttachment<dyn Any, SendSync>: Send, Sync);

        static_assertions::assert_not_impl_any!(ReportAttachment<(), Local>: Send, Sync);
        static_assertions::assert_not_impl_any!(ReportAttachment<String, Local>: Send, Sync);
        static_assertions::assert_not_impl_any!(ReportAttachment<NonSend, Local>: Send, Sync);
        static_assertions::assert_not_impl_any!(ReportAttachment<dyn Any, Local>: Send, Sync);
    }

    #[test]
    fn test_attachment_copy_clone() {
        static_assertions::assert_not_impl_any!(ReportAttachment<(), SendSync>: Copy, Clone);
        static_assertions::assert_not_impl_any!(ReportAttachment<String, SendSync>: Copy, Clone);
        static_assertions::assert_not_impl_any!(ReportAttachment<NonSend, SendSync>: Copy, Clone);
        static_assertions::assert_not_impl_any!(ReportAttachment<dyn Any, SendSync>: Copy, Clone);

        static_assertions::assert_not_impl_any!(ReportAttachment<(), Local>: Copy, Clone);
        static_assertions::assert_not_impl_any!(ReportAttachment<String, Local>: Copy, Clone);
        static_assertions::assert_not_impl_any!(ReportAttachment<NonSend, Local>: Copy, Clone);
        static_assertions::assert_not_impl_any!(ReportAttachment<dyn Any, Local>: Copy, Clone);
    }
}
