* this using dc_msg_get_width() / dc_msg_get_height().
 *
 * See also dc_msg_get_duration().
 *
 * @memberof dc_msg_t
 * @param msg The message object.
 * @return Width in pixels, if applicable. 0 otherwise or if unknown.
 */
int dc_msg_get_width(const dc_msg_t* msg)
{
	if (msg==NULL || msg->magic!=DC_MSG_MAGIC) {
		return 0;
	}
	return dc_param_get_int(msg->param, DC_PARAM_WIDTH, 0);
}

pub fn get_width(&self) -> i32 {
        self.param.get_int(Param::Width).unwrap_or_default()
    }

pub fn get_int(&self, key: Param) -> Option<i32> {
        self.get(key).and_then(|s| s.parse().ok())
    }

pub struct Message {
    /// Message ID.
    pub(crate) id: MsgId,

    /// `From:` contact ID.
    pub(crate) from_id: ContactId,

    /// ID of the first contact in the `To:` header.
    pub(crate) to_id: ContactId,

    /// ID of the chat message belongs to.
    pub(crate) chat_id: ChatId,

    /// Type of the message.
    pub(crate) viewtype: Viewtype,

    /// State of the message.
    pub(crate) state: MessageState,
    pub(crate) download_state: DownloadState,

    /// Whether the message is hidden.
    pub(crate) hidden: bool,
    pub(crate) timestamp_sort: i64,
    pub(crate) timestamp_sent: i64,
    pub(crate) timestamp_rcvd: i64,
    pub(crate) ephemeral_timer: EphemeralTimer,
    pub(crate) ephemeral_timestamp: i64,
    pub(crate) text: String,

    /// Message subject.
    ///
    /// If empty, a default subject will be generated when sending.
    pub(crate) subject: String,

    /// `Message-ID` header value.
    pub(crate) rfc724_mid: String,

    /// `In-Reply-To` header value.
    pub(crate) in_reply_to: Option<String>,
    pub(crate) is_dc_message: MessengerMessage,
    pub(crate) mime_modified: bool,
    pub(crate) chat_blocked: Blocked,
    pub(crate) location_id: u32,
    pub(crate) error: Option<String>,
    pub(crate) param: Params,
}

pub enum Param {
    /// For messages
    File = b'f',

    /// For messages: original filename (as shown in chat)
    Filename = b'v',

    /// For messages: This name should be shown instead of contact.get_display_name()
    /// (used if this is a mailinglist
    /// or explicitly set using set_override_sender_name(), eg. by bots)
    OverrideSenderDisplayname = b'O',

    /// For Messages
    Width = b'w',

    /// For Messages
    Height = b'h',

    /// For Messages
    Duration = b'd',

    /// For Messages
    MimeType = b'm',

    /// For Messages: HTML to be written to the database and to be send.
    /// `SendHtml` param is not used for received messages.
    /// Use `MsgId::get_html()` to get HTML of received messages.
    SendHtml = b'T',

    /// For Messages: message is encrypted, outgoing: guarantee E2EE or the message is not send
    GuaranteeE2ee = b'c',

    /// For Messages: quoted message is encrypted.
    ///
    /// If this message is sent unencrypted, quote text should be replaced.
    ProtectQuote = b'0',

    /// For Messages: decrypted with validation errors or without mutual set, if neither
    /// 'c' nor 'e' are preset, the messages is only transport encrypted.
    ErroneousE2ee = b'e',

    /// For Messages: force unencrypted message, a value from `ForcePlaintext` enum.
    ForcePlaintext = b'u',

    /// For Messages: do not include Autocrypt header.
    SkipAutocrypt = b'o',

    /// For Messages
    WantsMdn = b'r',

    /// For Messages: the message is a reaction.
    Reaction = b'x',

    /// For Chats: the timestamp of the last reaction.
    LastReactionTimestamp = b'y',

    /// For Chats: Message ID of the last reaction.
    LastReactionMsgId = b'Y',

    /// For Chats: Contact ID of the last reaction.
    LastReactionContactId = b'1',

    /// For Messages: a message with "Auto-Submitted: auto-generated" header ("bot").
    Bot = b'b',

    /// For Messages: unset or 0=not forwarded,
    /// 1=forwarded from unknown msg_id, >9 forwarded from msg_id
    Forwarded = b'a',

    /// For Messages: quoted text.
    Quote = b'q',

    /// For Messages
    Cmd = b'S',

    /// For Messages
    Arg = b'E',

    /// For Messages
    Arg2 = b'F',

    /// `Secure-Join-Fingerprint` header for `{vc,vg}-request-with-auth` messages.
    Arg3 = b'G',

    /// Deprecated `Secure-Join-Group` header for messages.
    Arg4 = b'H',

    /// For Messages
    AttachGroupImage = b'A',

    /// For Messages
    WebrtcRoom = b'V',

    /// For Messages: space-separated list of messaged IDs of forwarded copies.
    ///
    /// This is used when a [crate::message::Message] is in the
    /// [crate::message::MessageState::OutPending] state but is already forwarded.
    /// In this case the forwarded messages are written to the
    /// database and their message IDs are added to this parameter of
    /// the original message, which is also saved in the database.
    /// When the original message is then finally sent this parameter
    /// is used to also send all the forwarded messages.
    PrepForwards = b'P',

    /// For Messages
    SetLatitude = b'l',

    /// For Messages
    SetLongitude = b'n',

    /// For Groups
    ///
    /// An unpromoted group has not had any messages sent to it and thus only exists on the
    /// creator's device.  Any changes made to an unpromoted group do not need to send
    /// system messages to the group members to update them of the changes.  Once a message
    /// has been sent to a group it is promoted and group changes require sending system
    /// messages to all members.
    Unpromoted = b'U',

    /// For Groups and Contacts
    ProfileImage = b'i',

    /// For Chats
    /// Signals whether the chat is the `saved messages` chat
    Selftalk = b'K',

    /// For Chats: On sending a new message we set the subject to `Re: <last subject>`.
    /// Usually we just use the subject of the parent message, but if the parent message
    /// is deleted, we use the LastSubject of the chat.
    LastSubject = b't',

    /// For Chats
    Devicetalk = b'D',

    /// For Chats: If this is a mailing list chat, contains the List-Post address.
    /// None if there simply is no `List-Post` header in the mailing list.
    /// Some("") if the mailing list is using multiple different List-Post headers.
    ///
    /// The List-Post address is the email address where the user can write to in order to
    /// post something to the mailing list.
    ListPost = b'p',

    /// For Contacts: If this is the List-Post address of a mailing list, contains
    /// the List-Id of the mailing list (which is also used as the group id of the chat).
    ListId = b's',

    /// For Contacts: timestamp of status (aka signature or footer) update.
    StatusTimestamp = b'j',

    /// For Contacts and Chats: timestamp of avatar update.
    AvatarTimestamp = b'J',

    /// For Chats: timestamp of status/signature/footer update.
    EphemeralSettingsTimestamp = b'B',

    /// For Chats: timestamp of subject update.
    SubjectTimestamp = b'C',

    /// For Chats: timestamp of group name update.
    GroupNameTimestamp = b'g',

    /// For Chats: timestamp of member list update.
    MemberListTimestamp = b'k',

    /// For Webxdc Message Instances: Current document name
    WebxdcDocument = b'R',

    /// For Webxdc Message Instances: timestamp of document name update.
    WebxdcDocumentTimestamp = b'W',

    /// For Webxdc Message Instances: Current summary
    WebxdcSummary = b'N',

    /// For Webxdc Message Instances: timestamp of summary update.
    WebxdcSummaryTimestamp = b'Q',

    /// For Webxdc Message Instances: Webxdc is an integration, see init_webxdc_integration()
    WebxdcIntegration = b'3',

    /// For Webxdc Message Instances: Chat to integrate the Webxdc for.
    WebxdcIntegrateFor = b'2',

    /// For messages: Whether [crate::message::Viewtype::Sticker] should be forced.
    ForceSticker = b'X',
    // 'L' was defined as ProtectionSettingsTimestamp for Chats, however, never used in production.
}


* Same as dc_contact_is_verified() but allows speeding up things
 * by adding the peerstate belonging to the contact.
 * If you do not have the peerstate available, it is loaded automatically.
 *
 * @private @memberof dc_context_t
 */
int dc_contact_is_verified_ex(dc_contact_t* contact, const dc_apeerstate_t* peerstate)
{
	int              contact_verified = DC_NOT_VERIFIED;
	dc_apeerstate_t* peerstate_to_delete = NULL;

	if (contact==NULL || contact->magic!=DC_CONTACT_MAGIC) {
		goto cleanup;
	}

	if (contact->id==DC_CONTACT_ID_SELF) {
		contact_verified = DC_BIDIRECT_VERIFIED;
		goto cleanup; // we're always sort of secured-verified as we could verify the key on this device any time with the key on this device
	}

	if (peerstate==NULL) {
		peerstate_to_delete = dc_apeerstate_new(contact->context);
		if (!dc_apeerstate_load_by_addr(peerstate_to_delete, contact->context->sql, contact->addr)) {
			goto cleanup;
		}
		peerstate = peerstate_to_delete;
	}

	contact_verified = peerstate->verified_key? DC_BIDIRECT_VERIFIED : 0;

cleanup:
	dc_apeerstate_unref(peerstate_to_delete);
	return contact_verified;
}

pub async fn is_verified(&self, context: &Context) -> Result<bool> {
        // We're always sort of secured-verified as we could verify the key on this device any time with the key
        // on this device
        if self.id == ContactId::SELF {
            return Ok(true);
        }

        let Some(peerstate) = Peerstate::from_addr(context, &self.addr).await? else {
            return Ok(false);
        };

        let forward_verified = peerstate.is_using_verified_key();
        let backward_verified = peerstate.is_backward_verified(context).await?;
        Ok(forward_verified && backward_verified)
    }

pub(crate) async fn is_backward_verified(&self, context: &Context) -> Result<bool> {
        let Some(backward_verified_key_id) = self.backward_verified_key_id else {
            return Ok(false);
        };

        let self_key_id = context.get_config_i64(Config::KeyId).await?;

        let backward_verified = backward_verified_key_id == self_key_id;
        Ok(backward_verified)
    }

pub(crate) fn is_using_verified_key(&self) -> bool {
        let verified = self.peek_key_fingerprint(true);

        verified.is_some() && verified == self.peek_key_fingerprint(false)
    }

pub struct Context {
    pub(crate) inner: Arc<InnerContext>,
}

pub async fn from_addr(context: &Context, addr: &str) -> Result<Option<Peerstate>> {
        if context.is_self_addr(addr).await? {
            return Ok(None);
        }
        let query = "SELECT addr, last_seen, last_seen_autocrypt, prefer_encrypted, public_key, \
                     gossip_timestamp, gossip_key, public_key_fingerprint, gossip_key_fingerprint, \
                     verified_key, verified_key_fingerprint, \
                     verifier, \
                     secondary_verified_key, secondary_verified_key_fingerprint, \
                     secondary_verifier, \
                     backward_verified_key_id \
                     FROM acpeerstates \
                     WHERE addr=? COLLATE NOCASE LIMIT 1;";
        Self::from_stmt(context, query, (addr,)).await
    }

pub struct Contact {
    /// The contact ID.
    pub id: ContactId,

    /// Contact name. It is recommended to use `Contact::get_name`,
    /// `Contact::get_display_name` or `Contact::get_name_n_addr` to access this field.
    /// May be empty, initially set to `authname`.
    name: String,

    /// Name authorized by the contact himself. Only this name may be spread to others,
    /// e.g. in To:-lists. May be empty. It is recommended to use `Contact::get_authname`,
    /// to access this field.
    authname: String,

    /// E-Mail-Address of the contact. It is recommended to use `Contact::get_addr` to access this field.
    addr: String,

    /// Blocked state. Use contact_is_blocked to access this field.
    pub blocked: bool,

    /// Time when the contact was seen last time, Unix time in seconds.
    last_seen: i64,

    /// The origin/source of the contact.
    pub origin: Origin,

    /// Parameters as Param::ProfileImage
    pub param: Params,

    /// Last seen message signature for this contact, to be displayed in the profile.
    status: String,

    /// If the contact is a bot.
    is_bot: bool,
}

pub struct ContactId(u32);

impl ContactId {
    /// Undefined contact. Used as a placeholder for trashed messages.
    pub const UNDEFINED: ContactId = ContactId::new(0);

    /// The owner of the account.
    ///
    /// The email-address is set by `set_config` using "addr".
    pub const SELF: ContactId = ContactId::new(1);

    /// ID of the contact for info messages.
    pub const INFO: ContactId = ContactId::new(2);

