// Copyright 2021 The Matrix.org Foundation C.I.C.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

use std::sync::{Arc, Mutex};

use matrix_qrcode::{
    qrcode::QrCode, EncodingError, QrVerificationData, SelfVerificationData,
    SelfVerificationNoMasterKey, VerificationData,
};
use matrix_sdk_common::uuid::Uuid;
use ruma::{
    api::client::r0::keys::upload_signatures::Request as SignatureUploadRequest,
    events::{
        key::verification::{
            cancel::CancelCode,
            done::{DoneEventContent, DoneToDeviceEventContent},
            start::{
                self, ReciprocateV1Content, StartEventContent, StartMethod,
                StartToDeviceEventContent,
            },
            Relation,
        },
        AnyMessageEventContent, AnyToDeviceEventContent,
    },
    DeviceId, DeviceIdBox, DeviceKeyAlgorithm, RoomId, UserId,
};
use thiserror::Error;
use tracing::trace;

use super::{
    event_enums::{CancelContent, DoneContent, OutgoingContent, OwnedStartContent, StartContent},
    requests::RequestHandle,
    CancelInfo, Cancelled, Done, FlowId, IdentitiesBeingVerified, VerificationResult,
    VerificationStore,
};
use crate::{
    olm::PrivateCrossSigningIdentity, CryptoStoreError, OutgoingVerificationRequest,
    ReadOnlyDevice, ReadOnlyUserIdentities, RoomMessageRequest, ToDeviceRequest,
};

const SECRET_SIZE: usize = 16;

/// An error for the different failure modes that can happen during the
/// validation of a scanned QR code.
#[derive(Debug, Error)]
pub enum ScanError {
    /// An IO error inside the crypto store happened during the validation of
    /// the QR code scan.
    #[error(transparent)]
    Store(#[from] CryptoStoreError),
    /// A key mismatch happened during the validation of the QR code scan.
    #[error("The keys that are being verified didn't match (expected {expected}, found {found})")]
    KeyMismatch { expected: String, found: String },
    /// One of the users that is participating in this verification doesn't have
    /// a valid cross signing identity.
    #[error("The user {0} is missing a valid cross signing identity")]
    MissingCrossSigningIdentity(UserId),
    /// The device of the user that is participating in this verification
    /// doesn't have a valid device key.
    #[error("The user's {0} device {1} is not E2E capable")]
    MissingDeviceKeys(UserId, DeviceIdBox),
    /// The ID uniquely identifying this verification flow didn't match to the
    /// one that has been scanned.
    #[error("The unique verification flow id did not match (expected {expected}, found {found})")]
    FlowIdMismatch { expected: String, found: String },
}

/// An object controlling QR code style key verification flows.
#[derive(Clone)]
pub struct QrVerification {
    flow_id: FlowId,
    inner: Arc<QrVerificationData>,
    state: Arc<Mutex<InnerState>>,
    identities: IdentitiesBeingVerified,
    request_handle: Option<RequestHandle>,
    we_started: bool,
}

impl std::fmt::Debug for QrVerification {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        f.debug_struct("QrVerification")
            .field("flow_id", &self.flow_id)
            .field("inner", self.inner.as_ref())
            .field("state", &self.state.lock().unwrap())
            .finish()
    }
}

impl QrVerification {
    /// Has the QR verification been scanned by the other side.
    ///
    /// When the verification object is in this state it's required that the
    /// user confirms that the other side has scanned the QR code.
    pub fn has_been_scanned(&self) -> bool {
        matches!(&*self.state.lock().unwrap(), InnerState::Scanned(_))
    }

    /// Has the scanning of the QR code been confirmed by us.
    pub fn has_been_confirmed(&self) -> bool {
        matches!(&*self.state.lock().unwrap(), InnerState::Confirmed(_))
    }

    /// Get our own user id.
    pub fn user_id(&self) -> &UserId {
        self.identities.user_id()
    }

