use core::fmt::Display;
use core::hash::Hash;

use time::Date;
use time::OffsetDateTime;
use time_tz::PrimitiveDateTimeExt;

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

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

    pub account: Account,

    pub amount: Amount,

    pub meta: MetadataMap,
}

impl Balance {
    pub fn new(date: Date, account: impl Into<Account>, amount: Amount) -> Self {
        let (account, meta) = (account.into(), MetadataMap::default());
        Self {
            date,
            account,
            amount,
            meta,
        }
    }
}

impl Balance {
    #[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_account(&mut self, account: impl Into<Account>) -> &mut Self {
        self.account = account.into();
        self
    }

    #[inline]
    pub fn set_amount(&mut self, amount: Amount) -> &mut Self {
        self.amount = amount;
        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 Balance {
    #[inline]
    #[must_use]
    pub fn timestamp(&self) -> Option<OffsetDateTime> {
        // Balance statements occur first thing in the day
        let local_timestamp = self.date.midnight();
        let timezone = time_tz::system::get_timezone().ok()?;

        local_timestamp.assume_timezone(timezone).take_first()
    }
}

impl Display for Balance {
    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
        let Self {
            date,
            account,
            amount,
            meta,
        } = self;
        write!(f, "{date} balance {account} {amount}")?;

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

        Ok(())
    }
}