    /// ID of the contact for device messages.
    pub const DEVICE: ContactId = ContactId::new(5);
    pub(crate) const LAST_SPECIAL: ContactId = ContactId::new(9);

    /// Address to go with [`ContactId::DEVICE`].
    ///
    /// This is used by APIs which need to return an email address for this contact.
    pub const DEVICE_ADDR: &'static str = "device@localhost";
}

use std::cmp::{min, Reverse};
use std::collections::BinaryHeap;
use std::fmt;
use std::path::{Path, PathBuf};
use std::time::UNIX_EPOCH;
use anyhow::{bail, ensure, Context as _, Result};
use async_channel::{self as channel, Receiver, Sender};
use base64::Engine as _;
use deltachat_contact_tools::may_be_valid_addr;
use deltachat_contact_tools::{
    self as contact_tools, addr_cmp, addr_normalize, sanitize_name_and_addr, strip_rtlo_characters,
    ContactAddress, VcardContact,
};
use deltachat_derive::{FromSql, ToSql};
use rusqlite::OptionalExtension;
use serde::{Deserialize, Serialize};
use tokio::task;
use tokio::time::{timeout, Duration};
use crate::aheader::{Aheader, EncryptPreference};
use crate::blob::BlobObject;
use crate::chat::{ChatId, ChatIdBlocked, ProtectionStatus};
use crate::color::str_to_color;
use crate::config::Config;
use crate::constants::{Blocked, Chattype, DC_GCL_ADD_SELF, DC_GCL_VERIFIED_ONLY};
use crate::context::Context;
use crate::events::EventType;
use crate::key::{load_self_public_key, DcKey, SignedPublicKey};
use crate::log::LogExt;
use crate::login_param::LoginParam;
use crate::message::MessageState;
use crate::mimeparser::AvatarAction;
use crate::param::{Param, Params};
use crate::peerstate::Peerstate;
use crate::sql::{self, params_iter};
use crate::sync::{self, Sync::*};
use crate::tools::{
    duration_to_str, get_abs_path, improve_single_line_input, smeared_time, time, SystemTime,
};
use crate::{chat, chatlist_events, stock_str};
use deltachat_contact_tools::{may_be_valid_addr, normalize_name};
use super::*;
use crate::chat::{get_chat_contacts, send_text_msg, Chat};
use crate::chatlist::Chatlist;
use crate::receive_imf::receive_imf;
use crate::test_utils::{self, TestContext, TestContextManager};


void dc_apeerstate_degrade_encryption(dc_apeerstate_t* peerstate, time_t message_time)
{
	if (peerstate==NULL) {
		return 0;
	}

	peerstate->prefer_encrypt = DC_PE_RESET;
	peerstate->last_seen      = message_time; /*last_seen_autocrypt is not updated as there was not Autocrypt:-header seen*/

}

pub fn degrade_encryption(&mut self, message_time: i64) {
        self.prefer_encrypt = EncryptPreference::Reset;
        self.last_seen = message_time;
    }

pub struct Peerstate {
    /// E-mail address of the contact.
    pub addr: String,

    /// Timestamp of the latest peerstate update.
    ///
    /// Updated when a message is received from a contact,
    /// either with or without `Autocrypt` header.
    pub last_seen: i64,

    /// Timestamp of the latest `Autocrypt` header reception.
    pub last_seen_autocrypt: i64,

    /// Encryption preference of the contact.
    pub prefer_encrypt: EncryptPreference,

    /// Public key of the contact received in `Autocrypt` header.
    pub public_key: Option<SignedPublicKey>,

    /// Fingerprint of the contact public key.
    pub public_key_fingerprint: Option<Fingerprint>,

    /// Public key of the contact received in `Autocrypt-Gossip` header.
    pub gossip_key: Option<SignedPublicKey>,

    /// Timestamp of the latest `Autocrypt-Gossip` header reception.
    ///
    /// It is stored to avoid applying outdated gossiped key
    /// from delayed or reordered messages.
    pub gossip_timestamp: i64,

    /// Fingerprint of the contact gossip key.
    pub gossip_key_fingerprint: Option<Fingerprint>,

    /// Public key of the contact at the time it was verified,
    /// either directly or via gossip from the verified contact.
    pub verified_key: Option<SignedPublicKey>,

    /// Fingerprint of the verified public key.
    pub verified_key_fingerprint: Option<Fingerprint>,

    /// The address that introduced this verified key.
    pub verifier: Option<String>,

    /// Secondary public verified key of the contact.
    /// It could be a contact gossiped by another verified contact in a shared group
    /// or a key that was previously used as a verified key.
    pub secondary_verified_key: Option<SignedPublicKey>,

    /// Fingerprint of the secondary verified public key.
    pub secondary_verified_key_fingerprint: Option<Fingerprint>,

    /// The address that introduced secondary verified key.
    pub secondary_verifier: Option<String>,

    /// Row ID of the key in the `keypairs` table
    /// that we think the peer knows as verified.
    pub backward_verified_key_id: Option<i64>,

    /// True if it was detected
    /// that the fingerprint of the key used in chats with
    /// opportunistic encryption was changed after Peerstate creation.
    pub fingerprint_changed: bool,
}

pub enum EncryptPreference {
    #[default]
    NoPreference = 0,
    Mutual = 1,
    Reset = 20,
}

use std::mem;
use anyhow::{Context as _, Error, Result};
use deltachat_contact_tools::{addr_cmp, ContactAddress};
use num_traits::FromPrimitive;
use crate::aheader::{Aheader, EncryptPreference};
use crate::chat::{self, Chat};
use crate::chatlist::Chatlist;
use crate::config::Config;
use crate::constants::Chattype;
use crate::contact::{Contact, Origin};
use crate::context::Context;
use crate::events::EventType;
use crate::key::{DcKey, Fingerprint, SignedPublicKey};
use crate::message::Message;
use crate::mimeparser::SystemMessage;
use crate::sql::Sql;
use crate::{chatlist_events, stock_str};
use super::*;
use crate::test_utils::alice_keypair;


void dc_imex(dc_context_t* context, int what, const char* param1, const char* param2)
{
	dc_param_t* param = dc_param_new();

	dc_param_set_int(param, DC_PARAM_CMD,      what);
	dc_param_set    (param, DC_PARAM_CMD_ARG,  param1);
	dc_param_set    (param, DC_PARAM_CMD_ARG2, param2);

	dc_job_kill_action(context, DC_JOB_IMEX_IMAP);
	dc_job_add(context, DC_JOB_IMEX_IMAP, 0, param->packed, 0); // results in a call to dc_job_do_DC_JOB_IMEX_IMAP()

	dc_param_unref(param);
}

pub async fn imex(
    context: &Context,
    what: ImexMode,
    path: &Path,
    passphrase: Option<String>,
) -> Result<()> {
    let cancel = context.alloc_ongoing().await?;

    let res = {
        let _guard = context.scheduler.pause(context.clone()).await?;
        imex_inner(context, what, path, passphrase)
            .race(async {
                cancel.recv().await.ok();
                Err(format_err!("canceled"))
            })
            .await
    };
    context.free_ongoing().await;

    if let Err(err) = res.as_ref() {
        // We are using Anyhow's .context() and to show the inner error, too, we need the {:#}:
        error!(context, "IMEX failed to complete: {:#}", err);
        context.emit_event(EventType::ImexProgress(0));
    } else {
        info!(context, "IMEX successfully completed");
        context.emit_event(EventType::ImexProgress(1000));
    }

    res
}

pub(crate) async fn free_ongoing(&self) {
        let mut s = self.running_state.write().await;
        if let RunningState::ShallStop { request } = *s {
            info!(self, "Ongoing stopped in {:?}", time_elapsed(&request));
        }
        *s = RunningState::Stopped;
    }

async fn imex_inner(
    context: &Context,
    what: ImexMode,
    path: &Path,
    passphrase: Option<String>,
) -> Result<()> {
    info!(
        context,
        "{} path: {}",
        match what {
            ImexMode::ExportSelfKeys | ImexMode::ExportBackup => "Export",
            ImexMode::ImportSelfKeys | ImexMode::ImportBackup => "Import",
        },
        path.display()
    );
    ensure!(context.sql.is_open().await, "Database not opened.");
    context.emit_event(EventType::ImexProgress(10));

    if what == ImexMode::ExportBackup || what == ImexMode::ExportSelfKeys {
        // before we export anything, make sure the private key exists
        e2ee::ensure_secret_key_exists(context)
            .await
            .context("Cannot create private key or private key not available")?;

        create_folder(context, &path).await?;
    }

    match what {
        ImexMode::ExportSelfKeys => export_self_keys(context, path).await,
        ImexMode::ImportSelfKeys => import_self_keys(context, path).await,

        ImexMode::ExportBackup => {
            export_backup(context, path, passphrase.unwrap_or_default()).await
        }
        ImexMode::ImportBackup => {
            import_backup(context, path, passphrase.unwrap_or_default()).await
        }
    }
}

pub(crate) async fn alloc_ongoing(&self) -> Result<Receiver<()>> {
        let mut s = self.running_state.write().await;
        ensure!(
            matches!(*s, RunningState::Stopped),
            "There is already another ongoing process running."
        );

        let (sender, receiver) = channel::bounded(1);
        *s = RunningState::Running {
            cancel_sender: sender,
        };

        Ok(receiver)
    }

pub async fn recv(&self) -> Option<Event> {
        let mut lock = self.0.lock().await;
        match lock.recv().await {
            Err(async_broadcast::RecvError::Overflowed(n)) => Some(Event {
                id: 0,
                typ: EventType::EventChannelOverflow { n },
            }),
            Err(async_broadcast::RecvError::Closed) => None,
            Ok(event) => Some(event),
        }
    }

macro_rules! error {
    ($ctx:expr, $msg:expr) => {
        error!($ctx, $msg,)
    };
    ($ctx:expr, $msg:expr, $($args:expr),* $(,)?) => {{
        let formatted = format!($msg, $($args),*);
        $ctx.set_last_error(&formatted);
        $ctx.emit_event($crate::EventType::Error(formatted));
    }};
}

macro_rules! anyhow {
    ($msg:literal $(,)?) => {
        $crate::__private::must_use({
            let error = $crate::__private::format_err($crate::__private::format_args!($msg));
            error
        })
    };
    ($err:expr $(,)?) => {
        $crate::__private::must_use({
            use $crate::__private::kind::*;
            let error = match $err {
                error => (&error).anyhow_kind().new(error),
            };
            error
        })
    };
    ($fmt:expr, $($arg:tt)*) => {
        $crate::Error::msg($crate::__private::format!($fmt, $($arg)*))
    };
}

macro_rules! info {
    ($ctx:expr,  $msg:expr) => {
        info!($ctx, $msg,)
    };
    ($ctx:expr, $msg:expr, $($args:expr),* $(,)?) => {{
        let formatted = format!($msg, $($args),*);
        let full = format!("{file}:{line}: {msg}",
                           file = file!(),
                           line = line!(),
                           msg = &formatted);
        $ctx.emit_event($crate::EventType::Info(full));
    }};
}

pub fn emit_event(&self, event: EventType) {
        {
            let lock = self.debug_logging.read().expect("RwLock is poisoned");
            if let Some(debug_logging) = &*lock {
                debug_logging.log_event(event.clone());
            }
        }
        self.events.emit(Event {
            id: self.id,
            typ: event,
        });
    }

pub struct Context {
    pub(crate) inner: Arc<InnerContext>,
}

pub enum EventType {
    /// The library-user may write an informational string to the log.
    ///
    /// This event should *not* be reported to the end-user using a popup or something like
    /// that.
    Info(String),

    /// Emitted when SMTP connection is established and login was successful.
    SmtpConnected(String),

    /// Emitted when IMAP connection is established and login was successful.
    ImapConnected(String),

    /// Emitted when a message was successfully sent to the SMTP server.
    SmtpMessageSent(String),

    /// Emitted when an IMAP message has been marked as deleted
    ImapMessageDeleted(String),

    /// Emitted when an IMAP message has been moved
    ImapMessageMoved(String),

    /// Emitted before going into IDLE on the Inbox folder.
    ImapInboxIdle,

    /// Emitted when an new file in the $BLOBDIR was created
    NewBlobFile(String),

    /// Emitted when an file in the $BLOBDIR was deleted
    DeletedBlobFile(String),

    /// The library-user should write a warning string to the log.
    ///
    /// This event should *not* be reported to the end-user using a popup or something like
    /// that.
    Warning(String),

