use super::keys::{PublicKey, Signature, SignatureShare};
use super::money::Money;
use crdts::Dot;
use serde::{Deserialize, Serialize};
use std::fmt::Debug;
use threshold_crypto::PublicKeySet;

/// Transfer ID.
pub type TransferId = Dot<PublicKey>;

/// A transfer of money between two keys.
#[derive(Clone, Hash, Eq, PartialEq, PartialOrd, Serialize, Deserialize, Debug)]
pub struct Transfer {
    /// Transfer ID, containing source key.
    pub id: TransferId,
    /// The destination to transfer to.
    pub to: PublicKey,
    /// The amount to transfer.
    pub amount: Money,
}

impl Transfer {
    /// Get the transfer id
    pub fn id(&self) -> TransferId {
        self.id
    }

    /// Get the amount of this transfer
    pub fn amount(&self) -> Money {
        self.amount
    }

    /// Get the sender of this transfer
    pub fn from(&self) -> PublicKey {
        self.id.actor
    }

    /// Get the recipient of this transfer
    pub fn to(&self) -> PublicKey {
        self.to
    }
}

/// The aggregated Replica signatures of the Actor debit cmd.
#[derive(Clone, Eq, PartialEq, Serialize, Deserialize, Debug)]
pub struct DebitAgreementProof {
    /// The cmd generated by sender Actor.
    pub signed_transfer: SignedTransfer,
    /// Quorum of Replica sigs over the transfer cmd.
    pub debiting_replicas_sig: Signature,
    /// PublicKeySet of the replica when it validated the transfer.
    pub replica_key: ReplicaPublicKeySet,
}

impl DebitAgreementProof {
    /// Get the transfer id
    pub fn id(&self) -> TransferId {
        self.signed_transfer.id()
    }

    /// Get the amount of this transfer
    pub fn amount(&self) -> Money {
        self.signed_transfer.amount()
    }

    /// Get the sender of this transfer
    pub fn from(&self) -> PublicKey {
        self.signed_transfer.from()
    }

    /// Get the recipient of this transfer
    pub fn to(&self) -> PublicKey {
        self.signed_transfer.to()
    }

    /// Get the PublicKeySet of the replica that validated this transfer
    pub fn replica_keys(&self) -> ReplicaPublicKeySet {
        self.replica_key.clone()
    }
}

/// An Actor cmd.
#[derive(Clone, Eq, PartialEq, Serialize, Deserialize, Debug)]
pub struct SignedTransfer {
    /// The transfer.
    pub transfer: Transfer,
    /// Actor signature over the transfer.
    pub actor_signature: Signature,
}

impl SignedTransfer {
    /// Get the transfer id
    pub fn id(&self) -> TransferId {
        self.transfer.id
    }

    /// Get the amount of this transfer
    pub fn amount(&self) -> Money {
        self.transfer.amount
    }

    /// Get the sender of this transfer
    pub fn from(&self) -> PublicKey {
        self.transfer.id.actor
    }

    /// Get the recipient of this transfer
    pub fn to(&self) -> PublicKey {
        self.transfer.to
    }
}

// ------------------------------------------------------------
//                      Replica
// ------------------------------------------------------------

/// Events raised by the Replica.
#[allow(clippy::large_enum_variant)]
#[derive(Clone, Eq, PartialEq, Serialize, Deserialize, Debug)]
pub enum ReplicaEvent {
    /// The event raised when
    /// ValidateTransfer cmd has been successful.
    TransferValidated(TransferValidated),
    /// The event raised when
    /// RegisterTransfer cmd has been successful.
    TransferRegistered(TransferRegistered),
    /// The event raised when
    /// PropagateTransfer cmd has been successful.
    TransferPropagated(TransferPropagated),
    // /// The event raised when
    // /// peers changed so that we have a new PublicKeySet.
    // PeersChanged(PeersChanged),
    /// The event raised when
    /// we learn of a new group PK set.
    KnownGroupAdded(KnownGroupAdded),
}