    /// Get the user id of the other user that is participating in this
    /// verification flow.
    pub fn other_user_id(&self) -> &UserId {
        self.identities.other_user_id()
    }

    /// Get the device id of the other side.
    pub fn other_device_id(&self) -> &DeviceId {
        self.identities.other_device_id()
    }

    /// Did we initiate the verification request
    pub fn we_started(&self) -> bool {
        self.we_started
    }

    /// Get info about the cancellation if the verification flow has been
    /// cancelled.
    pub fn cancel_info(&self) -> Option<CancelInfo> {
        if let InnerState::Cancelled(c) = &*self.state.lock().unwrap() {
            Some(c.state.clone().into())
        } else {
            None
        }
    }

    /// Has the verification flow completed.
    pub fn is_done(&self) -> bool {
        matches!(&*self.state.lock().unwrap(), InnerState::Done(_))
    }

    /// Has the verification flow been cancelled.
    pub fn is_cancelled(&self) -> bool {
        matches!(&*self.state.lock().unwrap(), InnerState::Cancelled(_))
    }

    /// Is this a verification that is veryfying one of our own devices
    pub fn is_self_verification(&self) -> bool {
        self.identities.is_self_verification()
    }

    /// Have we successfully scanned the QR code and are able to send a
    /// reciprocation event.
    pub fn reciprocated(&self) -> bool {
        matches!(&*self.state.lock().unwrap(), InnerState::Reciprocated(_))
    }

    /// Get the unique ID that identifies this QR code verification flow.
    pub fn flow_id(&self) -> &FlowId {
        &self.flow_id
    }

    /// Get the room id if the verification is happening inside a room.
    pub fn room_id(&self) -> Option<&RoomId> {
        match self.flow_id() {
            FlowId::ToDevice(_) => None,
            FlowId::InRoom(r, _) => Some(r),
        }
    }

    /// Generate a QR code object that is representing this verification flow.
    ///
    /// The `QrCode` can then be rendered as an image or as an unicode string.
    ///
    /// The [`to_bytes()`](#method.to_bytes) method can be used to instead
    /// output the raw bytes that should be encoded as a QR code.
    pub fn to_qr_code(&self) -> Result<QrCode, EncodingError> {
        self.inner.to_qr_code()
    }

    /// Generate a the raw bytes that should be encoded as a QR code is
    /// representing this verification flow.
    ///
    /// The [`to_qr_code()`](#method.to_qr_code) method can be used to instead
    /// output a `QrCode` object that can be rendered.
    pub fn to_bytes(&self) -> Result<Vec<u8>, EncodingError> {
        self.inner.to_bytes()
    }

    /// Cancel the verification flow.
    pub fn cancel(&self) -> Option<OutgoingVerificationRequest> {
        self.cancel_with_code(CancelCode::User)
    }

    /// Cancel the verification.
    ///
    /// This cancels the verification with given `CancelCode`.
    ///
    /// **Note**: This method should generally not be used, the [`cancel()`]
    /// method should be preferred. The SDK will automatically cancel with the
    /// approprate cancel code, user initiated cancellations should only cancel
    /// with the `CancelCode::User`
    ///
    /// Returns None if the `Sas` object is already in a canceled state,
    /// otherwise it returns a request that needs to be sent out.
    ///
    /// [`cancel()`]: #method.cancel
    pub fn cancel_with_code(&self, code: CancelCode) -> Option<OutgoingVerificationRequest> {
        let mut state = self.state.lock().unwrap();

        if let Some(request) = &self.request_handle {
            request.cancel_with_code(&code);
        }

        let new_state = QrState::<Cancelled>::new(true, code);
        let content = new_state.as_content(self.flow_id());

        match &*state {
            InnerState::Confirmed(_)
            | InnerState::Created(_)
            | InnerState::Scanned(_)
            | InnerState::Reciprocated(_)
            | InnerState::Done(_) => {
                *state = InnerState::Cancelled(new_state);
                Some(self.content_to_request(content))
            }
            InnerState::Cancelled(_) => None,
        }
    }