    /// The library-user should report an error to the end-user.
    ///
    /// As most things are asynchronous, things may go wrong at any time and the user
    /// should not be disturbed by a dialog or so.  Instead, use a bubble or so.
    ///
    /// However, for ongoing processes (eg. configure())
    /// or for functions that are expected to fail (eg. dc_continue_key_transfer())
    /// it might be better to delay showing these events until the function has really
    /// failed (returned false). It should be sufficient to report only the *last* error
    /// in a messasge box then.
    Error(String),

    /// An action cannot be performed because the user is not in the group.
    /// Reported eg. after a call to
    /// dc_set_chat_name(), dc_set_chat_profile_image(),
    /// dc_add_contact_to_chat(), dc_remove_contact_from_chat(),
    /// dc_send_text_msg() or another sending function.
    ErrorSelfNotInGroup(String),

    /// Messages or chats changed.  One or more messages or chats changed for various
    /// reasons in the database:
    /// - Messages sent, received or removed
    /// - Chats created, deleted or archived
    /// - A draft has been set
    ///
    MsgsChanged {
        /// Set if only a single chat is affected by the changes, otherwise 0.
        chat_id: ChatId,

        /// Set if only a single message is affected by the changes, otherwise 0.
        msg_id: MsgId,
    },

    /// Reactions for the message changed.
    ReactionsChanged {
        /// ID of the chat which the message belongs to.
        chat_id: ChatId,

        /// ID of the message for which reactions were changed.
        msg_id: MsgId,

        /// ID of the contact whose reaction set is changed.
        contact_id: ContactId,
    },

    /// There is a fresh message. Typically, the user will show an notification
    /// when receiving this message.
    ///
    /// There is no extra #DC_EVENT_MSGS_CHANGED event send together with this event.
    IncomingMsg {
        /// ID of the chat where the message is assigned.
        chat_id: ChatId,

        /// ID of the message.
        msg_id: MsgId,
    },

    /// Downloading a bunch of messages just finished.
    IncomingMsgBunch,

    /// Messages were seen or noticed.
    /// chat id is always set.
    MsgsNoticed(ChatId),

    /// A single message is sent successfully. State changed from  DC_STATE_OUT_PENDING to
    /// DC_STATE_OUT_DELIVERED, see dc_msg_get_state().
    MsgDelivered {
        /// ID of the chat which the message belongs to.
        chat_id: ChatId,

        /// ID of the message that was successfully sent.
        msg_id: MsgId,
    },

    /// A single message could not be sent. State changed from DC_STATE_OUT_PENDING or DC_STATE_OUT_DELIVERED to
    /// DC_STATE_OUT_FAILED, see dc_msg_get_state().
    MsgFailed {
        /// ID of the chat which the message belongs to.
        chat_id: ChatId,

        /// ID of the message that could not be sent.
        msg_id: MsgId,
    },

    /// A single message is read by the receiver. State changed from DC_STATE_OUT_DELIVERED to
    /// DC_STATE_OUT_MDN_RCVD, see dc_msg_get_state().
    MsgRead {
        /// ID of the chat which the message belongs to.
        chat_id: ChatId,

        /// ID of the message that was read.
        msg_id: MsgId,
    },

    /// A single message was deleted.
    ///
    /// This event means that the message will no longer appear in the messagelist.
    /// UI should remove the message from the messagelist
    /// in response to this event if the message is currently displayed.
    ///
    /// The message may have been explicitly deleted by the user or expired.
    /// Internally the message may have been removed from the database,
    /// moved to the trash chat or hidden.
    ///
    /// This event does not indicate the message
    /// deletion from the server.
    MsgDeleted {
        /// ID of the chat where the message was prior to deletion.
        /// Never 0 or trash chat.
        chat_id: ChatId,

        /// ID of the deleted message. Never 0.
        msg_id: MsgId,
    },

    /// Chat changed.  The name or the image of a chat group was changed or members were added or removed.
    /// Or the verify state of a chat has changed.
    /// See dc_set_chat_name(), dc_set_chat_profile_image(), dc_add_contact_to_chat()
    /// and dc_remove_contact_from_chat().
    ///
    /// This event does not include ephemeral timer modification, which
    /// is a separate event.
    ChatModified(ChatId),

    /// Chat ephemeral timer changed.
    ChatEphemeralTimerModified {
        /// Chat ID.
        chat_id: ChatId,

        /// New ephemeral timer value.
        timer: EphemeralTimer,
    },

    /// Contact(s) created, renamed, blocked, deleted or changed their "recently seen" status.
    ///
    /// @param data1 (int) If set, this is the contact_id of an added contact that should be selected.
    ContactsChanged(Option<ContactId>),

    /// Location of one or more contact has changed.
    ///
    /// @param data1 (u32) contact_id of the contact for which the location has changed.
    ///     If the locations of several contacts have been changed,
    ///     eg. after calling dc_delete_all_locations(), this parameter is set to `None`.
    LocationChanged(Option<ContactId>),

    /// Inform about the configuration progress started by configure().
    ConfigureProgress {
        /// Progress.
        ///
        /// 0=error, 1-999=progress in permille, 1000=success and done
        progress: usize,

        /// Progress comment or error, something to display to the user.
        comment: Option<String>,
    },

    /// Inform about the import/export progress started by imex().
    ///
    /// @param data1 (usize) 0=error, 1-999=progress in permille, 1000=success and done
    /// @param data2 0
    ImexProgress(usize),

    /// A file has been exported. A file has been written by imex().
    /// This event may be sent multiple times by a single call to imex().
    ///
    /// A typical purpose for a handler of this event may be to make the file public to some system
    /// services.
    ///
    /// @param data2 0
    ImexFileWritten(PathBuf),

    /// Progress information of a secure-join handshake from the view of the inviter
    /// (Alice, the person who shows the QR code).
    ///
    /// These events are typically sent after a joiner has scanned the QR code
    /// generated by dc_get_securejoin_qr().
    SecurejoinInviterProgress {
        /// ID of the contact that wants to join.
        contact_id: ContactId,

        /// Progress as:
        /// 300=vg-/vc-request received, typically shown as "bob@addr joins".
        /// 600=vg-/vc-request-with-auth received, vg-member-added/vc-contact-confirm sent, typically shown as "bob@addr verified".
        /// 800=contact added to chat, shown as "bob@addr securely joined GROUP". Only for the verified-group-protocol.
        /// 1000=Protocol finished for this contact.
        progress: usize,
    },

    /// Progress information of a secure-join handshake from the view of the joiner
    /// (Bob, the person who scans the QR code).
    /// The events are typically sent while dc_join_securejoin(), which
    /// may take some time, is executed.
    SecurejoinJoinerProgress {
        /// ID of the inviting contact.
        contact_id: ContactId,

        /// Progress as:
        /// 400=vg-/vc-request-with-auth sent, typically shown as "alice@addr verified, introducing myself."
        /// (Bob has verified alice and waits until Alice does the same for him)
        /// 1000=vg-member-added/vc-contact-confirm received
        progress: usize,
    },

    /// The connectivity to the server changed.
    /// This means that you should refresh the connectivity view
    /// and possibly the connectivtiy HTML; see dc_get_connectivity() and
    /// dc_get_connectivity_html() for details.
    ConnectivityChanged,

    /// The user's avatar changed.
    /// Deprecated by `ConfigSynced`.
    SelfavatarChanged,

    /// A multi-device synced config value changed. Maybe the app needs to refresh smth. For
    /// uniformity this is emitted on the source device too. The value isn't here, otherwise it
    /// would be logged which might not be good for privacy.
    ConfigSynced {
        /// Configuration key.
        key: Config,
    },

    /// Webxdc status update received.
    WebxdcStatusUpdate {
        /// Message ID.
        msg_id: MsgId,

        /// Status update ID.
        status_update_serial: StatusUpdateSerial,
    },

    /// Data received over an ephemeral peer channel.
    WebxdcRealtimeData {
        /// Message ID.
        msg_id: MsgId,

        /// Realtime data.
        data: Vec<u8>,
    },

    /// Inform that a message containing a webxdc instance has been deleted.
    WebxdcInstanceDeleted {
        /// ID of the deleted message.
        msg_id: MsgId,
    },

    /// Tells that the Background fetch was completed (or timed out).
    /// This event acts as a marker, when you reach this event you can be sure
    /// that all events emitted during the background fetch were processed.
    ///
    /// This event is only emitted by the account manager
    AccountsBackgroundFetchDone,
    /// Inform that set of chats or the order of the chats in the chatlist has changed.
    ///
    /// Sometimes this is emitted together with `UIChatlistItemChanged`.
    ChatlistChanged,

    /// Inform that a single chat list item changed and needs to be rerendered.
    /// If `chat_id` is set to None, then all currently visible chats need to be rerendered, and all not-visible items need to be cleared from cache if the UI has a cache.
    ChatlistItemChanged {
        /// ID of the changed chat
        chat_id: Option<ChatId>,
    },

    /// Event for using in tests, e.g. as a fence between normally generated events.
    #[cfg(test)]
    Test,

    /// Inform than some events have been skipped due to event channel overflow.
    EventChannelOverflow {
        /// Number of events skipped.
        n: u64,
    },
}

use std::any::Any;
use std::ffi::OsStr;
use std::path::{Path, PathBuf};
use ::pgp::types::KeyTrait;
use anyhow::{bail, ensure, format_err, Context as _, Result};
use deltachat_contact_tools::EmailAddress;
use futures::StreamExt;
use futures_lite::FutureExt;
use rand::{thread_rng, Rng};
use tokio::fs::{self, File};
use tokio_tar::Archive;
use crate::blob::{BlobDirContents, BlobObject};
use crate::chat::{self, delete_and_reset_all_device_msgs, ChatId};
use crate::config::Config;
use crate::contact::ContactId;
use crate::context::Context;
use crate::e2ee;
use crate::events::EventType;
use crate::key::{
    self, load_self_secret_key, DcKey, DcSecretKey, SignedPublicKey, SignedSecretKey,
};
use crate::log::LogExt;
use crate::message::{Message, MsgId, Viewtype};
use crate::mimeparser::SystemMessage;
use crate::param::Param;
use crate::pgp;
use crate::sql;
use crate::stock_str;
use crate::tools::{
    create_folder, delete_file, get_filesuffix_lc, open_file_std, read_file, time, write_file,
};
use transfer::{get_backup, BackupProvider};
use std::time::Duration;
use ::pgp::armor::BlockType;
use tokio::task;
use super::*;
use crate::pgp::{split_armored_data, HEADER_AUTOCRYPT, HEADER_SETUPCODE};
use crate::receive_imf::receive_imf;
use crate::stock_str::StockMessage;
use crate::test_utils::{alice_keypair, TestContext, TestContextManager};


char* dc_get_securejoin_qr(dc_context_t* context, uint32_t group_chat_id)
{
	/* =========================================================
	   ====             Alice - the inviter side            ====
	   ====   Step 1 in "Setup verified contact" protocol   ====
	   ========================================================= */

	char*      qr = NULL;
	char*      self_addr = NULL;
	char*      self_addr_urlencoded = NULL;
	char*      self_name = NULL;
	char*      self_name_urlencoded = NULL;
	char*      fingerprint = NULL;
	char*      invitenumber = NULL;
	char*      auth = NULL;
	dc_chat_t* chat = NULL;
	char*      group_name = NULL;
	char*      group_name_urlencoded= NULL;

	if (context==NULL || context->magic!=DC_CONTEXT_MAGIC) {
		goto cleanup;
	}

	dc_ensure_secret_key_exists(context);

		// invitenumber will be used to allow starting the handshake, auth will be used to verify the fingerprint
		invitenumber = dc_token_lookup(context, DC_TOKEN_INVITENUMBER, group_chat_id);
		if (invitenumber==NULL) {
			invitenumber = dc_create_id();
			dc_token_save(context, DC_TOKEN_INVITENUMBER, group_chat_id, invitenumber);
		}

		auth = dc_token_lookup(context, DC_TOKEN_AUTH, group_chat_id);
		if (auth==NULL) {
			auth = dc_create_id();
			dc_token_save(context, DC_TOKEN_AUTH, group_chat_id, auth);
		}

		if ((self_addr = dc_sqlite3_get_config(context->sql, "configured_addr", NULL))==NULL) {
			dc_log_error(context, 0, "Not configured, cannot generate QR code.");
			goto cleanup;
		}

		self_name = dc_sqlite3_get_config(context->sql, "displayname", "");

	if ((fingerprint=get_self_fingerprint(context))==NULL) {
		goto cleanup;
	}

	self_addr_urlencoded = dc_urlencode(self_addr);
	self_name_urlencoded = dc_urlencode(self_name);

	if (group_chat_id)
	{
		// parameters used: a=g=x=i=s=
		chat = dc_get_chat(context, group_chat_id);
		if (chat==NULL) {
			dc_log_error(context, 0, "Cannot get QR-code for chat-id %i", group_chat_id);
			goto cleanup;
		}
		group_name = dc_chat_get_name(chat);
		group_name_urlencoded = dc_urlencode(group_name);
		qr = dc_mprintf(DC_OPENPGP4FPR_SCHEME "%s#a=%s&g=%s&x=%s&i=%s&s=%s", fingerprint, self_addr_urlencoded, group_name_urlencoded, chat->grpid, invitenumber, auth);
	}
	else
	{
		// parameters used: a=n=i=s=
		qr = dc_mprintf(DC_OPENPGP4FPR_SCHEME "%s#a=%s&n=%s&i=%s&s=%s", fingerprint, self_addr_urlencoded, self_name_urlencoded, invitenumber, auth);
	}

	dc_log_info(context, 0, "Generated QR code: %s", qr);

cleanup:
	free(self_addr_urlencoded);
	free(self_addr);
	free(self_name);
	free(self_name_urlencoded);
	free(fingerprint);
	free(invitenumber);
	free(auth);
	dc_chat_unref(chat);
	free(group_name);
	free(group_name_urlencoded);
	return qr? qr : dc_strdup(NULL);
}

