use crate::{
    OrderId,
    domain::{
        book::{
            MarketIdAssets,
            Price,
            Quantity,
        },
        trade::TradeId,
    },
    fuel_types::{
        Address,
        AssetId,
        Bytes32,
        ContractId,
        TxId,
        TxPointer,
    },
    parse::HexDisplayFromStr,
    primitives::{
        OrderType,
        Side,
    },
};
use serde_with::serde_as;
use std::str::FromStr;

/// Alias for on-chain timestamp. Represented in seconds since UNIX EPOCH.
pub type OnChainTimeStamp = u128;
/// Alias for timestamp when event was seen by the indexer.
/// Represented in nanoseconds since UNIX EPOCH.
pub type SeenTimeStamp = u128;

#[serde_as]
#[derive(
    Clone, Copy, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize, Hash,
)]
pub enum Identity {
    Address(#[serde_as(as = "HexDisplayFromStr")] Address),
    ContractId(#[serde_as(as = "HexDisplayFromStr")] ContractId),
}

impl From<Identity> for Bytes32 {
    fn from(value: Identity) -> Self {
        match value {
            Identity::Address(address) => Bytes32::new(*address),
            Identity::ContractId(contract_id) => Bytes32::new(*contract_id),
        }
    }
}

impl From<Address> for Identity {
    fn from(value: Address) -> Self {
        Identity::Address(value)
    }
}

impl From<ContractId> for Identity {
    fn from(value: ContractId) -> Self {
        Identity::ContractId(value)
    }
}

impl FromStr for Identity {
    type Err = serde_json::Error;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        serde_json::from_str(s)
    }
}

impl Default for Identity {
    fn default() -> Self {
        Self::Address(Address::default())
    }
}

/// Event emitted when a new order is added to the order book.
#[derive(
    Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize, Clone, Copy, Hash,
)]
pub struct OrderCreatedEvent {
    pub order_id: OrderId,
    pub trader_id: Identity,
    pub order_side: Side,
    pub order_type: OrderType,
    pub quantity: Quantity,
    pub price: Price,
    pub timestamp: u128,
}

/// Event emitted when two orders are matched and executed.
#[derive(
    Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize, Clone, Copy, Hash,
)]
pub struct OrderMatchedEvent {
    pub trade_id: TradeId,
    pub quantity: Quantity,
    pub price: Price,
    pub timestamp: u128,
}

/// Event emitted when an order is cancelled.
#[derive(
    Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize, Clone, Copy, Hash,
)]
pub struct OrderCancelledEvent {
    pub order_id: Bytes32,
    pub timestamp: u128,
    pub reason: CancellationReason,
}

/// Fake event injected at the end of the contract call which created a market order.
#[derive(
    Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize, Clone, Copy, Hash,
)]
pub struct EndOfMarketOrderEvent {
    pub order_id: Bytes32,
    pub timestamp: u128,
}

#[derive(
    Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize, Clone, Copy, Hash,
)]
pub enum CancellationReason {
    ByOwner,
    OutOfGas,
    AmountTooSmall,
    Internal,
    MarketOrder,
    ExpiredOrder,
}

/// Event emitted when a trader receives their settled balance.
#[derive(
    Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize, Clone, Copy, Hash,
)]
pub struct WithdrawSettledTradeEvent {
    pub trader_id: Identity,
    pub base_amount: u64,
    pub quote_amount: u64,
}

/// Event emitted at initialization that provides information about the order book.
#[derive(
    Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize, Clone, Copy, Hash,
)]
pub struct OrderBookConfigEvent {
    pub base_asset: AssetId,
    pub quote_asset: AssetId,
    pub base_decimals: u64,
    pub quote_decimals: u64,
    pub min_order: u64,
    pub maker_fee: u64,
    pub taker_fee: u64,
    pub price_precision: u64,
    pub quantity_precision: u64,
    pub price_window: u64,
    pub dust: u64,
    pub allow_fractional_price: bool,
}

/// Event emitted when the base and quote symbols are set.
#[derive(Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize, Clone, Hash)]
pub struct OrderBookSymbolsEvent {
    pub base_symbol: String,
    pub quote_symbol: String,
}

#[derive(Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize, Clone, Hash)]
pub struct OrderBookWhitelistEvent {
    pub whitelist: Option<ContractId>,
}

#[derive(Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize, Clone, Hash)]
pub struct OrderBookBlacklistEvent {
    pub blacklist: Option<ContractId>,
}