/// The debiting Replica event raised when
/// ValidateTransfer cmd has been successful.
#[derive(Clone, Eq, PartialEq, Serialize, Deserialize, Debug)]
pub struct TransferValidated {
    /// The cmd generated by Actor.
    pub signed_transfer: SignedTransfer,
    /// Replica signature over the transfer cmd.
    pub replica_signature: SignatureShare,
    /// The PK Set of the Replicas
    pub replicas: PublicKeySet,
    // NB: I'm a bit ambivalent to this implicit communication of public key change.
    // I generally prefer an explicit cmd + event for such a significant part of the logic.
    // Including it here minimizes msg types and traffic, and seamlessly - apparently -
    // updates Actors on any public key change, which they can accumulate in order to
    // apply the change to local state.
    // But it inflicts on, and complicates the logic for validating a transfer..
    // Cost / benefit to be discussed..
}

impl TransferValidated {
    /// Get the transfer id
    pub fn id(&self) -> TransferId {
        self.signed_transfer.id()
    }

    /// Get the amount of this transfer
    pub fn amount(&self) -> Money {
        self.signed_transfer.amount()
    }

    /// Get the recipient of this transfer
    pub fn from(&self) -> PublicKey {
        self.signed_transfer.from()
    }

    /// Get the recipient of this transfer
    pub fn to(&self) -> PublicKey {
        self.signed_transfer.to()
    }
}

/// The debiting Replica event raised when
/// RegisterTransfer cmd has been successful.
#[derive(Clone, Eq, PartialEq, Serialize, Deserialize, Debug)]
pub struct TransferRegistered {
    /// The debit proof.
    pub debit_proof: DebitAgreementProof,
}

impl TransferRegistered {
    /// Get the transfer id
    pub fn id(&self) -> TransferId {
        self.debit_proof.id()
    }

    /// Get the amount of this transfer
    pub fn amount(&self) -> Money {
        self.debit_proof.amount()
    }

    /// Get the recipient of this transfer
    pub fn from(&self) -> PublicKey {
        self.debit_proof.from()
    }

    /// Get the recipient of this transfer
    pub fn to(&self) -> PublicKey {
        self.debit_proof.to()
    }
}

/// The crediting Replica event raised when
/// PropagateTransfer cmd has been successful.
#[derive(Clone, Eq, PartialEq, Serialize, Deserialize, Debug)]
pub struct TransferPropagated {
    /// The debiting Replicas' proof.
    pub debit_proof: DebitAgreementProof,
    /// The pub key of the debiting Replicas.
    pub debiting_replicas: PublicKey,
    /// The crediting Replica signature.
    pub crediting_replica_sig: SignatureShare,
}

impl TransferPropagated {
    /// Get the transfer id
    pub fn id(&self) -> TransferId {
        self.debit_proof.id()
    }

    /// Get the amount of this transfer
    pub fn amount(&self) -> Money {
        self.debit_proof.amount()
    }

    /// Get the recipient of this transfer
    pub fn from(&self) -> PublicKey {
        self.debit_proof.from()
    }

    /// Get the recipient of this transfer
    pub fn to(&self) -> PublicKey {
        self.debit_proof.to()
    }
}

/// Public Key Set for a group of transfer replicas.
pub type ReplicaPublicKeySet = PublicKeySet;
/// The Replica event raised when
/// we learn of a new group PK set.
#[derive(Clone, Hash, Eq, PartialEq, PartialOrd, Serialize, Deserialize, Debug)]
pub struct KnownGroupAdded {
    /// The PublicKeySet of the group.
    pub group: PublicKeySet,
}

// /// (Draft) An Actor cmd to roll back a failed transfer.
// #[derive(Clone, Hash, Eq, PartialEq, PartialOrd, Ord, Serialize, Deserialize, Debug)]
// pub struct CancelTransfer {
//     /// The transfer id.
//     pub transfer_id: TransferId,
//     /// Actor signature over the transfer id.
//     pub actor_signature: Signature,
// }

/// Notification of a Transfer sent to a recipient.
#[derive(Eq, PartialEq, Clone, Serialize, Deserialize, Debug)]
pub struct TransferNotification(pub DebitAgreementProof);