pub async fn get_securejoin_qr(context: &Context, group: Option<ChatId>) -> Result<String> {
    /*=======================================================
    ====             Alice - the inviter side            ====
    ====   Step 1 in "Setup verified contact" protocol   ====
    =======================================================*/

    ensure_secret_key_exists(context).await.ok();

    // invitenumber will be used to allow starting the handshake,
    // auth will be used to verify the fingerprint
    let sync_token = token::lookup(context, Namespace::InviteNumber, group)
        .await?
        .is_none();
    let invitenumber = token::lookup_or_new(context, Namespace::InviteNumber, group).await?;
    let auth = token::lookup_or_new(context, Namespace::Auth, group).await?;
    let self_addr = context.get_primary_self_addr().await?;
    let self_name = context
        .get_config(Config::Displayname)
        .await?
        .unwrap_or_default();

    let fingerprint: Fingerprint = match get_self_fingerprint(context).await {
        Some(fp) => fp,
        None => {
            bail!("No fingerprint, cannot generate QR code.");
        }
    };

    let self_addr_urlencoded =
        utf8_percent_encode(&self_addr, NON_ALPHANUMERIC_WITHOUT_DOT).to_string();
    let self_name_urlencoded =
        utf8_percent_encode(&self_name, NON_ALPHANUMERIC_WITHOUT_DOT).to_string();

    let qr = if let Some(group) = group {
        // parameters used: a=g=x=i=s=
        let chat = Chat::load_from_db(context, group).await?;
        if chat.grpid.is_empty() {
            bail!(
                "can't generate securejoin QR code for ad-hoc group {}",
                group
            );
        }
        let group_name = chat.get_name();
        let group_name_urlencoded = utf8_percent_encode(group_name, NON_ALPHANUMERIC).to_string();
        if sync_token {
            context.sync_qr_code_tokens(Some(chat.id)).await?;
        }
        format!(
            "OPENPGP4FPR:{}#a={}&g={}&x={}&i={}&s={}",
            fingerprint.hex(),
            self_addr_urlencoded,
            &group_name_urlencoded,
            &chat.grpid,
            &invitenumber,
            &auth,
        )
    } else {
        // parameters used: a=n=i=s=
        if sync_token {
            context.sync_qr_code_tokens(None).await?;
        }
        format!(
            "OPENPGP4FPR:{}#a={}&n={}&i={}&s={}",
            fingerprint.hex(),
            self_addr_urlencoded,
            self_name_urlencoded,
            &invitenumber,
            &auth,
        )
    };

    info!(context, "Generated QR code.");
    Ok(qr)
}

pub async fn ensure_secret_key_exists(context: &Context) -> Result<()> {
    load_self_public_key(context).await?;
    Ok(())
}

pub async fn lookup(
    context: &Context,
    namespace: Namespace,
    chat: Option<ChatId>,
) -> Result<Option<String>> {
    let token = match chat {
        Some(chat_id) => {
            context
                .sql
                .query_get_value(
                    "SELECT token FROM tokens WHERE namespc=? AND foreign_id=? ORDER BY timestamp DESC LIMIT 1;",
                    (namespace, chat_id),
                )
                .await?
        }
        // foreign_id is declared as `INTEGER DEFAULT 0` in the schema.
        None => {
            context
                .sql
                .query_get_value(
                    "SELECT token FROM tokens WHERE namespc=? AND foreign_id=0 ORDER BY timestamp DESC LIMIT 1;",
                    (namespace,),
                )
                .await?
        }
    };
    Ok(token)
}

pub async fn lookup_or_new(
    context: &Context,
    namespace: Namespace,
    foreign_id: Option<ChatId>,
) -> Result<String> {
    if let Some(token) = lookup(context, namespace, foreign_id).await? {
        return Ok(token);
    }

    let token = create_id();
    save(context, namespace, foreign_id, &token).await?;
    Ok(token)
}

pub async fn get_config(&self, key: Config) -> Result<Option<String>> {
        let env_key = format!("DELTACHAT_{}", key.as_ref().to_uppercase());
        if let Ok(value) = env::var(env_key) {
            return Ok(Some(value));
        }

        let value = match key {
            Config::Selfavatar => {
                let rel_path = self.sql.get_raw_config(key.as_ref()).await?;
                rel_path.map(|p| {
                    get_abs_path(self, Path::new(&p))
                        .to_string_lossy()
                        .into_owned()
                })
            }
            Config::SysVersion => Some((*DC_VERSION_STR).clone()),
            Config::SysMsgsizeMaxRecommended => Some(format!("{RECOMMENDED_FILE_SIZE}")),
            Config::SysConfigKeys => Some(get_config_keys_string()),
            _ => self.sql.get_raw_config(key.as_ref()).await?,
        };

        if value.is_some() {
            return Ok(value);
        }

        // Default values
        match key {
            Config::ConfiguredInboxFolder => Ok(Some("INBOX".to_owned())),
            _ => Ok(key.get_str("default").map(|s| s.to_string())),
        }
    }

pub fn is_empty(&self) -> bool {
        self.inner.is_empty()
    }

pub(crate) async fn sync_qr_code_tokens(&self, chat_id: Option<ChatId>) -> Result<()> {
        if !self.get_config_bool(Config::SyncMsgs).await? {
            return Ok(());
        }

        if let (Some(invitenumber), Some(auth)) = (
            token::lookup(self, Namespace::InviteNumber, chat_id).await?,
            token::lookup(self, Namespace::Auth, chat_id).await?,
        ) {
            let grpid = if let Some(chat_id) = chat_id {
                let chat = Chat::load_from_db(self, chat_id).await?;
                if !chat.is_promoted() {
                    info!(
                        self,
                        "group '{}' not yet promoted, do not sync tokens yet.", chat.grpid
                    );
                    return Ok(());
                }
                Some(chat.grpid)
            } else {
                None
            };
            self.add_sync_item(SyncData::AddQrToken(QrTokenData {
                invitenumber,
                auth,
                grpid,
            }))
            .await?;
        }
        Ok(())
    }

pub async fn load_from_db(context: &Context, chat_id: ChatId) -> Result<Self> {
        let mut chat = context
            .sql
            .query_row(
                "SELECT c.type, c.name, c.grpid, c.param, c.archived,
                    c.blocked, c.locations_send_until, c.muted_until, c.protected
             FROM chats c
             WHERE c.id=?;",
                (chat_id,),
                |row| {
                    let c = Chat {
                        id: chat_id,
                        typ: row.get(0)?,
                        name: row.get::<_, String>(1)?,
                        grpid: row.get::<_, String>(2)?,
                        param: row.get::<_, String>(3)?.parse().unwrap_or_default(),
                        visibility: row.get(4)?,
                        blocked: row.get::<_, Option<_>>(5)?.unwrap_or_default(),
                        is_sending_locations: row.get(6)?,
                        mute_duration: row.get(7)?,
                        protected: row.get(8)?,
                    };
                    Ok(c)
                },
            )
            .await
            .context(format!("Failed loading chat {chat_id} from database"))?;

        if chat.id.is_archived_link() {
            chat.name = stock_str::archived_chats(context).await;
        } else {
            if chat.typ == Chattype::Single && chat.name.is_empty() {
                // chat.name is set to contact.display_name on changes,
                // however, if things went wrong somehow, we do this here explicitly.
                let mut chat_name = "Err [Name not found]".to_owned();
                match get_chat_contacts(context, chat.id).await {
                    Ok(contacts) => {
                        if let Some(contact_id) = contacts.first() {
                            if let Ok(contact) = Contact::get_by_id(context, *contact_id).await {
                                contact.get_display_name().clone_into(&mut chat_name);
                            }
                        }
                    }
                    Err(err) => {
                        error!(
                            context,
                            "Failed to load contacts for {}: {:#}.", chat.id, err
                        );
                    }
                }
                chat.name = chat_name;
            }
            if chat.param.exists(Param::Selftalk) {
                chat.name = stock_str::saved_messages(context).await;
            } else if chat.param.exists(Param::Devicetalk) {
                chat.name = stock_str::device_messages(context).await;
            }
        }

        Ok(chat)
    }

pub async fn get_primary_self_addr(&self) -> Result<String> {
        self.get_config(Config::ConfiguredAddr)
            .await?
            .context("No self addr configured")
    }

pub fn get_name(&self) -> &str {
        &self.name
    }

async fn get_self_fingerprint(context: &Context) -> Option<Fingerprint> {
    match load_self_public_key(context).await {
        Ok(key) => Some(key.fingerprint()),
        Err(_) => {
            warn!(context, "get_self_fingerprint(): failed to load key");
            None
        }
    }
}

pub enum Namespace {
    #[default]
    Unknown = 0,
    Auth = 110,
    InviteNumber = 100,
}

pub struct Context {
    pub(crate) inner: Arc<InnerContext>,
}

pub struct Fingerprint(Vec<u8>);

pub enum Config {
    /// Email address, used in the `From:` field.
    Addr,

    /// IMAP server hostname.
    MailServer,

    /// IMAP server username.
    MailUser,

    /// IMAP server password.
    MailPw,

    /// IMAP server port.
    MailPort,

    /// IMAP server security (e.g. TLS, STARTTLS).
    MailSecurity,

    /// How to check IMAP server TLS certificates.
    ImapCertificateChecks,

    /// SMTP server hostname.
    SendServer,

    /// SMTP server username.
    SendUser,

    /// SMTP server password.
    SendPw,

    /// SMTP server port.
    SendPort,

    /// SMTP server security (e.g. TLS, STARTTLS).
    SendSecurity,

    /// How to check SMTP server TLS certificates.
    SmtpCertificateChecks,

    /// Whether to use OAuth 2.
    ///
    /// Historically contained other bitflags, which are now deprecated.
    /// Should not be extended in the future, create new config keys instead.
    ServerFlags,

    /// True if SOCKS5 is enabled.
    ///
    /// Can be used to disable SOCKS5 without erasing SOCKS5 configuration.
    Socks5Enabled,

    /// SOCKS5 proxy server hostname or address.
    Socks5Host,

    /// SOCKS5 proxy server port.
    Socks5Port,

    /// SOCKS5 proxy server username.
    Socks5User,

    /// SOCKS5 proxy server password.
    Socks5Password,

    /// Own name to use in the `From:` field when sending messages.
    Displayname,

    /// Own status to display, sent in message footer.
    Selfstatus,

    /// Own avatar filename.
    Selfavatar,

    /// Send BCC copy to self.
    ///
    /// Should be enabled for multidevice setups.
    #[strum(props(default = "1"))]
    BccSelf,

    /// True if encryption is preferred according to Autocrypt standard.
    #[strum(props(default = "1"))]
    E2eeEnabled,

    /// True if Message Delivery Notifications (read receipts) should
    /// be sent and requested.
    #[strum(props(default = "1"))]
    MdnsEnabled,

    /// True if "Sent" folder should be watched for changes.
    #[strum(props(default = "0"))]
    SentboxWatch,

    /// True if chat messages should be moved to a separate folder.
    #[strum(props(default = "1"))]
    MvboxMove,

    /// Watch for new messages in the "Mvbox" (aka DeltaChat folder) only.
    ///
    /// This will not entirely disable other folders, e.g. the spam folder will also still
    /// be watched for new messages.
    #[strum(props(default = "0"))]
    OnlyFetchMvbox,

    /// Whether to show classic emails or only chat messages.
    #[strum(props(default = "2"))] // also change ShowEmails.default() on changes
    ShowEmails,