    /// Notify the other side that we have successfully scanned the QR code and
    /// that the QR verification flow can start.
    ///
    /// This will return some `OutgoingContent` if the object is in the correct
    /// state to start the verification flow, otherwise `None`.
    pub fn reciprocate(&self) -> Option<OutgoingVerificationRequest> {
        match &*self.state.lock().unwrap() {
            InnerState::Reciprocated(s) => {
                Some(self.content_to_request(s.as_content(self.flow_id())))
            }
            InnerState::Created(_)
            | InnerState::Scanned(_)
            | InnerState::Confirmed(_)
            | InnerState::Done(_)
            | InnerState::Cancelled(_) => None,
        }
    }

    /// Confirm that the other side has scanned our QR code.
    pub fn confirm_scanning(&self) -> Option<OutgoingVerificationRequest> {
        let mut state = self.state.lock().unwrap();

        match &*state {
            InnerState::Scanned(s) => {
                let new_state = s.clone().confirm_scanning();
                let content = new_state.as_content(&self.flow_id);
                *state = InnerState::Confirmed(new_state);

                Some(self.content_to_request(content))
            }
            InnerState::Created(_)
            | InnerState::Cancelled(_)
            | InnerState::Confirmed(_)
            | InnerState::Reciprocated(_)
            | InnerState::Done(_) => None,
        }
    }

    fn content_to_request(&self, content: OutgoingContent) -> OutgoingVerificationRequest {
        match content {
            OutgoingContent::Room(room_id, content) => {
                RoomMessageRequest { room_id, txn_id: Uuid::new_v4(), content }.into()
            }
            OutgoingContent::ToDevice(c) => ToDeviceRequest::new(
                self.identities.other_user_id(),
                self.identities.other_device_id().to_owned(),
                c,
            )
            .into(),
        }
    }

    async fn mark_as_done(
        &self,
        new_state: QrState<Done>,
    ) -> Result<
        (Option<OutgoingVerificationRequest>, Option<SignatureUploadRequest>),
        CryptoStoreError,
    > {
        let (devices, identities) = new_state.verified_identities();

        let mut new_state = InnerState::Done(new_state);

        let (content, request) =
            match self.identities.mark_as_done(Some(&devices), Some(&identities)).await? {
                VerificationResult::Ok => (None, None),
                VerificationResult::Cancel(c) => {
                    let canceled = QrState::<Cancelled>::new(false, c);
                    let content = canceled.as_content(self.flow_id());
                    new_state = InnerState::Cancelled(canceled);
                    (Some(content), None)
                }
                VerificationResult::SignatureUpload(s) => (None, Some(s)),
            };

        *self.state.lock().unwrap() = new_state;

        Ok((content.map(|c| self.content_to_request(c)), request))
    }

    pub(crate) async fn receive_done(
        &self,
        content: &DoneContent<'_>,
    ) -> Result<
        (Option<OutgoingVerificationRequest>, Option<SignatureUploadRequest>),
        CryptoStoreError,
    > {
        let state = (*self.state.lock().unwrap()).clone();

        Ok(match state {
            InnerState::Confirmed(s) => {
                let (verified_device, verified_identity) = match &*self.inner {
                    QrVerificationData::Verification(_) => {
                        (None, self.identities.identity_being_verified.as_ref())
                    }
                    QrVerificationData::SelfVerification(_) => {
                        (Some(&self.identities.device_being_verified), None)
                    }
                    QrVerificationData::SelfVerificationNoMasterKey(_) => {
                        (None, self.identities.identity_being_verified.as_ref())
                    }
                };

                let new_state = s.clone().into_done(content, verified_device, verified_identity);
                self.mark_as_done(new_state).await?
            }
            InnerState::Reciprocated(s) => {
                let (verified_device, verified_identity) = match &*self.inner {
                    QrVerificationData::Verification(_) => {
                        (None, self.identities.identity_being_verified.as_ref())
                    }
                    QrVerificationData::SelfVerification(_) => {
                        (None, self.identities.identity_being_verified.as_ref())
                    }
                    QrVerificationData::SelfVerificationNoMasterKey(_) => {
                        (Some(&self.identities.device_being_verified), None)
                    }
                };

                let new_state = s.clone().into_done(content, verified_device, verified_identity);
                let content = Some(new_state.as_content(self.flow_id()));
                let (cancel_content, request) = self.mark_as_done(new_state).await?;

                if cancel_content.is_some() {
                    (cancel_content, request)
                } else {
                    (content.map(|c| self.content_to_request(c)), request)
                }
            }
            InnerState::Created(_)
            | InnerState::Scanned(_)
            | InnerState::Done(_)
            | InnerState::Cancelled(_) => (None, None),
        })
    }

