use chrono::NaiveDate;
use serde::{Deserialize, Serialize};

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Instrument {
    pub symbol: String,
    pub name: String,
    pub board: String,
    pub round_lot: u32,
    #[serde(default, with = "optional_date_format")]
    pub listed_at: Option<NaiveDate>,
    #[serde(default, with = "optional_date_format")]
    pub delisted_at: Option<NaiveDate>,
    #[serde(default = "default_status")]
    pub status: String,
}

impl Instrument {
    pub fn effective_round_lot(&self) -> u32 {
        self.round_lot.max(1)
    }

    pub fn minimum_order_quantity(&self) -> u32 {
        match self.board.trim().to_ascii_uppercase().as_str() {
            "KSH" => 200,
            "BJS" | "BJ" | "BJSE" => 100,
            _ => self.effective_round_lot(),
        }
    }

    pub fn order_step_size(&self) -> u32 {
        match self.board.trim().to_ascii_uppercase().as_str() {
            "KSH" | "BJS" | "BJ" | "BJSE" => 1,
            _ => self.effective_round_lot(),
        }
    }

    pub fn is_delisted_before(&self, date: NaiveDate) -> bool {
        self.delisted_at
            .is_some_and(|delisted_at| delisted_at < date)
    }
}

fn default_status() -> String {
    "active".to_string()
}

mod optional_date_format {
    use chrono::NaiveDate;
    use serde::{self, Deserialize, Deserializer, Serializer};

    const FORMAT: &str = "%Y-%m-%d";

    pub fn serialize<S>(value: &Option<NaiveDate>, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: Serializer,
    {
        match value {
            Some(date) => serializer.serialize_some(&date.format(FORMAT).to_string()),
            None => serializer.serialize_none(),
        }
    }

    pub fn deserialize<'de, D>(deserializer: D) -> Result<Option<NaiveDate>, D::Error>
    where
        D: Deserializer<'de>,
    {
        let value = Option::<String>::deserialize(deserializer)?;
        match value.as_deref().map(str::trim).filter(|v| !v.is_empty()) {
            Some(text) => NaiveDate::parse_from_str(text, FORMAT)
                .map(Some)
                .map_err(serde::de::Error::custom),
            None => Ok(None),
        }
    }
}