    /// Quality of the media files to send.
    #[strum(props(default = "0"))] // also change MediaQuality.default() on changes
    MediaQuality,

    /// If set to "1", on the first time `start_io()` is called after configuring,
    /// the newest existing messages are fetched.
    /// Existing recipients are added to the contact database regardless of this setting.
    #[strum(props(default = "0"))]
    FetchExistingMsgs,

    /// If set to "1", then existing messages are considered to be already fetched.
    /// This flag is reset after successful configuration.
    #[strum(props(default = "1"))]
    FetchedExistingMsgs,

    /// Type of the OpenPGP key to generate.
    #[strum(props(default = "0"))]
    KeyGenType,

    /// Timer in seconds after which the message is deleted from the
    /// server.
    ///
    /// Equals to 0 by default, which means the message is never
    /// deleted.
    ///
    /// Value 1 is treated as "delete at once": messages are deleted
    /// immediately, without moving to DeltaChat folder.
    #[strum(props(default = "0"))]
    DeleteServerAfter,

    /// Timer in seconds after which the message is deleted from the
    /// device.
    ///
    /// Equals to 0 by default, which means the message is never
    /// deleted.
    #[strum(props(default = "0"))]
    DeleteDeviceAfter,

    /// Move messages to the Trash folder instead of marking them "\Deleted". Overrides
    /// `ProviderOptions::delete_to_trash`.
    DeleteToTrash,

    /// Save raw MIME messages with headers in the database if true.
    SaveMimeHeaders,

    /// The primary email address. Also see `SecondaryAddrs`.
    ConfiguredAddr,

    /// Configured IMAP server hostname.
    ConfiguredMailServer,

    /// Configured IMAP server username.
    ConfiguredMailUser,

    /// Configured IMAP server password.
    ConfiguredMailPw,

    /// Configured IMAP server port.
    ConfiguredMailPort,

    /// Configured IMAP server security (e.g. TLS, STARTTLS).
    ConfiguredMailSecurity,

    /// How to check IMAP server TLS certificates.
    ConfiguredImapCertificateChecks,

    /// Configured SMTP server hostname.
    ConfiguredSendServer,

    /// Configured SMTP server username.
    ConfiguredSendUser,

    /// Configured SMTP server password.
    ConfiguredSendPw,

    /// Configured SMTP server port.
    ConfiguredSendPort,

    /// How to check SMTP server TLS certificates.
    ConfiguredSmtpCertificateChecks,

    /// Whether OAuth 2 is used with configured provider.
    ConfiguredServerFlags,

    /// Configured SMTP server security (e.g. TLS, STARTTLS).
    ConfiguredSendSecurity,

    /// Configured folder for incoming messages.
    ConfiguredInboxFolder,

    /// Configured folder for chat messages.
    ConfiguredMvboxFolder,

    /// Configured "Sent" folder.
    ConfiguredSentboxFolder,

    /// Configured "Trash" folder.
    ConfiguredTrashFolder,

    /// Unix timestamp of the last successful configuration.
    ConfiguredTimestamp,

    /// ID of the configured provider from the provider database.
    ConfiguredProvider,

    /// True if account is configured.
    Configured,

    /// True if account is a chatmail account.
    IsChatmail,

    /// All secondary self addresses separated by spaces
    /// (`addr1@example.org addr2@example.org addr3@example.org`)
    SecondaryAddrs,

    /// Read-only core version string.
    #[strum(serialize = "sys.version")]
    SysVersion,

    /// Maximal recommended attachment size in bytes.
    #[strum(serialize = "sys.msgsize_max_recommended")]
    SysMsgsizeMaxRecommended,

    /// Space separated list of all config keys available.
    #[strum(serialize = "sys.config_keys")]
    SysConfigKeys,

    /// True if it is a bot account.
    Bot,

    /// True when to skip initial start messages in groups.
    #[strum(props(default = "0"))]
    SkipStartMessages,

    /// Whether we send a warning if the password is wrong (set to false when we send a warning
    /// because we do not want to send a second warning)
    #[strum(props(default = "0"))]
    NotifyAboutWrongPw,

    /// If a warning about exceeding quota was shown recently,
    /// this is the percentage of quota at the time the warning was given.
    /// Unset, when quota falls below minimal warning threshold again.
    QuotaExceeding,

    /// address to webrtc instance to use for videochats
    WebrtcInstance,

    /// Timestamp of the last time housekeeping was run
    LastHousekeeping,

    /// Timestamp of the last `CantDecryptOutgoingMsgs` notification.
    LastCantDecryptOutgoingMsgs,

    /// To how many seconds to debounce scan_all_folders. Used mainly in tests, to disable debouncing completely.
    #[strum(props(default = "60"))]
    ScanAllFoldersDebounceSecs,

    /// Whether to avoid using IMAP IDLE even if the server supports it.
    ///
    /// This is a developer option for testing "fake idle".
    #[strum(props(default = "0"))]
    DisableIdle,

    /// Defines the max. size (in bytes) of messages downloaded automatically.
    /// 0 = no limit.
    #[strum(props(default = "0"))]
    DownloadLimit,

    /// Enable sending and executing (applying) sync messages. Sending requires `BccSelf` to be set.
    #[strum(props(default = "1"))]
    SyncMsgs,

    /// Space-separated list of all the authserv-ids which we believe
    /// may be the one of our email server.
    ///
    /// See `crate::authres::update_authservid_candidates`.
    AuthservIdCandidates,

    /// Make all outgoing messages with Autocrypt header "multipart/signed".
    SignUnencrypted,

    /// Let the core save all events to the database.
    /// This value is used internally to remember the MsgId of the logging xdc
    #[strum(props(default = "0"))]
    DebugLogging,

    /// Last message processed by the bot.
    LastMsgId,

    /// How often to gossip Autocrypt keys in chats with multiple recipients, in seconds. 2 days by
    /// default.
    ///
    /// This is not supposed to be changed by UIs and only used for testing.
    #[strum(props(default = "172800"))]
    GossipPeriod,

    /// Feature flag for verified 1:1 chats; the UI should set it
    /// to 1 if it supports verified 1:1 chats.
    /// Regardless of this setting, `chat.is_protected()` returns true while the key is verified,
    /// and when the key changes, an info message is posted into the chat.
    /// 0=Nothing else happens when the key changes.
    /// 1=After the key changed, `can_send()` returns false and `is_protection_broken()` returns true
    /// until `chat_id.accept()` is called.
    #[strum(props(default = "0"))]
    VerifiedOneOnOneChats,

    /// Row ID of the key in the `keypairs` table
    /// used for signatures, encryption to self and included in `Autocrypt` header.
    KeyId,

    /// This key is sent to the self_reporting bot so that the bot can recognize the user
    /// without storing the email address
    SelfReportingId,

    /// MsgId of webxdc map integration.
    WebxdcIntegration,

    /// Iroh secret key.
    IrohSecretKey,
}

use anyhow::{bail, Context as _, Error, Result};
use percent_encoding::{utf8_percent_encode, AsciiSet, NON_ALPHANUMERIC};
use crate::aheader::EncryptPreference;
use crate::chat::{self, Chat, ChatId, ChatIdBlocked, ProtectionStatus};
use crate::chatlist_events;
use crate::config::Config;
use crate::constants::Blocked;
use crate::contact::{Contact, ContactId, Origin};
use crate::context::Context;
use crate::e2ee::ensure_secret_key_exists;
use crate::events::EventType;
use crate::headerdef::HeaderDef;
use crate::key::{load_self_public_key, DcKey, Fingerprint};
use crate::message::{Message, Viewtype};
use crate::mimeparser::{MimeMessage, SystemMessage};
use crate::param::Param;
use crate::peerstate::Peerstate;
use crate::qr::check_qr;
use crate::securejoin::bob::JoinerProgress;
use crate::stock_str;
use crate::sync::Sync::*;
use crate::token;
use crate::tools::time;
use bobstate::BobState;
use qrinvite::QrInvite;
use crate::token::Namespace;
use deltachat_contact_tools::{ContactAddress, EmailAddress};
use super::*;
use crate::chat::{remove_contact_from_chat, CantSendReason};
use crate::chatlist::Chatlist;
use crate::constants::{self, Chattype};
use crate::imex::{imex, ImexMode};
use crate::receive_imf::receive_imf;
use crate::stock_str::{self, chat_protection_enabled};
use crate::test_utils::get_chat_msg;
use crate::test_utils::{TestContext, TestContextManager};
use crate::tools::SystemTime;
use std::collections::HashSet;
use std::time::Duration;


char* dc_addr_normalize(const char* addr)
{
	char* addr_normalized = dc_strdup(addr);
	dc_trim(addr_normalized);
	if (strncmp(addr_normalized, "mailto:", 7)==0) {
		char* old = addr_normalized;
		addr_normalized = dc_strdup(&old[7]);
		free(old);
		dc_trim(addr_normalized);
	}
	return addr_normalized;
}

fn normalize_addr(addr: &str) -> &str {
    let normalized = addr.trim();
    normalized.trim_start_matches("mailto:")
}

use std::collections::HashMap;
use anyhow::Result;
use percent_encoding::{utf8_percent_encode, NON_ALPHANUMERIC};
use serde::Deserialize;
use crate::config::Config;
use crate::context::Context;
use crate::provider;
use crate::provider::Oauth2Authorizer;
use crate::socks::Socks5Config;
use crate::tools::time;
use super::*;
use crate::test_utils::TestContext;


int dc_addr_equals_self(dc_context_t* context, const char* addr)
{
	int   ret             = 0;
	char* normalized_addr = NULL;
	char* self_addr       = NULL;

	if (context==NULL || addr==NULL) {
		goto cleanup;
	}

	normalized_addr = dc_addr_normalize(addr);

	if (NULL==(self_addr=dc_sqlite3_get_config(context->sql, "configured_addr", NULL))) {
		goto cleanup;
	}

	ret = strcasecmp(normalized_addr, self_addr)==0? 1 : 0;

cleanup:
	free(self_addr);
	free(normalized_addr);
	return ret;
}

pub(crate) async fn is_self_addr(&self, addr: &str) -> Result<bool> {
        Ok(self
            .get_config(Config::ConfiguredAddr)
            .await?
            .iter()
            .any(|a| addr_cmp(addr, a))
            || self
                .get_secondary_self_addrs()
                .await?
                .iter()
                .any(|a| addr_cmp(addr, a)))
    }

pub(crate) async fn get_secondary_self_addrs(&self) -> Result<Vec<String>> {
        let secondary_addrs = self
            .get_config(Config::SecondaryAddrs)
            .await?
            .unwrap_or_default();
        Ok(secondary_addrs
            .split_ascii_whitespace()
            .map(|s| s.to_string())
            .collect())
    }

pub(crate) fn iter(&self) -> BlobDirIter<'_> {
        BlobDirIter::new(self.context, self.inner.iter())
    }

pub async fn get_config(&self, key: Config) -> Result<Option<String>> {
        let env_key = format!("DELTACHAT_{}", key.as_ref().to_uppercase());
        if let Ok(value) = env::var(env_key) {
            return Ok(Some(value));
        }

        let value = match key {
            Config::Selfavatar => {
                let rel_path = self.sql.get_raw_config(key.as_ref()).await?;
                rel_path.map(|p| {
                    get_abs_path(self, Path::new(&p))
                        .to_string_lossy()
                        .into_owned()
                })
            }
            Config::SysVersion => Some((*DC_VERSION_STR).clone()),
            Config::SysMsgsizeMaxRecommended => Some(format!("{RECOMMENDED_FILE_SIZE}")),
            Config::SysConfigKeys => Some(get_config_keys_string()),
            _ => self.sql.get_raw_config(key.as_ref()).await?,
        };

        if value.is_some() {
            return Ok(value);
        }

        // Default values
        match key {
            Config::ConfiguredInboxFolder => Ok(Some("INBOX".to_owned())),
            _ => Ok(key.get_str("default").map(|s| s.to_string())),
        }
    }

pub struct Context {
    pub(crate) inner: Arc<InnerContext>,
}

pub enum Config {
    /// Email address, used in the `From:` field.
    Addr,

    /// IMAP server hostname.
    MailServer,

    /// IMAP server username.
    MailUser,

    /// IMAP server password.
    MailPw,

    /// IMAP server port.
    MailPort,

    /// IMAP server security (e.g. TLS, STARTTLS).
    MailSecurity,

    /// How to check IMAP server TLS certificates.
    ImapCertificateChecks,

    /// SMTP server hostname.
    SendServer,

    /// SMTP server username.
    SendUser,

