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

use crate::{Money, minivec::MiniVec};

#[derive(Debug, Clone, Copy, Serialize, PartialEq, Eq)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
pub enum TaxAppliedBy {
    KwhConsumed,
}

// NOTE: Just increate N when we need it
type TaxesInner = MiniVec<&'static Tax, 2>;

#[derive(Debug, Clone, Copy, Serialize, PartialEq, Eq)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
pub struct Taxes(TaxesInner);

pub(crate) struct TaxesBuilder {
    t: TaxesInner,
}

impl TaxesBuilder {
    pub(crate) const fn new() -> Self {
        Self {
            t: TaxesInner::new(),
        }
    }

    pub(crate) const fn add(mut self, t: &'static Tax) -> Self {
        self.t.push(t);
        self
    }

    pub(crate) const fn build(self) -> Taxes {
        Taxes(self.t)
    }
}

impl Taxes {
    /// Get taxes that are current for the given date
    pub fn for_date(&self, date: NaiveDate) -> Vec<Tax> {
        self.0
            .iter()
            .filter(|tax| tax.valid_for(date))
            .copied()
            .collect()
    }

    /// Tax total for the given date
    pub fn kwh_total(&self, date: NaiveDate) -> Money {
        self.for_date(date)
            .into_iter()
            .filter(|tax| tax.is_kwh_based())
            .map(|tax| tax.amount())
            .sum()
    }

    pub fn with_current_only(mut self) -> Taxes {
        let today = Local::now().date_naive();
        self.0 = self.0.iter().filter(|tax| tax.valid_for(today)).collect();
        self
    }
}

#[derive(Debug, Clone, Copy, Serialize, PartialEq, Eq)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
pub struct Tax {
    description: &'static str,
    amount: Money,
    applied_by: TaxAppliedBy,
    from_date: NaiveDate,
    /// Last date when this tax applies
    to_date: Option<NaiveDate>,
}

impl Tax {
    pub(crate) const fn new(
        description: &'static str,
        amount: Money,
        applied_by: TaxAppliedBy,
        from_date: NaiveDate,
        to_date: Option<NaiveDate>,
    ) -> Self {
        Self {
            description,
            amount,
            applied_by,
            from_date,
            to_date,
        }
    }

    pub fn description(&self) -> &'static str {
        self.description
    }

    pub fn amount(&self) -> Money {
        self.amount
    }

    pub(crate) fn valid_for(&self, today: NaiveDate) -> bool {
        today >= self.from_date && self.to_date.is_none_or(|to_date| today <= to_date)
    }

    pub(crate) fn is_kwh_based(&self) -> bool {
        matches!(self.applied_by, TaxAppliedBy::KwhConsumed)
    }

    /// Reduce the tax by the given amount
    pub(crate) const fn reduce_by(mut self, reduce_amount: Money) -> Self {
        self.amount = self.amount.reduce_by(reduce_amount);
        self
    }
}
