use crate::SharedData;
use crate::common::session::Session;
use crate::common::time::unix_timestamp_secs;
#[cfg(feature = "didcomm")]
use crate::messages::MessageHandler;
#[cfg(feature = "didcomm")]
use crate::messages::PackOptions;
use affinidi_messaging_mediator_common::errors::MediatorError;
use affinidi_messaging_mediator_common::store::MediatorStore;
#[cfg(feature = "didcomm")]
use affinidi_messaging_sdk::messages::compat::PackEncryptedMetadata;
use affinidi_messaging_sdk::messages::compat::UnpackMetadata;
use affinidi_messaging_sdk::messages::problem_report::{ProblemReportScope, ProblemReportSorter};
use affinidi_messaging_sdk::messages::sending::{InboundMessageList, InboundMessageResponse};
use http::StatusCode;
use sha256::digest;
use std::sync::Arc;
use tracing::{Instrument, debug, error, span, trace, warn};

use super::{ProcessMessageResponse, WrapperType};

async fn _store_message(
    state: &SharedData,
    session: &Session,
    response: &ProcessMessageResponse,
    data: &str,
    to_did_hash: &str,
    from_did_hash: Option<&str>,
    expiry: u64,
) -> Result<String, MediatorError> {
    // Live stream the message?
    if let Some(stream_uuid) = state
        .database
        .streaming_is_client_live(to_did_hash, response.force_live_delivery)
        .await
    {
        _live_stream(
            &state.database,
            to_did_hash,
            &stream_uuid,
            data,
            response.force_live_delivery,
        )
        .await;
    }

    state
        .database
        .store_message(
            &session.session_id,
            data,
            to_did_hash,
            from_did_hash,
            expiry,
            state.config.limits.queued_receive_messages_hard as usize,
        )
        .await
}

/// Stores a message in the mediator's database
/// handles a lot of higher order logic for storing messages
/// - state: SharedData
/// - session: Session
/// - response: ProcessMessageResponse
/// - metadata: UnpackMetadata
pub(crate) async fn store_message(
    state: &SharedData,
    session: &Session,
    response: &ProcessMessageResponse,
    metadata: &UnpackMetadata,
) -> Result<InboundMessageResponse, MediatorError> {
    let _span = span!(tracing::Level::DEBUG, "store_message",);

    async move {
        if response.forward_message {
            return Ok(InboundMessageResponse::Forwarded);
        }

        if response.store_message {
            let mut stored_messages = InboundMessageList::default();
            match &response.data {
                WrapperType::None => {}
                #[cfg(feature = "didcomm")]
                WrapperType::Message(message) => {
                    // Pack the message for the next recipient(s)
                    let Some(to_dids) = &message.to else {
                        return Err(MediatorError::problem(
                            75,
                            &session.session_id,
                            Some(message.id.to_string()),
                            ProblemReportSorter::Error,
                            ProblemReportScope::Protocol,
                            "message.recipients.missing",
                            "Message has no recipients",
                            vec![],
                            StatusCode::INTERNAL_SERVER_ERROR,
                        ));
                    };
                    debug!(
                        "response to_dids: count({}) vec({:?})",
                        to_dids.len(),
                        to_dids
                    );

                    if to_dids.len() > state.config.limits.to_recipients {
                        return Err(MediatorError::problem_with_log(
                            76,
                            &session.session_id,
                            Some(message.id.to_string()),
                            ProblemReportSorter::Error,
                            ProblemReportScope::Protocol,
                            "message.recipients.too_many",
                            "Message has too many recipients ({1}). Max: {2}",
                            vec![
                                to_dids.len().to_string(),
                                state.config.limits.to_recipients.to_string(),
                            ],
                            StatusCode::INTERNAL_SERVER_ERROR,
                            format!(
                                "Message has too many recipients ({}). Max: {}",
                                to_dids.len(),
                                state.config.limits.to_recipients
                            ),
                        ));
                    }

                    let expires_at = if let Some(expires_at) = message.expires_time {
                        let now = unix_timestamp_secs();

                        if expires_at > now + state.config.limits.message_expiry_seconds {
                            now + state.config.limits.message_expiry_seconds
                        } else {
                            expires_at
                        }
                    } else {
                        unix_timestamp_secs() + state.config.limits.message_expiry_seconds
                    };

                    for recipient in to_dids {
                        let (packed, _): (String, PackEncryptedMetadata) = message
                            .pack(
                                &session.session_id,
                                recipient,
                                &state.config.mediator_did,
                                metadata,
                                &*state.config.security.mediator_secrets,
                                &state.did_resolver,
                                &PackOptions {
                                    to_keys_per_recipient_limit: state
                                        .config
                                        .limits
                                        .to_keys_per_recipient,
                                    forward: true,
                                },
                                &state.config.processors.forwarding.blocked_forwarding,
                            )
                            .await?;

                        match _store_message(
                            state,
                            session,
                            response,
                            &packed,
                            &digest(recipient),
                            Some(&state.config.mediator_did_hash),
                            expires_at,
                        )
                        .await
                        {
                            Ok(msg_id) => {
                                debug!(
                                    "message id({}) stored successfully recipient({})",
                                    msg_id, recipient
                                );
                                stored_messages
                                    .messages
                                    .push((recipient.to_string(), msg_id));
                            }
                            Err(e) => {
                                warn!("error storing message recipient({}): {:?}", recipient, e);
                                stored_messages
                                    .errors
                                    .push((recipient.to_string(), e.to_string()));
                            }
                        }
                    }
                }
                WrapperType::Envelope(to_did, message, expiry) => {
                    // Message is already packed, likely a direct delivery from a client
                    match _store_message(
                        state,
                        session,
                        response,
                        message,
                        &digest(to_did),
                        Some(&session.did_hash),
                        *expiry,
                    )
                    .await
                    {
                        Ok(msg_id) => {
                            debug!(
                                "message id({}) stored successfully recipient({})",
                                msg_id, to_did
                            );
                            stored_messages.messages.push((to_did.to_string(), msg_id));
                        }
                        Err(e) => {
                            warn!("error storing message recipient({}): {:?}", to_did, e);
                            stored_messages
                                .errors
                                .push((to_did.to_string(), e.to_string()));
                        }
                    }
                }
            }

            return Ok(InboundMessageResponse::Stored(stored_messages));
        }

        // Ephemeral message handling (DIDComm only)
        #[cfg(feature = "didcomm")]
        if let WrapperType::Message(message) = &response.data {
            let (packed, meta) = message
                .pack(
                    &session.session_id,
                    &session.did,
                    &state.config.mediator_did,
                    metadata,
                    &*state.config.security.mediator_secrets,
                    &state.did_resolver,
                    &PackOptions {
                        to_keys_per_recipient_limit: state.config.limits.to_keys_per_recipient,
                        forward: true,
                    },
                    &state.config.processors.forwarding.blocked_forwarding,
                )
                .await?;
            // NOTE: messaging_service forwarding check removed during migration to affinidi-messaging-didcomm.
            // The new pack implementation does not auto-forward via service endpoints.
            trace!("Ephemeral message packed (meta):\n{:#?}", meta);
            trace!("Ephemeral message (msg):\n{:#?}", packed);
            // Live stream the message?
            if let Some(stream_uuid) = state
                .database
                .streaming_is_client_live(&session.did_hash, response.force_live_delivery)
                .await
            {
                _live_stream(
                    &state.database,
                    &session.did_hash,
                    &stream_uuid,
                    &packed,
                    response.force_live_delivery,
                )
                .await;
            }
            return Ok(InboundMessageResponse::Ephemeral(packed));
        }

        error!("No message to return");
        Err(MediatorError::problem(
            77,
            &session.session_id,
            None,
            ProblemReportSorter::Error,
            ProblemReportScope::Protocol,
            "me.storage.message.error",
            "Expected a message to store, instead got None",
            vec![],
            StatusCode::INTERNAL_SERVER_ERROR,
        ))
    }
    .instrument(_span)
    .await
}