    /// SMTP server password.
    SendPw,

    /// SMTP server port.
    SendPort,

    /// SMTP server security (e.g. TLS, STARTTLS).
    SendSecurity,

    /// How to check SMTP server TLS certificates.
    SmtpCertificateChecks,

    /// Whether to use OAuth 2.
    ///
    /// Historically contained other bitflags, which are now deprecated.
    /// Should not be extended in the future, create new config keys instead.
    ServerFlags,

    /// True if SOCKS5 is enabled.
    ///
    /// Can be used to disable SOCKS5 without erasing SOCKS5 configuration.
    Socks5Enabled,

    /// SOCKS5 proxy server hostname or address.
    Socks5Host,

    /// SOCKS5 proxy server port.
    Socks5Port,

    /// SOCKS5 proxy server username.
    Socks5User,

    /// SOCKS5 proxy server password.
    Socks5Password,

    /// Own name to use in the `From:` field when sending messages.
    Displayname,

    /// Own status to display, sent in message footer.
    Selfstatus,

    /// Own avatar filename.
    Selfavatar,

    /// Send BCC copy to self.
    ///
    /// Should be enabled for multidevice setups.
    #[strum(props(default = "1"))]
    BccSelf,

    /// True if encryption is preferred according to Autocrypt standard.
    #[strum(props(default = "1"))]
    E2eeEnabled,

    /// True if Message Delivery Notifications (read receipts) should
    /// be sent and requested.
    #[strum(props(default = "1"))]
    MdnsEnabled,

    /// True if "Sent" folder should be watched for changes.
    #[strum(props(default = "0"))]
    SentboxWatch,

    /// True if chat messages should be moved to a separate folder.
    #[strum(props(default = "1"))]
    MvboxMove,

    /// Watch for new messages in the "Mvbox" (aka DeltaChat folder) only.
    ///
    /// This will not entirely disable other folders, e.g. the spam folder will also still
    /// be watched for new messages.
    #[strum(props(default = "0"))]
    OnlyFetchMvbox,

    /// Whether to show classic emails or only chat messages.
    #[strum(props(default = "2"))] // also change ShowEmails.default() on changes
    ShowEmails,

    /// Quality of the media files to send.
    #[strum(props(default = "0"))] // also change MediaQuality.default() on changes
    MediaQuality,

    /// If set to "1", on the first time `start_io()` is called after configuring,
    /// the newest existing messages are fetched.
    /// Existing recipients are added to the contact database regardless of this setting.
    #[strum(props(default = "0"))]
    FetchExistingMsgs,

    /// If set to "1", then existing messages are considered to be already fetched.
    /// This flag is reset after successful configuration.
    #[strum(props(default = "1"))]
    FetchedExistingMsgs,

    /// Type of the OpenPGP key to generate.
    #[strum(props(default = "0"))]
    KeyGenType,

    /// Timer in seconds after which the message is deleted from the
    /// server.
    ///
    /// Equals to 0 by default, which means the message is never
    /// deleted.
    ///
    /// Value 1 is treated as "delete at once": messages are deleted
    /// immediately, without moving to DeltaChat folder.
    #[strum(props(default = "0"))]
    DeleteServerAfter,

    /// Timer in seconds after which the message is deleted from the
    /// device.
    ///
    /// Equals to 0 by default, which means the message is never
    /// deleted.
    #[strum(props(default = "0"))]
    DeleteDeviceAfter,

    /// Move messages to the Trash folder instead of marking them "\Deleted". Overrides
    /// `ProviderOptions::delete_to_trash`.
    DeleteToTrash,

    /// Save raw MIME messages with headers in the database if true.
    SaveMimeHeaders,

    /// The primary email address. Also see `SecondaryAddrs`.
    ConfiguredAddr,

    /// Configured IMAP server hostname.
    ConfiguredMailServer,

    /// Configured IMAP server username.
    ConfiguredMailUser,

    /// Configured IMAP server password.
    ConfiguredMailPw,

    /// Configured IMAP server port.
    ConfiguredMailPort,

    /// Configured IMAP server security (e.g. TLS, STARTTLS).
    ConfiguredMailSecurity,

    /// How to check IMAP server TLS certificates.
    ConfiguredImapCertificateChecks,

    /// Configured SMTP server hostname.
    ConfiguredSendServer,

    /// Configured SMTP server username.
    ConfiguredSendUser,

    /// Configured SMTP server password.
    ConfiguredSendPw,

    /// Configured SMTP server port.
    ConfiguredSendPort,

    /// How to check SMTP server TLS certificates.
    ConfiguredSmtpCertificateChecks,

    /// Whether OAuth 2 is used with configured provider.
    ConfiguredServerFlags,

    /// Configured SMTP server security (e.g. TLS, STARTTLS).
    ConfiguredSendSecurity,

    /// Configured folder for incoming messages.
    ConfiguredInboxFolder,

    /// Configured folder for chat messages.
    ConfiguredMvboxFolder,

    /// Configured "Sent" folder.
    ConfiguredSentboxFolder,

    /// Configured "Trash" folder.
    ConfiguredTrashFolder,

    /// Unix timestamp of the last successful configuration.
    ConfiguredTimestamp,

    /// ID of the configured provider from the provider database.
    ConfiguredProvider,

    /// True if account is configured.
    Configured,

    /// True if account is a chatmail account.
    IsChatmail,

    /// All secondary self addresses separated by spaces
    /// (`addr1@example.org addr2@example.org addr3@example.org`)
    SecondaryAddrs,

    /// Read-only core version string.
    #[strum(serialize = "sys.version")]
    SysVersion,

    /// Maximal recommended attachment size in bytes.
    #[strum(serialize = "sys.msgsize_max_recommended")]
    SysMsgsizeMaxRecommended,

    /// Space separated list of all config keys available.
    #[strum(serialize = "sys.config_keys")]
    SysConfigKeys,

    /// True if it is a bot account.
    Bot,

    /// True when to skip initial start messages in groups.
    #[strum(props(default = "0"))]
    SkipStartMessages,

    /// Whether we send a warning if the password is wrong (set to false when we send a warning
    /// because we do not want to send a second warning)
    #[strum(props(default = "0"))]
    NotifyAboutWrongPw,

    /// If a warning about exceeding quota was shown recently,
    /// this is the percentage of quota at the time the warning was given.
    /// Unset, when quota falls below minimal warning threshold again.
    QuotaExceeding,

    /// address to webrtc instance to use for videochats
    WebrtcInstance,

    /// Timestamp of the last time housekeeping was run
    LastHousekeeping,

    /// Timestamp of the last `CantDecryptOutgoingMsgs` notification.
    LastCantDecryptOutgoingMsgs,

    /// To how many seconds to debounce scan_all_folders. Used mainly in tests, to disable debouncing completely.
    #[strum(props(default = "60"))]
    ScanAllFoldersDebounceSecs,

    /// Whether to avoid using IMAP IDLE even if the server supports it.
    ///
    /// This is a developer option for testing "fake idle".
    #[strum(props(default = "0"))]
    DisableIdle,

    /// Defines the max. size (in bytes) of messages downloaded automatically.
    /// 0 = no limit.
    #[strum(props(default = "0"))]
    DownloadLimit,

    /// Enable sending and executing (applying) sync messages. Sending requires `BccSelf` to be set.
    #[strum(props(default = "1"))]
    SyncMsgs,

    /// Space-separated list of all the authserv-ids which we believe
    /// may be the one of our email server.
    ///
    /// See `crate::authres::update_authservid_candidates`.
    AuthservIdCandidates,

    /// Make all outgoing messages with Autocrypt header "multipart/signed".
    SignUnencrypted,

    /// Let the core save all events to the database.
    /// This value is used internally to remember the MsgId of the logging xdc
    #[strum(props(default = "0"))]
    DebugLogging,

    /// Last message processed by the bot.
    LastMsgId,

    /// How often to gossip Autocrypt keys in chats with multiple recipients, in seconds. 2 days by
    /// default.
    ///
    /// This is not supposed to be changed by UIs and only used for testing.
    #[strum(props(default = "172800"))]
    GossipPeriod,

    /// Feature flag for verified 1:1 chats; the UI should set it
    /// to 1 if it supports verified 1:1 chats.
    /// Regardless of this setting, `chat.is_protected()` returns true while the key is verified,
    /// and when the key changes, an info message is posted into the chat.
    /// 0=Nothing else happens when the key changes.
    /// 1=After the key changed, `can_send()` returns false and `is_protection_broken()` returns true
    /// until `chat_id.accept()` is called.
    #[strum(props(default = "0"))]
    VerifiedOneOnOneChats,

    /// Row ID of the key in the `keypairs` table
    /// used for signatures, encryption to self and included in `Autocrypt` header.
    KeyId,

    /// This key is sent to the self_reporting bot so that the bot can recognize the user
    /// without storing the email address
    SelfReportingId,

    /// MsgId of webxdc map integration.
    WebxdcIntegration,

    /// Iroh secret key.
    IrohSecretKey,
}

use std::env;
use std::path::Path;
use std::str::FromStr;
use anyhow::{ensure, Context as _, Result};
use base64::Engine as _;
use deltachat_contact_tools::addr_cmp;
use serde::{Deserialize, Serialize};
use strum::{EnumProperty, IntoEnumIterator};
use strum_macros::{AsRefStr, Display, EnumIter, EnumString};
use tokio::fs;
use crate::blob::BlobObject;
use crate::constants::{self, DC_VERSION_STR};
use crate::context::Context;
use crate::events::EventType;
use crate::log::LogExt;
use crate::mimefactory::RECOMMENDED_FILE_SIZE;
use crate::provider::{get_provider_by_id, Provider};
use crate::sync::{self, Sync::*, SyncData};
use crate::tools::{get_abs_path, improve_single_line_input};
use num_traits::FromPrimitive;
use super::*;
use crate::test_utils::{sync, TestContext, TestContextManager};


int dc_pgp_create_keypair(dc_context_t* context, const char* addr, dc_key_t* ret_public_key, dc_key_t* ret_private_key)
{
	int              success = 0;
	pgp_key_t        seckey;
	pgp_key_t        pubkey;
	pgp_key_t        subkey;
	uint8_t          subkeyid[PGP_KEY_ID_SIZE];
	uint8_t*         user_id = NULL;
	pgp_memory_t*    pubmem = pgp_memory_new();
	pgp_memory_t*    secmem = pgp_memory_new();
	pgp_output_t*    pubout = pgp_output_new();
	pgp_output_t*    secout = pgp_output_new();

	memset(&seckey, 0, sizeof(pgp_key_t));
	memset(&pubkey, 0, sizeof(pgp_key_t));
	memset(&subkey, 0, sizeof(pgp_key_t));

	if (context==NULL || addr==NULL || ret_public_key==NULL || ret_private_key==NULL
	 || pubmem==NULL || secmem==NULL || pubout==NULL || secout==NULL) {
		goto cleanup;
	}

	/* Generate User ID.
	By convention, this is the e-mail-address in angle brackets.

	As the user-id is only decorative in Autocrypt and not needed for Delta Chat,
	so we _could_ just use sth. that looks like an e-mail-address.
	This would protect the user's privacy if someone else uploads the keys to keyservers.

	However, as eg. Enigmail displayes the user-id in "Good signature from <user-id>,
	for now, we decided to leave the address in the user-id */
	#if 0
		user_id = (uint8_t*)dc_mprintf("<%08X@%08X.org>", (int)random(), (int)random());
	#else
		user_id = (uint8_t*)dc_mprintf("<%s>", addr);
	#endif

	/* generate two keypairs */
	if (!pgp_rsa_generate_keypair(&seckey, DC_KEYGEN_BITS, DC_KEYGEN_E, NULL, NULL, NULL, 0)
	 || !pgp_rsa_generate_keypair(&subkey, DC_KEYGEN_BITS, DC_KEYGEN_E, NULL, NULL, NULL, 0)) {
		goto cleanup;
	}

	/* Create public key, bind public subkey to public key */

	pubkey.type = PGP_PTAG_CT_PUBLIC_KEY;
	pgp_pubkey_dup(&pubkey.key.pubkey, &seckey.key.pubkey);
	memcpy(pubkey.pubkeyid, seckey.pubkeyid, PGP_KEY_ID_SIZE);
	pgp_fingerprint(&pubkey.pubkeyfpr, &seckey.key.pubkey, 0);
	add_selfsigned_userid(&seckey, &pubkey, (const uint8_t*)user_id, 0/*never expire*/);

	EXPAND_ARRAY((&pubkey), subkey);
	{
		pgp_subkey_t* p = &pubkey.subkeys[pubkey.subkeyc++];
		pgp_pubkey_dup(&p->key.pubkey, &subkey.key.pubkey);
		pgp_keyid(subkeyid, PGP_KEY_ID_SIZE, &pubkey.key.pubkey, PGP_HASH_SHA1);
		memcpy(p->id, subkeyid, PGP_KEY_ID_SIZE);
	}

	EXPAND_ARRAY((&pubkey), subkeysig);
	add_subkey_binding_signature(&pubkey.subkeysigs[pubkey.subkeysigc++], &pubkey, &subkey, &seckey);

	/* Create secret key, bind secret subkey to secret key */

	EXPAND_ARRAY((&seckey), subkey);
	{
		pgp_subkey_t* p = &seckey.subkeys[seckey.subkeyc++];
		pgp_seckey_dup(&p->key.seckey, &subkey.key.seckey);
		pgp_keyid(subkeyid, PGP_KEY_ID_SIZE, &seckey.key.pubkey, PGP_HASH_SHA1);
		memcpy(p->id, subkeyid, PGP_KEY_ID_SIZE);
	}

	EXPAND_ARRAY((&seckey), subkeysig);
	add_subkey_binding_signature(&seckey.subkeysigs[seckey.subkeysigc++], &seckey, &subkey, &seckey);

	/* Done with key generation, write binary keys to memory */

	pgp_writer_set_memory(pubout, pubmem);
	if (!pgp_write_xfer_key(pubout, &pubkey, 0/*armored*/)
	 || pubmem->buf==NULL || pubmem->length <= 0) {
		goto cleanup;
	}

	pgp_writer_set_memory(secout, secmem);
	if (!pgp_write_xfer_key(secout, &seckey, 0/*armored*/)
	 || secmem->buf==NULL || secmem->length <= 0) {
		goto cleanup;
	}

	dc_key_set_from_binary(ret_public_key, pubmem->buf, pubmem->length, DC_KEY_PUBLIC);
	dc_key_set_from_binary(ret_private_key, secmem->buf, secmem->length, DC_KEY_PRIVATE);

	success = 1;

cleanup:
	if (pubout) { pgp_output_delete(pubout); }
	if (secout) { pgp_output_delete(secout); }
	if (pubmem) { pgp_memory_free(pubmem); }
	if (secmem) { pgp_memory_free(secmem); }
	pgp_key_free(&seckey); /* not: pgp_keydata_free() which will also free the pointer itself (we created it on the stack) */
	pgp_key_free(&pubkey);
	pgp_key_free(&subkey);
	free(user_id);
	return success;
}

