// Copyright 2020 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.

mod cache;
mod event_enums;
mod machine;
mod qrcode;
mod requests;
mod sas;

use std::sync::Arc;

use event_enums::OutgoingContent;
pub use machine::VerificationMachine;
pub use qrcode::QrVerification;
pub use requests::VerificationRequest;
use ruma::{
    api::client::r0::keys::upload_signatures::Request as SignatureUploadRequest,
    events::{
        key::verification::{
            cancel::{CancelCode, CancelEventContent, CancelToDeviceEventContent},
            done::{DoneEventContent, DoneToDeviceEventContent},
            Relation,
        },
        AnyMessageEventContent, AnyToDeviceEventContent,
    },
    DeviceId, EventId, RoomId, UserId,
};
pub use sas::{AcceptSettings, Sas};
use tracing::{error, info, trace, warn};

use crate::{
    error::SignatureError,
    olm::PrivateCrossSigningIdentity,
    store::{Changes, CryptoStore},
    CryptoStoreError, LocalTrust, ReadOnlyDevice, UserIdentities,
};

/// An enum over the different verification types the SDK supports.
#[derive(Clone, Debug)]
pub enum Verification {
    /// The `m.sas.v1` verification variant.
    SasV1(Sas),
    /// The `m.qr_code.*.v1` verification variant.
    QrV1(QrVerification),
}

impl Verification {
    /// Try to deconstruct this verification enum into a SAS verification.
    pub fn sas_v1(self) -> Option<Sas> {
        if let Verification::SasV1(sas) = self {
            Some(sas)
        } else {
            None
        }
    }

    /// Try to deconstruct this verification enum into a QR code verification.
    pub fn qr_v1(self) -> Option<QrVerification> {
        if let Verification::QrV1(qr) = self {
            Some(qr)
        } else {
            None
        }
    }

    /// Has this verification finished.
    pub fn is_done(&self) -> bool {
        match self {
            Verification::SasV1(s) => s.is_done(),
            Verification::QrV1(qr) => qr.is_done(),
        }
    }

    /// Get the ID that uniquely identifies this verification flow.
    pub fn flow_id(&self) -> &str {
        match self {
            Verification::SasV1(s) => s.flow_id().as_str(),
            Verification::QrV1(qr) => qr.flow_id().as_str(),
        }
    }

    /// Has the verification been cancelled.
    pub fn is_cancelled(&self) -> bool {
        match self {
            Verification::SasV1(s) => s.is_cancelled(),
            Verification::QrV1(qr) => qr.is_cancelled(),
        }
    }

    /// Get our own user id that is participating in this verification.
    pub fn user_id(&self) -> &UserId {
        match self {
            Verification::SasV1(v) => v.user_id(),
            Verification::QrV1(v) => v.user_id(),
        }
    }

    /// Get the other user id that is participating in this verification.
    pub fn other_user(&self) -> &UserId {
        match self {
            Verification::SasV1(s) => s.other_user_id(),
            Verification::QrV1(qr) => qr.other_user_id(),
        }
    }

    /// Is this a verification verifying a device that belongs to us.
    pub fn is_self_verification(&self) -> bool {
        match self {
            Verification::SasV1(v) => v.is_self_verification(),
            Verification::QrV1(v) => v.is_self_verification(),
        }
    }
}

impl From<Sas> for Verification {
    fn from(sas: Sas) -> Self {
        Self::SasV1(sas)
    }
}

impl From<QrVerification> for Verification {
    fn from(qr: QrVerification) -> Self {
        Self::QrV1(qr)
    }
}

/// The verification state indicating that the verification finished
/// successfully.
///
/// We can now mark the device in our verified devices list as verified and sign
/// the master keys in the verified devices list.
#[derive(Clone, Debug)]
pub struct Done {
    verified_devices: Arc<[ReadOnlyDevice]>,
    verified_master_keys: Arc<[UserIdentities]>,
}

impl Done {
    pub 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(),
        }
    }
}