/// If live streaming is enabled, this function will send the message to the live stream
/// Ok to ignore errors here
async fn _live_stream(
    database: &Arc<dyn MediatorStore>,
    did_hash: &str,
    stream_uuid: &str,
    message: &str,
    force_live_delivery: bool,
) {
    if database
        .streaming_publish_message(did_hash, stream_uuid, message, force_live_delivery)
        .await
        .is_ok()
    {
        debug!("Live streaming message to UUID: {}", stream_uuid);
    }
}

/// Stores a message in the mediator's database that has been forwarded
/// handles a lot of higher order logic for storing messages
/// - state: SharedData
/// - session: Session
/// - response: ProcessMessageResponse
/// - metadata: UnpackMetadata
/// - expires_at: Option<u64>
///   - None: use default expiry
///   - Some: use the provided expiry time in seconds
pub(crate) async fn store_forwarded_message(
    state: &SharedData,
    session: &Session,
    message: &str,
    sender_hash: Option<&str>,
    recipient: &str,
    expires_at: Option<u64>,
) -> Result<(), MediatorError> {
    let _span = span!(
        tracing::Level::DEBUG,
        "store_forwarded_message",
        session = session.session_id
    );

    async move {
        let recipient_did_hash = digest(recipient);
        // Live stream the message?
        if let Some(stream_uuid) = state
            .database
            .streaming_is_client_live(&recipient_did_hash, false)
            .await
        {
            _live_stream(
                &state.database,
                &recipient_did_hash,
                &stream_uuid,
                message,
                false,
            )
            .await;
            debug!("Live streaming message to did_hash: {}", recipient_did_hash);
        }

        let expires_at = if let Some(expires_at) = expires_at {
            let now = unix_timestamp_secs();

            if expires_at > now + state.config.limits.message_expiry_seconds {
                now + state.config.limits.message_expiry_seconds
            } else {
                expires_at
            }
        } else {
            unix_timestamp_secs() + state.config.limits.message_expiry_seconds
        };

        match state
            .database
            .store_message(
                &session.session_id,
                message,
                &recipient_did_hash,
                sender_hash,
                expires_at,
                state.config.limits.queued_receive_messages_hard as usize,
            )
            .await
        {
            Ok(msg_id) => {
                debug!(
                    "message id({}) stored successfully recipient({})",
                    msg_id, recipient
                );
            }
            Err(e) => {
                warn!("error storing message recipient({}): {:?}", recipient, e);
            }
        }

        Ok(())
    }
    .instrument(_span)
    .await
}