pub(crate) fn create_keypair(addr: EmailAddress, keygen_type: KeyGenType) -> Result<KeyPair> {
    let (signing_key_type, encryption_key_type) = match keygen_type {
        KeyGenType::Rsa2048 => (PgpKeyType::Rsa(2048), PgpKeyType::Rsa(2048)),
        KeyGenType::Rsa4096 => (PgpKeyType::Rsa(4096), PgpKeyType::Rsa(4096)),
        KeyGenType::Ed25519 | KeyGenType::Default => (PgpKeyType::EdDSA, PgpKeyType::ECDH),
    };

    let user_id = format!("<{addr}>");
    let key_params = SecretKeyParamsBuilder::default()
        .key_type(signing_key_type)
        .can_certify(true)
        .can_sign(true)
        .primary_user_id(user_id)
        .passphrase(None)
        .preferred_symmetric_algorithms(smallvec![
            SymmetricKeyAlgorithm::AES256,
            SymmetricKeyAlgorithm::AES192,
            SymmetricKeyAlgorithm::AES128,
        ])
        .preferred_hash_algorithms(smallvec![
            HashAlgorithm::SHA2_256,
            HashAlgorithm::SHA2_384,
            HashAlgorithm::SHA2_512,
            HashAlgorithm::SHA2_224,
            HashAlgorithm::SHA1,
        ])
        .preferred_compression_algorithms(smallvec![
            CompressionAlgorithm::ZLIB,
            CompressionAlgorithm::ZIP,
        ])
        .subkey(
            SubkeyParamsBuilder::default()
                .key_type(encryption_key_type)
                .can_encrypt(true)
                .passphrase(None)
                .build()
                .context("failed to build subkey parameters")?,
        )
        .build()
        .context("failed to build key parameters")?;

    let secret_key = key_params
        .generate()
        .context("failed to generate the key")?
        .sign(|| "".into())
        .context("failed to sign secret key")?;
    secret_key
        .verify()
        .context("invalid secret key generated")?;

    let public_key = secret_key
        .public_key()
        .sign(&secret_key, || "".into())
        .context("failed to sign public key")?;
    public_key
        .verify()
        .context("invalid public key generated")?;

    Ok(KeyPair {
        addr,
        public: public_key,
        secret: secret_key,
    })
}

pub struct KeyPair {
    /// Email address.
    pub addr: EmailAddress,

    /// Public key.
    pub public: SignedPublicKey,

    /// Secret key.
    pub secret: SignedSecretKey,
}

pub enum KeyGenType {
    #[default]
    Default = 0,

    /// 2048-bit RSA.
    Rsa2048 = 1,

    /// [Ed25519](https://ed25519.cr.yp.to/) signature and X25519 encryption.
    Ed25519 = 2,

    /// 4096-bit RSA.
    Rsa4096 = 3,
}

use std::collections::{BTreeMap, HashSet};
use std::io;
use std::io::Cursor;
use anyhow::{bail, Context as _, Result};
use deltachat_contact_tools::EmailAddress;
use pgp::armor::BlockType;
use pgp::composed::{
    Deserializable, KeyType as PgpKeyType, Message, SecretKeyParamsBuilder, SignedPublicKey,
    SignedPublicSubKey, SignedSecretKey, StandaloneSignature, SubkeyParamsBuilder,
};
use pgp::crypto::hash::HashAlgorithm;
use pgp::crypto::sym::SymmetricKeyAlgorithm;
use pgp::types::{
    CompressionAlgorithm, KeyTrait, Mpi, PublicKeyTrait, SecretKeyTrait, StringToKey,
};
use rand::{thread_rng, CryptoRng, Rng};
use tokio::runtime::Handle;
use crate::constants::KeyGenType;
use crate::key::{DcKey, Fingerprint};
use std::io::Read;
use once_cell::sync::Lazy;
use tokio::sync::OnceCell;
use super::*;
use crate::test_utils::{alice_keypair, bob_keypair};


* The result must be dc_array_unref()'d
 *
 * The list is already sorted and starts with the oldest message.
 * Clients should not try to re-sort the list as this would be an expensive action
 * and would result in inconsistencies between clients.
 *
 * @memberof dc_context_t
 * @param context The context object as returned from dc_context_new().
 * @param chat_id The chat ID to get all messages with media from.
 * @param msg_type Specify a message type to query here, one of the DC_MSG_* constats.
 * @param msg_type2 Alternative message type to search for. 0 to skip.
 * @param msg_type3 Alternative message type to search for. 0 to skip.
 * @return An array with messages from the given chat ID that have the wanted message types.
 */
dc_array_t* dc_get_chat_media(dc_context_t* context, uint32_t chat_id,
                              int msg_type, int msg_type2, int msg_type3)
{
	if (context==NULL || context->magic!=DC_CONTEXT_MAGIC) {
		return NULL;
	}

	dc_array_t* ret = dc_array_new(context, 100);

	sqlite3_stmt* stmt = dc_sqlite3_prepare(context->sql,
		"SELECT id FROM msgs WHERE chat_id=? AND chat_id != ? AND (type=? OR type=? OR type=?) ORDER BY timestamp, id;");
	sqlite3_bind_int(stmt, 1, chat_id);
    sqlite3_bind_int(stmt, 2, DC_CHAT_ID_TRASH);
	sqlite3_bind_int(stmt, 3, msg_type);
	sqlite3_bind_int(stmt, 4, msg_type2>0? msg_type2 : msg_type);
	sqlite3_bind_int(stmt, 5, msg_type3>0? msg_type3 : msg_type);
	while (sqlite3_step(stmt)==SQLITE_ROW) {
		dc_array_add_id(ret, sqlite3_column_int(stmt, 0));
	}
	sqlite3_finalize(stmt);

	return ret;
}

pub async fn get_chat_media(
    context: &Context,
    chat_id: Option<ChatId>,
    msg_type: Viewtype,
    msg_type2: Viewtype,
    msg_type3: Viewtype,
) -> Result<Vec<MsgId>> {
    // TODO This query could/should be converted to `AND type IN (?, ?, ?)`.
    let list = context
        .sql
        .query_map(
            "SELECT id
               FROM msgs
              WHERE (1=? OR chat_id=?)
                AND chat_id != ?
                AND (type=? OR type=? OR type=?)
                AND hidden=0
              ORDER BY timestamp, id;",
            (
                chat_id.is_none(),
                chat_id.unwrap_or_else(|| ChatId::new(0)),
                DC_CHAT_ID_TRASH,
                msg_type,
                if msg_type2 != Viewtype::Unknown {
                    msg_type2
                } else {
                    msg_type
                },
                if msg_type3 != Viewtype::Unknown {
                    msg_type3
                } else {
                    msg_type
                },
            ),
            |row| row.get::<_, MsgId>(0),
            |ids| Ok(ids.flatten().collect()),
        )
        .await?;
    Ok(list)
}

pub struct Context {
    pub(crate) inner: Arc<InnerContext>,
}

pub struct InnerContext {
    /// Blob directory path
    pub(crate) blobdir: PathBuf,
    pub(crate) sql: Sql,
    pub(crate) smeared_timestamp: SmearedTimestamp,
    /// The global "ongoing" process state.
    ///
    /// This is a global mutex-like state for operations which should be modal in the
    /// clients.
    running_state: RwLock<RunningState>,
    /// Mutex to avoid generating the key for the user more than once.
    pub(crate) generating_key_mutex: Mutex<()>,
    /// Mutex to enforce only a single running oauth2 is running.
    pub(crate) oauth2_mutex: Mutex<()>,
    /// Mutex to prevent a race condition when a "your pw is wrong" warning is sent, resulting in multiple messages being sent.
    pub(crate) wrong_pw_warning_mutex: Mutex<()>,
    pub(crate) translated_stockstrings: StockStrings,
    pub(crate) events: Events,

    pub(crate) scheduler: SchedulerState,
    pub(crate) ratelimit: RwLock<Ratelimit>,

    /// Recently loaded quota information, if any.
    /// Set to `None` if quota was never tried to load.
    pub(crate) quota: RwLock<Option<QuotaInfo>>,

    /// IMAP UID resync request.
    pub(crate) resync_request: AtomicBool,

    /// Notify about new messages.
    ///
    /// This causes [`Context::wait_next_msgs`] to wake up.
    pub(crate) new_msgs_notify: Notify,

    /// Server ID response if ID capability is supported
    /// and the server returned non-NIL on the inbox connection.
    /// <https://datatracker.ietf.org/doc/html/rfc2971>
    pub(crate) server_id: RwLock<Option<HashMap<String, String>>>,

    /// IMAP METADATA.
    pub(crate) metadata: RwLock<Option<ServerMetadata>>,

    pub(crate) last_full_folder_scan: Mutex<Option<tools::Time>>,

    /// ID for this `Context` in the current process.
    ///
    /// This allows for multiple `Context`s open in a single process where each context can
    /// be identified by this ID.
    pub(crate) id: u32,

    creation_time: tools::Time,

    /// The text of the last error logged and emitted as an event.
    /// If the ui wants to display an error after a failure,
    /// `last_error` should be used to avoid races with the event thread.
    pub(crate) last_error: std::sync::RwLock<String>,

    /// If debug logging is enabled, this contains all necessary information
    ///
    /// Standard RwLock instead of [`tokio::sync::RwLock`] is used
    /// because the lock is used from synchronous [`Context::emit_event`].
    pub(crate) debug_logging: std::sync::RwLock<Option<DebugLogging>>,

    /// Push subscriber to store device token
    /// and register for heartbeat notifications.
    pub(crate) push_subscriber: PushSubscriber,

    /// True if account has subscribed to push notifications via IMAP.
    pub(crate) push_subscribed: AtomicBool,

    /// Iroh for realtime peer channels.
    pub(crate) iroh: OnceCell<Iroh>,
}

pub async fn query_map<T, F, G, H>(
        &self,
        sql: &str,
        params: impl rusqlite::Params + Send,
        f: F,
        mut g: G,
    ) -> Result<H>
    where
        F: Send + FnMut(&rusqlite::Row) -> rusqlite::Result<T>,
        G: Send + FnMut(rusqlite::MappedRows<F>) -> Result<H>,
        H: Send + 'static,
    {
        self.call(move |conn| {
            let mut stmt = conn.prepare(sql)?;
            let res = stmt.query_map(params, f)?;
            g(res)
        })
        .await
    }