    pub(crate) fn receive_reciprocation(
        &self,
        content: &StartContent,
    ) -> Option<OutgoingVerificationRequest> {
        let mut state = self.state.lock().unwrap();

        match &*state {
            InnerState::Created(s) => match s.clone().receive_reciprocate(content) {
                Ok(s) => {
                    *state = InnerState::Scanned(s);
                    None
                }
                Err(s) => {
                    let content = s.as_content(self.flow_id());
                    *state = InnerState::Cancelled(s);
                    Some(self.content_to_request(content))
                }
            },
            InnerState::Confirmed(_)
            | InnerState::Scanned(_)
            | InnerState::Reciprocated(_)
            | InnerState::Done(_)
            | InnerState::Cancelled(_) => None,
        }
    }

    pub(crate) fn receive_cancel(&self, sender: &UserId, content: &CancelContent<'_>) {
        if sender == self.other_user_id() {
            let mut state = self.state.lock().unwrap();

            let new_state = match &*state {
                InnerState::Created(s) => s.clone().into_cancelled(content),
                InnerState::Scanned(s) => s.clone().into_cancelled(content),
                InnerState::Confirmed(s) => s.clone().into_cancelled(content),
                InnerState::Reciprocated(s) => s.clone().into_cancelled(content),
                InnerState::Done(_) | InnerState::Cancelled(_) => return,
            };

            trace!(
                sender = sender.as_str(),
                code = content.cancel_code().as_str(),
                "Cancelling a QR verification, other user has cancelled"
            );

            *state = InnerState::Cancelled(new_state);
        }
    }

    fn generate_secret() -> String {
        let mut shared_secret = [0u8; SECRET_SIZE];
        getrandom::getrandom(&mut shared_secret)
            .expect("Can't generate randomness for the shared secret");
        crate::utilities::encode(shared_secret)
    }

    pub(crate) fn new_self(
        flow_id: FlowId,
        own_master_key: String,
        other_device_key: String,
        identities: IdentitiesBeingVerified,
        we_started: bool,
        request_handle: Option<RequestHandle>,
    ) -> Self {
        let secret = Self::generate_secret();

        let inner: QrVerificationData = SelfVerificationData::new(
            flow_id.as_str().to_owned(),
            own_master_key,
            other_device_key,
            secret,
        )
        .into();

        Self::new_helper(flow_id, inner, identities, we_started, request_handle)
    }

    pub(crate) fn new_self_no_master(
        store: VerificationStore,
        flow_id: FlowId,
        own_master_key: String,
        identities: IdentitiesBeingVerified,
        we_started: bool,
        request_handle: Option<RequestHandle>,
    ) -> QrVerification {
        let secret = Self::generate_secret();

        let inner: QrVerificationData = SelfVerificationNoMasterKey::new(
            flow_id.as_str().to_owned(),
            store.account.identity_keys().ed25519().to_string(),
            own_master_key,
            secret,
        )
        .into();

        Self::new_helper(flow_id, inner, identities, we_started, request_handle)
    }

