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 TaxReductionAppliedBy {
    KwhFeedIn,
}

// NOTE: Just increase N when we need it
type TaxReductionsInner = MiniVec<&'static TaxReduction, 2>;

pub(crate) struct TaxReductionsBuilder {
    t: TaxReductionsInner,
}

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

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

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

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

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

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

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

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

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

    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, TaxReductionAppliedBy::KwhFeedIn)
    }
}
