use crate::{
    domain::book::MarketId,
    fuel_types::Bytes32,
    parse::HexDisplayFromStr,
};
use serde::{
    Deserialize,
    Serialize,
};
use serde_with::{
    DisplayFromStr,
    serde_as,
};
use std::{
    fmt::Display,
    str::FromStr,
};

#[derive(
    Debug,
    Clone,
    Copy,
    PartialEq,
    Eq,
    strum_macros::EnumCount,
    enum_iterator::Sequence,
    serde::Serialize,
    Hash,
)]
pub enum BarResolution {
    Second,
    OneMinute,
    TwoMinutes,
    ThreeMinutes,
    FiveMinutes,
    FifteenMinutes,
    ThirtyMinutes,
    OneHour,
    TwoHours,
    FourHours,
    SixHours,
    EightHours,
    TwelveHours,
    OneDay,
    ThreeDays,
    OneWeek,
    OneMonth,
    ThreeMonths,
}

impl Display for BarResolution {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            BarResolution::Second => write!(f, "1s"),
            BarResolution::OneMinute => write!(f, "1m"),
            BarResolution::TwoMinutes => write!(f, "2m"),
            BarResolution::ThreeMinutes => write!(f, "3m"),
            BarResolution::FiveMinutes => write!(f, "5m"),
            BarResolution::FifteenMinutes => write!(f, "15m"),
            BarResolution::ThirtyMinutes => write!(f, "30m"),
            BarResolution::OneHour => write!(f, "1h"),
            BarResolution::TwoHours => write!(f, "2h"),
            BarResolution::FourHours => write!(f, "4h"),
            BarResolution::SixHours => write!(f, "6h"),
            BarResolution::EightHours => write!(f, "8h"),
            BarResolution::TwelveHours => write!(f, "12h"),
            BarResolution::OneDay => write!(f, "1d"),
            BarResolution::ThreeDays => write!(f, "3d"),
            BarResolution::OneWeek => write!(f, "1w"),
            BarResolution::OneMonth => write!(f, "1M"),
            BarResolution::ThreeMonths => write!(f, "3M"),
        }
    }
}

impl BarResolution {
    pub fn as_millis(&self) -> u128 {
        match self {
            BarResolution::Second => 1_000,
            BarResolution::OneMinute => 60_000,
            BarResolution::TwoMinutes => 120_000,
            BarResolution::ThreeMinutes => 180_000,
            BarResolution::FiveMinutes => 300_000,
            BarResolution::FifteenMinutes => 900_000,
            BarResolution::ThirtyMinutes => 1_800_000,
            BarResolution::OneHour => 3_600_000,
            BarResolution::TwoHours => 7_200_000,
            BarResolution::FourHours => 14_400_000,
            BarResolution::SixHours => 21_600_000,
            BarResolution::EightHours => 28_800_000,
            BarResolution::TwelveHours => 43_200_000,
            BarResolution::OneDay => 86_400_000,
            BarResolution::ThreeDays => 259_200_000,
            BarResolution::OneWeek => 604_800_000,
            BarResolution::OneMonth => 2_629_746_000,
            BarResolution::ThreeMonths => 7_889_472_000,
        }
    }
}

impl FromStr for BarResolution {
    type Err = anyhow::Error;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        match s {
            "1s" => Ok(BarResolution::Second),
            "1m" => Ok(BarResolution::OneMinute),
            "2m" => Ok(BarResolution::TwoMinutes),
            "3m" => Ok(BarResolution::ThreeMinutes),
            "5m" => Ok(BarResolution::FiveMinutes),
            "15m" => Ok(BarResolution::FifteenMinutes),
            "30m" => Ok(BarResolution::ThirtyMinutes),
            "1h" => Ok(BarResolution::OneHour),
            "2h" => Ok(BarResolution::TwoHours),
            "4h" => Ok(BarResolution::FourHours),
            "6h" => Ok(BarResolution::SixHours),
            "8h" => Ok(BarResolution::EightHours),
            "12h" => Ok(BarResolution::TwelveHours),
            "1d" => Ok(BarResolution::OneDay),
            "3d" => Ok(BarResolution::ThreeDays),
            "1w" => Ok(BarResolution::OneWeek),
            "1M" => Ok(BarResolution::OneMonth),
            "3M" => Ok(BarResolution::ThreeMonths),
            s => Err(anyhow::anyhow!("Invalid bar resolution: {s}")),
        }
    }
}

#[serde_as]
#[derive(Serialize, Deserialize, Debug)]
pub struct GetBarsParams {
    #[serde_as(as = "HexDisplayFromStr")]
    pub market_id: MarketId,
    #[serde_as(as = "DisplayFromStr")]
    pub from: u128,
    #[serde_as(as = "DisplayFromStr")]
    pub to: u128,
    pub count_back: Option<usize>,
    #[serde_as(as = "DisplayFromStr")]
    pub resolution: BarResolution,
}

#[serde_as]
#[derive(serde::Serialize, serde::Deserialize, Copy, Clone, Debug, PartialEq, Eq)]
pub struct BarResponse {
    #[serde_as(as = "DisplayFromStr")]
    pub open: u64,
    #[serde_as(as = "DisplayFromStr")]
    pub close: u64,
    #[serde_as(as = "DisplayFromStr")]
    pub high: u64,
    #[serde_as(as = "DisplayFromStr")]
    pub low: u64,
    #[serde_as(as = "DisplayFromStr")]
    pub buy_volume: u128,
    #[serde_as(as = "DisplayFromStr")]
    pub sell_volume: u128,
    /// Segments start timestamp.
    pub timestamp: u128,
}

#[serde_as]
#[derive(serde::Serialize, serde::Deserialize, Debug)]
pub struct GetBarsResponse {
    pub action: String,
    pub bars: Vec<BarResponse>,
    #[serde_as(as = "HexDisplayFromStr")]
    pub market_id: Bytes32,
}

/// Subscription frequency for depth views.
#[derive(
    Clone, Copy, Debug, PartialEq, Eq, strum_macros::EnumCount, enum_iterator::Sequence,
)]
pub enum SubscriptionFrequency {
    HundredMillis,
    FiveHundredMillis,
    OneSecond,
    ThreeSecond,
}

impl Display for SubscriptionFrequency {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            SubscriptionFrequency::HundredMillis => write!(f, "100ms"),
            SubscriptionFrequency::FiveHundredMillis => write!(f, "500ms"),
            SubscriptionFrequency::OneSecond => write!(f, "1s"),
            SubscriptionFrequency::ThreeSecond => write!(f, "3s"),
        }
    }
}

impl FromStr for SubscriptionFrequency {
    type Err = anyhow::Error;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        match s {
            "100ms" => Ok(SubscriptionFrequency::HundredMillis),
            "500ms" => Ok(SubscriptionFrequency::FiveHundredMillis),
            "1s" => Ok(SubscriptionFrequency::OneSecond),
            "3s" => Ok(SubscriptionFrequency::ThreeSecond),
            s => Err(anyhow::anyhow!("Invalid subscription frequency: {s}")),
        }
    }
}