    pub(crate) fn new_cross(
        flow_id: FlowId,
        own_master_key: String,
        other_master_key: String,
        identities: IdentitiesBeingVerified,
        we_started: bool,
        request_handle: Option<RequestHandle>,
    ) -> Self {
        let secret = Self::generate_secret();

        let event_id = if let FlowId::InRoom(_, e) = &flow_id {
            e.to_owned()
        } else {
            panic!("A verification between users is only valid in a room");
        };

        let inner: QrVerificationData =
            VerificationData::new(event_id, own_master_key, other_master_key, secret).into();

        Self::new_helper(flow_id, inner, identities, we_started, request_handle)
    }

    #[allow(clippy::too_many_arguments)]
    pub(crate) async fn from_scan(
        store: VerificationStore,
        private_identity: PrivateCrossSigningIdentity,
        other_user_id: UserId,
        other_device_id: DeviceIdBox,
        flow_id: FlowId,
        qr_code: QrVerificationData,
        we_started: bool,
        request_handle: Option<RequestHandle>,
    ) -> Result<Self, ScanError> {
        if flow_id.as_str() != qr_code.flow_id() {
            return Err(ScanError::FlowIdMismatch {
                expected: flow_id.as_str().to_owned(),
                found: qr_code.flow_id().to_owned(),
            });
        }

        let own_identity =
            store.get_user_identity(store.account.user_id()).await?.ok_or_else(|| {
                ScanError::MissingCrossSigningIdentity(store.account.user_id().to_owned())
            })?;
        let other_identity = store
            .get_user_identity(&other_user_id)
            .await?
            .ok_or_else(|| ScanError::MissingCrossSigningIdentity(other_user_id.clone()))?;
        let other_device =
            store.get_device(&other_user_id, &other_device_id).await?.ok_or_else(|| {
                ScanError::MissingDeviceKeys(other_user_id.clone(), other_device_id.clone())
            })?;

        let check_master_key = |key, identity: &ReadOnlyUserIdentities| {
            let master_key = identity.master_key().get_first_key().ok_or_else(|| {
                ScanError::MissingCrossSigningIdentity(identity.user_id().clone())
            })?;

            if key != master_key {
                Err(ScanError::KeyMismatch {
                    expected: master_key.to_owned(),
                    found: qr_code.first_key().to_owned(),
                })
            } else {
                Ok(())
            }
        };

        let (device_being_verified, identity_being_verified) = match qr_code {
            QrVerificationData::Verification(_) => {
                check_master_key(qr_code.first_key(), &other_identity)?;
                check_master_key(qr_code.second_key(), &own_identity)?;

                (other_device, Some(other_identity))
            }
            QrVerificationData::SelfVerification(_) => {
                check_master_key(qr_code.first_key(), &other_identity)?;
                if qr_code.second_key() != store.account.identity_keys().ed25519() {
                    return Err(ScanError::KeyMismatch {
                        expected: store.account.identity_keys().ed25519().to_owned(),
                        found: qr_code.second_key().to_owned(),
                    });
                }

                (other_device, Some(other_identity))
            }
            QrVerificationData::SelfVerificationNoMasterKey(_) => {
                let device_key =
                    other_device.get_key(DeviceKeyAlgorithm::Ed25519).ok_or_else(|| {
                        ScanError::MissingDeviceKeys(other_user_id.clone(), other_device_id.clone())
                    })?;
                if qr_code.first_key() != device_key {
                    return Err(ScanError::KeyMismatch {
                        expected: device_key.to_owned(),
                        found: qr_code.first_key().to_owned(),
                    });
                }
                check_master_key(qr_code.second_key(), &other_identity)?;
                (other_device, Some(other_identity))
            }
        };

        let identities = IdentitiesBeingVerified {
            private_identity,
            store: store.clone(),
            device_being_verified,
            identity_being_verified,
        };

        let secret = qr_code.secret().to_owned();
        let own_device_id = store.account.device_id().to_owned();

        Ok(Self {
            flow_id,
            inner: qr_code.into(),
            state: Mutex::new(InnerState::Reciprocated(QrState {
                state: Reciprocated { secret, own_device_id },
            }))
            .into(),
            identities,
            we_started,
            request_handle,
        })
    }