pub enum Viewtype {
    /// Unknown message type.
    #[default]
    Unknown = 0,

    /// Text message.
    /// The text of the message is set using dc_msg_set_text() and retrieved with dc_msg_get_text().
    Text = 10,

    /// Image message.
    /// If the image is a GIF and has the appropriate extension, the viewtype is auto-changed to
    /// `Gif` when sending the message.
    /// File, width and height are set via dc_msg_set_file(), dc_msg_set_dimension
    /// and retrieved via dc_msg_set_file(), dc_msg_set_dimension().
    Image = 20,

    /// Animated GIF message.
    /// File, width and height are set via dc_msg_set_file(), dc_msg_set_dimension()
    /// and retrieved via dc_msg_get_file(), dc_msg_get_width(), dc_msg_get_height().
    Gif = 21,

    /// Message containing a sticker, similar to image.
    /// If possible, the ui should display the image without borders in a transparent way.
    /// A click on a sticker will offer to install the sticker set in some future.
    Sticker = 23,

    /// Message containing an Audio file.
    /// File and duration are set via dc_msg_set_file(), dc_msg_set_duration()
    /// and retrieved via dc_msg_get_file(), dc_msg_get_duration().
    Audio = 40,

    /// A voice message that was directly recorded by the user.
    /// For all other audio messages, the type #DC_MSG_AUDIO should be used.
    /// File and duration are set via dc_msg_set_file(), dc_msg_set_duration()
    /// and retrieved via dc_msg_get_file(), dc_msg_get_duration()
    Voice = 41,

    /// Video messages.
    /// File, width, height and durarion
    /// are set via dc_msg_set_file(), dc_msg_set_dimension(), dc_msg_set_duration()
    /// and retrieved via
    /// dc_msg_get_file(), dc_msg_get_width(),
    /// dc_msg_get_height(), dc_msg_get_duration().
    Video = 50,

    /// Message containing any file, eg. a PDF.
    /// The file is set via dc_msg_set_file()
    /// and retrieved via dc_msg_get_file().
    File = 60,

    /// Message is an invitation to a videochat.
    VideochatInvitation = 70,

    /// Message is an webxdc instance.
    Webxdc = 80,

    /// Message containing shared contacts represented as a vCard (virtual contact file)
    /// with email addresses and possibly other fields.
    /// Use `parse_vcard()` to retrieve them.
    Vcard = 90,
}

pub struct MsgId(u32);

pub struct ChatId(u32);

impl ChatId {
    /// Create a new [ChatId].
    pub const fn new(id: u32) -> ChatId {
        ChatId(id)
    }
}

use std::cmp;
use std::collections::{HashMap, HashSet};
use std::fmt;
use std::path::{Path, PathBuf};
use std::str::FromStr;
use std::time::Duration;
use anyhow::{anyhow, bail, ensure, Context as _, Result};
use deltachat_contact_tools::{strip_rtlo_characters, ContactAddress};
use deltachat_derive::{FromSql, ToSql};
use serde::{Deserialize, Serialize};
use strum_macros::EnumIter;
use tokio::task;
use crate::aheader::EncryptPreference;
use crate::blob::BlobObject;
use crate::chatlist::Chatlist;
use crate::chatlist_events;
use crate::color::str_to_color;
use crate::config::Config;
use crate::constants::{
    self, Blocked, Chattype, DC_CHAT_ID_ALLDONE_HINT, DC_CHAT_ID_ARCHIVED_LINK,
    DC_CHAT_ID_LAST_SPECIAL, DC_CHAT_ID_TRASH, DC_RESEND_USER_AVATAR_DAYS,
};
use crate::contact::{self, Contact, ContactId, Origin};
use crate::context::Context;
use crate::debug_logging::maybe_set_logging_xdc;
use crate::download::DownloadState;
use crate::ephemeral::Timer as EphemeralTimer;
use crate::events::EventType;
use crate::html::new_html_mimepart;
use crate::location;
use crate::log::LogExt;
use crate::message::{self, Message, MessageState, MsgId, Viewtype};
use crate::mimefactory::MimeFactory;
use crate::mimeparser::SystemMessage;
use crate::param::{Param, Params};
use crate::peerstate::Peerstate;
use crate::receive_imf::ReceivedMsg;
use crate::securejoin::BobState;
use crate::smtp::send_msg_to_smtp;
use crate::sql;
use crate::stock_str;
use crate::sync::{self, Sync::*, SyncData};
use crate::tools::{
    buf_compress, create_id, create_outgoing_rfc724_mid, create_smeared_timestamp,
    create_smeared_timestamps, get_abs_path, gm2local_offset, improve_single_line_input,
    smeared_time, time, IsNoneOrEmpty, SystemTime,
};
use crate::webxdc::WEBXDC_SUFFIX;
use CantSendReason::*;
use super::*;
use crate::chatlist::get_archived_cnt;
use crate::constants::{DC_GCL_ARCHIVED_ONLY, DC_GCL_NO_SPECIALS};
use crate::message::delete_msgs;
use crate::receive_imf::receive_imf;
use crate::test_utils::{sync, TestContext, TestContextManager};
use strum::IntoEnumIterator;
use tokio::fs;


uint32_t dc_msg_get_from_id(const dc_msg_t* msg)
{
	if (msg==NULL || msg->magic!=DC_MSG_MAGIC) {
		return 0;
	}
	return msg->from_id;
}

pub fn get_from_id(&self) -> ContactId {
        self.from_id
    }

pub struct ContactId(u32);

pub struct Message {
    /// Message ID.
    pub(crate) id: MsgId,

    /// `From:` contact ID.
    pub(crate) from_id: ContactId,

    /// ID of the first contact in the `To:` header.
    pub(crate) to_id: ContactId,

    /// ID of the chat message belongs to.
    pub(crate) chat_id: ChatId,

    /// Type of the message.
    pub(crate) viewtype: Viewtype,

    /// State of the message.
    pub(crate) state: MessageState,
    pub(crate) download_state: DownloadState,

    /// Whether the message is hidden.
    pub(crate) hidden: bool,
    pub(crate) timestamp_sort: i64,
    pub(crate) timestamp_sent: i64,
    pub(crate) timestamp_rcvd: i64,
    pub(crate) ephemeral_timer: EphemeralTimer,
    pub(crate) ephemeral_timestamp: i64,
    pub(crate) text: String,

    /// Message subject.
    ///
    /// If empty, a default subject will be generated when sending.
    pub(crate) subject: String,

    /// `Message-ID` header value.
    pub(crate) rfc724_mid: String,

    /// `In-Reply-To` header value.
    pub(crate) in_reply_to: Option<String>,
    pub(crate) is_dc_message: MessengerMessage,
    pub(crate) mime_modified: bool,
    pub(crate) chat_blocked: Blocked,
    pub(crate) location_id: u32,
    pub(crate) error: Option<String>,
    pub(crate) param: Params,
}

use std::collections::BTreeSet;
use std::path::{Path, PathBuf};
use anyhow::{ensure, format_err, Context as _, Result};
use deltachat_contact_tools::{parse_vcard, VcardContact};
use deltachat_derive::{FromSql, ToSql};
use serde::{Deserialize, Serialize};
use tokio::{fs, io};
use crate::blob::BlobObject;
use crate::chat::{Chat, ChatId, ChatIdBlocked};
use crate::chatlist_events;
use crate::config::Config;
use crate::constants::{
    Blocked, Chattype, VideochatType, DC_CHAT_ID_TRASH, DC_DESIRED_TEXT_LEN, DC_MSG_ID_LAST_SPECIAL,
};
use crate::contact::{Contact, ContactId};
use crate::context::Context;
use crate::debug_logging::set_debug_logging_xdc;
use crate::download::DownloadState;
use crate::ephemeral::{start_ephemeral_timers_msgids, Timer as EphemeralTimer};
use crate::events::EventType;
use crate::imap::markseen_on_imap_table;
use crate::location::delete_poi_location;
use crate::mimeparser::{parse_message_id, SystemMessage};
use crate::param::{Param, Params};
use crate::pgp::split_armored_data;
use crate::reaction::get_msg_reactions;
use crate::sql;
use crate::summary::Summary;
use crate::tools::{
    buf_compress, buf_decompress, get_filebytes, get_filemeta, gm2local_offset, read_file, time,
    timestamp_to_str, truncate,
};
use MessageState::*;
use MessageState::*;
use num_traits::FromPrimitive;
use super::*;
use crate::chat::{
        self, add_contact_to_chat, marknoticed_chat, send_text_msg, ChatItem, ProtectionStatus,
    };
use crate::chatlist::Chatlist;
use crate::config::Config;
use crate::reaction::send_reaction;
use crate::receive_imf::receive_imf;
use crate::test_utils as test;
use crate::test_utils::{TestContext, TestContextManager};


uint32_t dc_contact_get_id(const dc_contact_t* contact)
{
	if (contact==NULL || contact->magic!=DC_CONTACT_MAGIC) {
		return 0;
	}
	return contact->id;
}

pub fn get_id(&self) -> ContactId {
        self.id
    }

pub struct ContactId(u32);

pub struct Contact {
    /// The contact ID.
    pub id: ContactId,

    /// Contact name. It is recommended to use `Contact::get_name`,
    /// `Contact::get_display_name` or `Contact::get_name_n_addr` to access this field.
    /// May be empty, initially set to `authname`.
    name: String,

    /// Name authorized by the contact himself. Only this name may be spread to others,
    /// e.g. in To:-lists. May be empty. It is recommended to use `Contact::get_authname`,
    /// to access this field.
    authname: String,

    /// E-Mail-Address of the contact. It is recommended to use `Contact::get_addr` to access this field.
    addr: String,

    /// Blocked state. Use contact_is_blocked to access this field.
    pub blocked: bool,

    /// Time when the contact was seen last time, Unix time in seconds.
    last_seen: i64,

    /// The origin/source of the contact.
    pub origin: Origin,

    /// Parameters as Param::ProfileImage
    pub param: Params,

    /// Last seen message signature for this contact, to be displayed in the profile.
    status: String,

    /// If the contact is a bot.
    is_bot: bool,
}


uint32_t dc_get_chat_id_by_contact_id(dc_context_t* context, uint32_t contact_id)
{
	uint32_t chat_id = 0;
	int      chat_id_blocked = 0;

	if (context==NULL || context->magic!=DC_CONTEXT_MAGIC) {
		return 0;
	}

	dc_lookup_real_nchat_by_contact_id(context, contact_id, &chat_id, &chat_id_blocked);

	return chat_id_blocked? 0 : chat_id; /* from outside view, chats only existing in the deaddrop do not exist */
}

pub async fn lookup_by_contact(
        context: &Context,
        contact_id: ContactId,
    ) -> Result<Option<Self>> {
        let Some(chat_id_blocked) = ChatIdBlocked::lookup_by_contact(context, contact_id).await?
        else {
            return Ok(None);
        };

        let chat_id = match chat_id_blocked.blocked {
            Blocked::Not | Blocked::Request => Some(chat_id_blocked.id),
            Blocked::Yes => None,
        };
        Ok(chat_id)
    }

pub struct Context {
    pub(crate) inner: Arc<InnerContext>,
}

pub struct ChatId(u32);

pub(crate) struct ChatIdBlocked {
    /// Chat ID.
    pub id: ChatId,

    /// Whether the chat is blocked, unblocked or a contact request.
    pub blocked: Blocked,
}

impl ChatIdBlocked {
    pub async fn lookup_by_contact(
        context: &Context,
        contact_id: ContactId,
    ) -> Result<Option<Self>> {
        ensure!(context.sql.is_open().await, "Database not available");
        ensure!(
            contact_id != ContactId::UNDEFINED,
            "Invalid contact id requested"
        );

        context
            .sql
            .query_row_optional(
                "SELECT c.id, c.blocked
                   FROM chats c
                  INNER JOIN chats_contacts j
                          ON c.id=j.chat_id
                  WHERE c.type=100  -- 100 = Chattype::Single
                    AND c.id>9      -- 9 = DC_CHAT_ID_LAST_SPECIAL
                    AND j.contact_id=?;",
                (contact_id,),
                |row| {
                    let id: ChatId = row.get(0)?;
                    let blocked: Blocked = row.get(1)?;
                    Ok(ChatIdBlocked { id, blocked })
                },
            )
            .await
            .map_err(Into::into)
    }
}

pub struct ContactId(u32);

pub enum Blocked {
    #[default]
    Not = 0,
    Yes = 1,
    Request = 2,
}