#[derive(Clone, Debug)]
pub struct Cancelled {
    cancel_code: CancelCode,
    reason: &'static str,
}

impl Cancelled {
    fn new(code: CancelCode) -> Self {
        let reason = match code {
            CancelCode::Accepted => {
                "A m.key.verification.request was accepted by a different device."
            }
            CancelCode::InvalidMessage => "The received message was invalid.",
            CancelCode::KeyMismatch => "The expected key did not match the verified one",
            CancelCode::Timeout => "The verification process timed out.",
            CancelCode::UnexpectedMessage => "The device received an unexpected message.",
            CancelCode::UnknownMethod => {
                "The device does not know how to handle the requested method."
            }
            CancelCode::UnknownTransaction => {
                "The device does not know about the given transaction ID."
            }
            CancelCode::User => "The user cancelled the verification.",
            CancelCode::UserMismatch => "The expected user did not match the verified user",
            _ => "Unknown cancel reason",
        };

        Self { cancel_code: code, reason }
    }

    pub fn as_content(&self, flow_id: &FlowId) -> OutgoingContent {
        match flow_id {
            FlowId::ToDevice(s) => {
                AnyToDeviceEventContent::KeyVerificationCancel(CancelToDeviceEventContent::new(
                    s.clone(),
                    self.reason.to_string(),
                    self.cancel_code.clone(),
                ))
                .into()
            }

            FlowId::InRoom(r, e) => (
                r.clone(),
                AnyMessageEventContent::KeyVerificationCancel(CancelEventContent::new(
                    self.reason.to_string(),
                    self.cancel_code.clone(),
                    Relation::new(e.clone()),
                )),
            )
                .into(),
        }
    }
}

#[derive(Clone, Debug, Hash, PartialEq, PartialOrd)]
pub enum FlowId {
    ToDevice(String),
    InRoom(RoomId, EventId),
}

impl FlowId {
    pub fn room_id(&self) -> Option<&RoomId> {
        if let FlowId::InRoom(r, _) = &self {
            Some(r)
        } else {
            None
        }
    }

    pub fn as_str(&self) -> &str {
        match self {
            FlowId::InRoom(_, r) => r.as_str(),
            FlowId::ToDevice(t) => t.as_str(),
        }
    }
}

impl From<String> for FlowId {
    fn from(transaction_id: String) -> Self {
        FlowId::ToDevice(transaction_id)
    }
}

impl From<(RoomId, EventId)> for FlowId {
    fn from(ids: (RoomId, EventId)) -> Self {
        FlowId::InRoom(ids.0, ids.1)
    }
}

impl From<(&RoomId, &EventId)> for FlowId {
    fn from(ids: (&RoomId, &EventId)) -> Self {
        FlowId::InRoom(ids.0.to_owned(), ids.1.to_owned())
    }
}

/// A result of a verification flow.
#[derive(Clone, Debug)]
pub enum VerificationResult {
    /// The verification succeeded, nothing needs to be done.
    Ok,
    /// The verification was canceled.
    Cancel(CancelCode),
    /// The verification is done and has signatures that need to be uploaded.
    SignatureUpload(SignatureUploadRequest),
}

#[derive(Clone, Debug)]
pub struct IdentitiesBeingVerified {
    private_identity: PrivateCrossSigningIdentity,
    store: Arc<dyn CryptoStore>,
    device_being_verified: ReadOnlyDevice,
    identity_being_verified: Option<UserIdentities>,
}

impl IdentitiesBeingVerified {
    async fn can_sign_devices(&self) -> bool {
        self.private_identity.can_sign_devices().await
    }

    fn user_id(&self) -> &UserId {
        self.private_identity.user_id()
    }

    fn is_self_verification(&self) -> bool {
        self.user_id() == self.other_user_id()
    }

    fn other_user_id(&self) -> &UserId {
        self.device_being_verified.user_id()
    }

    fn other_device_id(&self) -> &DeviceId {
        self.device_being_verified.device_id()
    }