    fn new_helper(
        flow_id: FlowId,
        inner: QrVerificationData,
        identities: IdentitiesBeingVerified,
        we_started: bool,
        request_handle: Option<RequestHandle>,
    ) -> Self {
        let secret = inner.secret().to_owned();

        Self {
            flow_id,
            inner: inner.into(),
            state: Mutex::new(InnerState::Created(QrState { state: Created { secret } })).into(),
            identities,
            we_started,
            request_handle,
        }
    }
}

#[derive(Debug, Clone)]
enum InnerState {
    Created(QrState<Created>),
    Scanned(QrState<Scanned>),
    Confirmed(QrState<Confirmed>),
    Reciprocated(QrState<Reciprocated>),
    Done(QrState<Done>),
    Cancelled(QrState<Cancelled>),
}

#[derive(Clone, Debug)]
struct QrState<S: Clone> {
    state: S,
}

impl<S: Clone> QrState<S> {
    pub fn into_cancelled(self, content: &CancelContent<'_>) -> QrState<Cancelled> {
        QrState { state: Cancelled::new(false, content.cancel_code().to_owned()) }
    }
}

#[derive(Clone, Debug)]
struct Created {
    secret: String,
}

#[derive(Clone, Debug)]
struct Scanned {}

#[derive(Clone, Debug)]
struct Confirmed {}

#[derive(Clone, Debug)]
struct Reciprocated {
    own_device_id: DeviceIdBox,
    secret: String,
}

impl Reciprocated {
    fn as_content(&self, flow_id: &FlowId) -> OutgoingContent {
        let content = ReciprocateV1Content::new(self.secret.clone());
        let method = StartMethod::ReciprocateV1(content);

        let content: OwnedStartContent = match flow_id {
            FlowId::ToDevice(t) => {
                StartToDeviceEventContent::new(self.own_device_id.clone(), t.clone(), method).into()
            }
            FlowId::InRoom(r, e) => (
                r.clone(),
                StartEventContent::new(
                    self.own_device_id.clone(),
                    method,
                    Relation::new(e.clone()),
                ),
            )
                .into(),
        };

        content.into()
    }
}

impl QrState<Scanned> {
    fn confirm_scanning(self) -> QrState<Confirmed> {
        QrState { state: Confirmed {} }
    }
}

impl QrState<Cancelled> {
    fn new(cancelled_by_us: bool, cancel_code: CancelCode) -> Self {
        QrState { state: Cancelled::new(cancelled_by_us, cancel_code) }
    }

    fn as_content(&self, flow_id: &FlowId) -> OutgoingContent {
        self.state.as_content(flow_id)
    }
}

impl QrState<Created> {
    fn receive_reciprocate(
        self,
        content: &StartContent,
    ) -> Result<QrState<Scanned>, QrState<Cancelled>> {
        match content.method() {
            start::StartMethod::ReciprocateV1(m) => {
                // TODO use constant time eq here.
                if self.state.secret == m.secret {
                    Ok(QrState { state: Scanned {} })
                } else {
                    Err(QrState::<Cancelled>::new(false, CancelCode::KeyMismatch))
                }
            }
            _ => Err(QrState::<Cancelled>::new(false, CancelCode::UnknownMethod)),
        }
    }
}

impl QrState<Done> {
    fn as_content(&self, flow_id: &FlowId) -> OutgoingContent {
        self.state.as_content(flow_id)
    }

    fn verified_identities(&self) -> (Arc<[ReadOnlyDevice]>, Arc<[ReadOnlyUserIdentities]>) {
        (self.state.verified_devices.clone(), self.state.verified_master_keys.clone())
    }
}

impl QrState<Confirmed> {
    fn into_done(
        self,
        _: &DoneContent,
        verified_device: Option<&ReadOnlyDevice>,
        verified_identity: Option<&ReadOnlyUserIdentities>,
    ) -> QrState<Done> {
        let devices: Vec<_> = verified_device.into_iter().cloned().collect();
        let identities: Vec<_> = verified_identity.into_iter().cloned().collect();

        QrState {
            state: Done {
                verified_devices: devices.into(),
                verified_master_keys: identities.into(),
            },
        }
    }