/// Event emitted when a nonce change occurs for a contract.
#[derive(
    Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize, Clone, Copy, Hash,
)]
pub struct NonceChangeEvent {
    pub nonce: u64,
}

#[derive(
    Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize, Clone, Copy, Hash,
)]
pub struct BlockChainEvent<Event> {
    pub tx_pointer: TxPointer,
    pub receipt_index: u16,
    pub contract_id: ContractId,
    pub tx_id: TxId,
    pub event: Event,
}

#[derive(
    Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize, Clone, Copy, Hash,
)]
pub struct TradeAccountReferer {
    pub trade_account: ContractId,
    pub referer: Identity,
}

#[derive(
    Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize, Clone, Copy, Hash,
)]
pub struct BalanceIncrease {
    pub from: Option<ContractId>,
    pub amount: u64,
    pub asset_id: AssetId,
}

#[derive(
    Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize, Clone, Copy, Hash,
)]
pub struct BalanceDecrease {
    pub amount: u64,
    pub asset_id: AssetId,
    pub to: Option<ContractId>,
}

#[derive(
    Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize, Clone, Copy, Hash,
)]
pub struct TradeAccountRegistered {
    pub contract_id: ContractId,
    pub owner: Identity,
}

#[derive(
    Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize, Clone, Copy, Hash,
)]
pub struct OrderBookRegistered {
    pub contract_id: ContractId,
    pub market_id: MarketIdAssets,
}

#[derive(
    Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize, Clone, Copy, Hash,
)]
pub struct PauseEvent {
    pub caller: Identity,
}

#[derive(
    Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize, Clone, Copy, Hash,
)]
pub struct UnpauseEvent {
    pub caller: Identity,
}

#[derive(
    Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize, Clone, Copy, Hash,
)]
pub struct FeesClaimedEvent {
    pub base_fees: u64,
    pub quote_fees: u64,
}

#[derive(Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize, Clone, Hash)]
pub struct Session {
    pub session_id: Identity,
    pub expiry: u64,
    pub contract_ids: Vec<ContractId>,
}

#[derive(Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize, Clone, Hash)]
pub struct SessionCreated {
    pub account: Identity,
    pub session: Session,
}

#[derive(
    Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize, Clone, Copy, Hash,
)]
pub struct SessionRevoked {
    pub account: Identity,
    pub session_id: Identity,
}

#[derive(
    Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize, Clone, Copy, Hash,
)]
pub struct NewTx {
    pub tx_pointer: TxPointer,
    pub tx_id: TxId,
}

#[derive(
    Debug,
    PartialEq,
    Eq,
    serde::Serialize,
    serde::Deserialize,
    Clone,
    strum_macros::AsRefStr,
    Hash,
)]
#[strum(serialize_all = "snake_case")]
pub enum EventV1 {
    BalanceIncrease(BlockChainEvent<BalanceIncrease>),
    BalanceDecrease(BlockChainEvent<BalanceDecrease>),
    OrderCreated(BlockChainEvent<OrderCreatedEvent>),
    OrderCanceled(BlockChainEvent<OrderCancelledEvent>),
    EndOfMarketOrder(BlockChainEvent<EndOfMarketOrderEvent>),
    TradePerformed(BlockChainEvent<OrderMatchedEvent>),
    Withdrawal(BlockChainEvent<WithdrawSettledTradeEvent>),
    Configuration(BlockChainEvent<OrderBookConfigEvent>),
    ConfigurationSymbols(BlockChainEvent<OrderBookSymbolsEvent>),
    ConfigurationWhitelist(BlockChainEvent<OrderBookWhitelistEvent>),
    ConfigurationBlacklist(BlockChainEvent<OrderBookBlacklistEvent>),
    NonceChange(BlockChainEvent<NonceChangeEvent>),
    TradeAccountRegistration(BlockChainEvent<TradeAccountRegistered>),
    TradeAccountReferer(BlockChainEvent<TradeAccountReferer>),
    OrderBookRegistration(BlockChainEvent<OrderBookRegistered>),
    PauseEvent(BlockChainEvent<PauseEvent>),
    UnpauseEvent(BlockChainEvent<UnpauseEvent>),
    FeesClaimedEvent(BlockChainEvent<FeesClaimedEvent>),
    SessionCreated(BlockChainEvent<SessionCreated>),
    SessionRevoked(BlockChainEvent<SessionRevoked>),
}

#[derive(Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize, Clone, Hash)]
pub enum Event {
    EventV1(EventV1),
}