    fn other_device(&self) -> &ReadOnlyDevice {
        &self.device_being_verified
    }

    pub async fn mark_as_done(
        &self,
        verified_devices: Option<&[ReadOnlyDevice]>,
        verified_identities: Option<&[UserIdentities]>,
    ) -> Result<VerificationResult, CryptoStoreError> {
        let device = self.mark_device_as_verified(verified_devices).await?;
        let identity = self.mark_identity_as_verified(verified_identities).await?;

        if device.is_none() && identity.is_none() {
            // Something wen't wrong if nothing was verified, we use key
            // mismatch here, since it's the closest to nothing was verified
            return Ok(VerificationResult::Cancel(CancelCode::KeyMismatch));
        }

        let mut changes = Changes::default();

        let signature_request = if let Some(device) = device {
            // We only sign devices of our own user here.
            let signature_request = if device.user_id() == self.user_id() {
                match self.private_identity.sign_device(&device).await {
                    Ok(r) => Some(r),
                    Err(SignatureError::MissingSigningKey) => {
                        warn!(
                            "Can't sign the device keys for {} {}, \
                                  no private user signing key found",
                            device.user_id(),
                            device.device_id(),
                        );

                        None
                    }
                    Err(e) => {
                        error!(
                            "Error signing device keys for {} {} {:?}",
                            device.user_id(),
                            device.device_id(),
                            e
                        );
                        None
                    }
                }
            } else {
                None
            };

            changes.devices.changed.push(device);
            signature_request
        } else {
            None
        };

        let identity_signature_request = if let Some(i) = identity {
            // We only sign other users here.
            let request = if let Some(i) = i.other() {
                // Signing can fail if the user signing key is missing.
                match self.private_identity.sign_user(i).await {
                    Ok(r) => Some(r),
                    Err(SignatureError::MissingSigningKey) => {
                        warn!(
                            "Can't sign the public cross signing keys for {}, \
                              no private user signing key found",
                            i.user_id()
                        );
                        None
                    }
                    Err(e) => {
                        error!(
                            "Error signing the public cross signing keys for {} {:?}",
                            i.user_id(),
                            e
                        );
                        None
                    }
                }
            } else {
                None
            };

            changes.identities.changed.push(i);
            request
        } else {
            None
        };

        // If there are two signature upload requests, merge them. Otherwise
        // use the one we have or None.
        //
        // Realistically at most one request will be used but let's make
        // this future proof.
        let merged_request = if let Some(mut r) = signature_request {
            if let Some(user_request) = identity_signature_request {
                r.signed_keys.extend(user_request.signed_keys);
                Some(r)
            } else {
                Some(r)
            }
        } else {
            identity_signature_request
        };

        // TODO store the signature upload request as well.
        self.store.save_changes(changes).await?;

        Ok(merged_request
            .map(VerificationResult::SignatureUpload)
            .unwrap_or(VerificationResult::Ok))
    }

    async fn mark_identity_as_verified(
        &self,
        verified_identities: Option<&[UserIdentities]>,
    ) -> Result<Option<UserIdentities>, CryptoStoreError> {
        // If there wasn't an identity available during the verification flow
        // return early as there's nothing to do.
        if self.identity_being_verified.is_none() {
            return Ok(None);
        }

        let identity = self.store.get_user_identity(self.other_user_id()).await?;

        if let Some(identity) = identity {
            if self
                .identity_being_verified
                .as_ref()
                .map_or(false, |i| i.master_key() == identity.master_key())
            {
                if verified_identities.map_or(false, |i| i.contains(&identity)) {
                    trace!(
                        user_id = self.other_user_id().as_str(),
                        "Marking the user identity of as verified."
                    );

                    if let UserIdentities::Own(i) = &identity {
                        i.mark_as_verified();
                    }

                    Ok(Some(identity))
                } else {
                    info!(
                        user_id = self.other_user_id().as_str(),
                        "The interactive verification process didn't verify \
                         the user identity of the user that participated in \
                         the interactive verification",
                    );

                    Ok(None)
                }
            } else {
                warn!(
                    user_id = self.other_user_id().as_str(),
                    "The master keys of the user have changed while an interactive \
                      verification was going on, not marking the identity as verified.",
                );

                Ok(None)
            }
        } else {
            info!(
                user_id = self.other_user_id().as_str(),
                "The identity of the user was deleted while an interactive \
                 verification was going on.",
            );
            Ok(None)
        }
    }