    fn as_content(&self, flow_id: &FlowId) -> OutgoingContent {
        match flow_id {
            FlowId::ToDevice(t) => AnyToDeviceEventContent::KeyVerificationDone(
                DoneToDeviceEventContent::new(t.to_owned()),
            )
            .into(),
            FlowId::InRoom(r, e) => (
                r.to_owned(),
                AnyMessageEventContent::KeyVerificationDone(DoneEventContent::new(Relation::new(
                    e.to_owned(),
                ))),
            )
                .into(),
        }
    }
}

impl QrState<Reciprocated> {
    fn as_content(&self, flow_id: &FlowId) -> OutgoingContent {
        self.state.as_content(flow_id)
    }

    fn into_done(
        self,
        _: &DoneContent,
        verified_device: Option<&ReadOnlyDevice>,
        verified_identity: Option<&ReadOnlyUserIdentities>,
    ) -> QrState<Done> {
        let devices: Vec<_> = verified_device.into_iter().cloned().collect();
        let identities: Vec<_> = verified_identity.into_iter().cloned().collect();

        QrState {
            state: Done {
                verified_devices: devices.into(),
                verified_master_keys: identities.into(),
            },
        }
    }
}

#[cfg(test)]
mod test {
    use std::{convert::TryFrom, sync::Arc};

    use matrix_qrcode::QrVerificationData;
    use matrix_sdk_test::async_test;
    use ruma::{event_id, room_id, user_id, DeviceIdBox, UserId};

    use crate::{
        olm::{PrivateCrossSigningIdentity, ReadOnlyAccount},
        store::{Changes, CryptoStore, MemoryStore},
        verification::{
            event_enums::{DoneContent, OutgoingContent, StartContent},
            FlowId, IdentitiesBeingVerified, VerificationStore,
        },
        QrVerification, ReadOnlyDevice,
    };

    fn user_id() -> UserId {
        user_id!("@example:localhost")
    }

    fn memory_store() -> Arc<dyn CryptoStore> {
        Arc::new(MemoryStore::new())
    }

    fn device_id() -> DeviceIdBox {
        "DEVICEID".into()
    }

    #[async_test]
    async fn test_verification_creation() {
        let store = memory_store();
        let account = ReadOnlyAccount::new(&user_id(), &device_id());

        let store = VerificationStore { account: account.clone(), inner: store };

        let private_identity = PrivateCrossSigningIdentity::new(user_id()).await;
        let flow_id = FlowId::ToDevice("test_transaction".to_owned());

        let device_key = account.identity_keys().ed25519().to_owned();
        let master_key = private_identity.master_public_key().await.unwrap();
        let master_key = master_key.get_first_key().unwrap().to_owned();

        let alice_device = ReadOnlyDevice::from_account(&account).await;

        let identities = IdentitiesBeingVerified {
            private_identity,
            store: store.clone(),
            device_being_verified: alice_device,
            identity_being_verified: None,
        };

        let verification = QrVerification::new_self_no_master(
            store.clone(),
            flow_id.clone(),
            master_key.clone(),
            identities.clone(),
            false,
            None,
        );

        assert_eq!(verification.inner.first_key(), &device_key);
        assert_eq!(verification.inner.second_key(), &master_key);

        let verification = QrVerification::new_self(
            flow_id,
            master_key.clone(),
            device_key.clone(),
            identities.clone(),
            false,
            None,
        );

        assert_eq!(verification.inner.first_key(), &master_key);
        assert_eq!(verification.inner.second_key(), &device_key);

        let bob_identity = PrivateCrossSigningIdentity::new(user_id!("@bob:example")).await;
        let bob_master_key = bob_identity.master_public_key().await.unwrap();
        let bob_master_key = bob_master_key.get_first_key().unwrap().to_owned();

        let flow_id = FlowId::InRoom(room_id!("!test:example"), event_id!("$EVENTID"));

        let verification = QrVerification::new_cross(
            flow_id,
            master_key.clone(),
            bob_master_key.clone(),
            identities,
            false,
            None,
        );

        assert_eq!(verification.inner.first_key(), &master_key);
        assert_eq!(verification.inner.second_key(), &bob_master_key);
    }

