use core::fmt::Display;

use time::Date;

use crate::Amount;
use crate::Commodity;
use crate::MetadataKey;
use crate::MetadataMap;
use crate::MetadataValue;

#[derive(Clone, Debug, Eq, Hash, PartialEq)]
pub struct Price {
    pub date: Date,

    pub quote: Commodity,

    pub price: Amount,

    pub meta: MetadataMap,
}

impl Price {
    #[must_use]
    pub fn new(date: Date, quote: Commodity, price: Amount) -> Self {
        let meta = MetadataMap::default();
        Self {
            date,
            quote,
            price,
            meta,
        }
    }
}

impl Price {
    #[inline]
    pub fn add_meta(
        &mut self,
        key: impl Into<MetadataKey>,
        value: impl Into<MetadataValue>,
    ) -> &mut Self {
        self.meta.insert(key.into(), value.into());
        self
    }

    #[inline]
    pub fn clear_meta(&mut self) -> &mut Self {
        self.meta.clear();
        self
    }

    #[inline]
    pub fn set_quote(&mut self, quote: impl Into<Commodity>) -> &mut Self {
        self.quote = quote.into();
        self
    }

    #[inline]
    pub fn set_price(&mut self, price: Amount) -> &mut Self {
        self.price = price;
        self
    }

    #[inline]
    pub fn set_date(&mut self, date: Date) -> &mut Self {
        self.date = date;
        self
    }

    #[inline]
    pub fn set_meta(&mut self, meta: impl Into<MetadataMap>) -> &mut Self {
        self.meta = meta.into();
        self
    }
}

impl Display for Price {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        let Self {
            date,
            quote,
            price,
            meta,
        } = self;
        write!(f, "{date} price {quote} {price}")?;

        for (key, value) in meta {
            write!(f, "\n  {key}: {value}")?;
        }

        Ok(())
    }
}