    async fn mark_device_as_verified(
        &self,
        verified_devices: Option<&[ReadOnlyDevice]>,
    ) -> Result<Option<ReadOnlyDevice>, CryptoStoreError> {
        let device = self.store.get_device(self.other_user_id(), self.other_device_id()).await?;

        if let Some(device) = device {
            if device.keys() == self.device_being_verified.keys() {
                if verified_devices.map_or(false, |v| v.contains(&device)) {
                    trace!(
                        user_id = device.user_id().as_str(),
                        device_id = device.device_id().as_str(),
                        "Marking device as verified.",
                    );

                    device.set_trust_state(LocalTrust::Verified);

                    Ok(Some(device))
                } else {
                    info!(
                        user_id = device.user_id().as_str(),
                        device_id = device.device_id().as_str(),
                        "The interactive verification process didn't verify \
                        the device",
                    );

                    Ok(None)
                }
            } else {
                warn!(
                    user_id = device.user_id().as_str(),
                    device_id = device.device_id().as_str(),
                    "The device keys have changed while an interactive \
                     verification was going on, not marking the device as verified.",
                );
                Ok(None)
            }
        } else {
            let device = &self.device_being_verified;

            info!(
                user_id = device.user_id().as_str(),
                device_id = device.device_id().as_str(),
                "The device was deleted while an interactive verification was \
                 going on.",
            );

            Ok(None)
        }
    }
}

#[cfg(test)]
pub(crate) mod test {
    use ruma::{
        events::{AnyToDeviceEvent, AnyToDeviceEventContent, ToDeviceEvent},
        UserId,
    };

    use super::event_enums::OutgoingContent;
    use crate::{
        requests::{OutgoingRequest, OutgoingRequests},
        OutgoingVerificationRequest,
    };

    pub(crate) fn request_to_event(
        sender: &UserId,
        request: &OutgoingVerificationRequest,
    ) -> AnyToDeviceEvent {
        let content = request.to_owned().into();
        wrap_any_to_device_content(sender, content)
    }

    pub(crate) fn outgoing_request_to_event(
        sender: &UserId,
        request: &OutgoingRequest,
    ) -> AnyToDeviceEvent {
        match request.request() {
            OutgoingRequests::ToDeviceRequest(r) => request_to_event(sender, &r.clone().into()),
            _ => panic!("Unsupported outgoing request"),
        }
    }

    pub(crate) fn wrap_any_to_device_content(
        sender: &UserId,
        content: OutgoingContent,
    ) -> AnyToDeviceEvent {
        let content = if let OutgoingContent::ToDevice(c) = content { c } else { unreachable!() };

        match content {
            AnyToDeviceEventContent::KeyVerificationKey(c) => {
                AnyToDeviceEvent::KeyVerificationKey(ToDeviceEvent {
                    sender: sender.clone(),
                    content: c,
                })
            }
            AnyToDeviceEventContent::KeyVerificationStart(c) => {
                AnyToDeviceEvent::KeyVerificationStart(ToDeviceEvent {
                    sender: sender.clone(),
                    content: c,
                })
            }
            AnyToDeviceEventContent::KeyVerificationAccept(c) => {
                AnyToDeviceEvent::KeyVerificationAccept(ToDeviceEvent {
                    sender: sender.clone(),
                    content: c,
                })
            }
            AnyToDeviceEventContent::KeyVerificationMac(c) => {
                AnyToDeviceEvent::KeyVerificationMac(ToDeviceEvent {
                    sender: sender.clone(),
                    content: c,
                })
            }

            _ => unreachable!(),
        }
    }
}