    #[async_test]
    async fn test_reciprocate_receival() {
        let test = |flow_id: FlowId| async move {
            let alice_account = ReadOnlyAccount::new(&user_id(), &device_id());
            let store = memory_store();

            let store = VerificationStore { account: alice_account.clone(), inner: store };

            let bob_account = ReadOnlyAccount::new(alice_account.user_id(), "BOBDEVICE".into());

            let private_identity = PrivateCrossSigningIdentity::new(user_id()).await;
            let identity = private_identity.to_public_identity().await.unwrap();

            let master_key = private_identity.master_public_key().await.unwrap();
            let master_key = master_key.get_first_key().unwrap().to_owned();

            let alice_device = ReadOnlyDevice::from_account(&alice_account).await;
            let bob_device = ReadOnlyDevice::from_account(&bob_account).await;

            let mut changes = Changes::default();
            changes.identities.new.push(identity.clone().into());
            changes.devices.new.push(bob_device.clone());
            store.save_changes(changes).await.unwrap();

            let identities = IdentitiesBeingVerified {
                private_identity: PrivateCrossSigningIdentity::empty(
                    alice_account.user_id().to_owned(),
                ),
                store: store.clone(),
                device_being_verified: alice_device.clone(),
                identity_being_verified: Some(identity.clone().into()),
            };

            let alice_verification = QrVerification::new_self_no_master(
                store,
                flow_id.clone(),
                master_key.clone(),
                identities,
                false,
                None,
            );

            let bob_store = memory_store();

            let bob_store = VerificationStore { account: bob_account.clone(), inner: bob_store };

            let mut changes = Changes::default();
            changes.identities.new.push(identity.into());
            changes.devices.new.push(alice_device.clone());
            bob_store.save_changes(changes).await.unwrap();

            let qr_code = alice_verification.to_bytes().unwrap();
            let qr_code = QrVerificationData::from_bytes(qr_code).unwrap();

            let bob_verification = QrVerification::from_scan(
                bob_store,
                private_identity,
                alice_account.user_id().to_owned(),
                alice_account.device_id().to_owned(),
                flow_id,
                qr_code,
                false,
                None,
            )
            .await
            .unwrap();

            let request = bob_verification.reciprocate().unwrap();
            let content = OutgoingContent::try_from(request).unwrap();
            let content = StartContent::try_from(&content).unwrap();

            alice_verification.receive_reciprocation(&content);

            let request = alice_verification.confirm_scanning().unwrap();
            let content = OutgoingContent::try_from(request).unwrap();
            let content = DoneContent::try_from(&content).unwrap();

            assert!(!alice_verification.is_done());
            assert!(!bob_verification.is_done());

            let (request, _) = bob_verification.receive_done(&content).await.unwrap();
            let content = OutgoingContent::try_from(request.unwrap()).unwrap();
            let content = DoneContent::try_from(&content).unwrap();
            alice_verification.receive_done(&content).await.unwrap();

            assert!(alice_verification.is_done());
            assert!(bob_verification.is_done());

            let identity = alice_verification
                .identities
                .store
                .get_user_identity(alice_account.user_id())
                .await
                .unwrap()
                .unwrap();

            let identity = identity.own().unwrap();

            assert!(!bob_device.is_locally_trusted());
            assert!(alice_device.is_locally_trusted());
            assert!(identity.is_verified());
        };

        let flow_id = FlowId::ToDevice("test_transaction".to_owned());
        test(flow_id).await;

        let flow_id = FlowId::InRoom(room_id!("!test:example"), event_id!("$EVENTID"));
        test(flow_id).await;
    }
}
