use crate::dropshipping::*;
use crate::foundation::*;

domain_struct! {
    pub struct DropshipProfitCosts {
        supplier_goods: Money,
        supplier_shipping: Money,
        marketplace_fee: Money,
        payment_fee: Money,
        ad_spend: Money,
        return_reserve: Money,
        tax: Money,
        other_costs: Money,
    }
}

pub fn dropship_profit_costs_total(c: &DropshipProfitCosts) -> DomainResult<Money> {
    checked_sum(
        [
            c.supplier_goods,
            c.supplier_shipping,
            c.marketplace_fee,
            c.payment_fee,
            c.ad_spend,
            c.return_reserve,
            c.tax,
            c.other_costs,
        ],
        "dropship_profit_costs_total",
    )
}

pub fn revenue_after_discount(gross: Money, discount: Money) -> Money {
    nat_sub(gross, discount)
}

pub fn required_revenue_for_profit(total_costs: Money, min_profit: Money) -> DomainResult<Money> {
    checked_add(total_costs, min_profit, "required_revenue_for_profit")
}

pub fn required_gross_for_profit(
    total_costs: Money,
    min_profit: Money,
    discount: Money,
) -> DomainResult<Money> {
    checked_add(
        required_revenue_for_profit(total_costs, min_profit)?,
        discount,
        "required_gross_for_profit",
    )
}

#[derive(Clone, Debug, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct GuaranteedDropshipProfitQuote {
    pub(crate) revenue: Money,
    pub(crate) costs: DropshipProfitCosts,
    pub(crate) min_profit: Money,
    pub(crate) profit: Money,
}

impl GuaranteedDropshipProfitQuote {
    pub fn try_new(
        revenue: Money,
        costs: DropshipProfitCosts,
        min_profit: Money,
        profit: Money,
    ) -> DomainResult<Self> {
        let costs_total = dropship_profit_costs_total(&costs)?;
        if profit != profit_amount(revenue, costs_total) {
            return Err(ValidationError::Invariant("profit is incorrect"));
        }
        if checked_add(costs_total, min_profit, "guaranteed quote")? > revenue {
            return Err(ValidationError::Invariant(
                "revenue does not cover costs plus minimum profit",
            ));
        }
        Ok(Self {
            revenue,
            costs,
            min_profit,
            profit,
        })
    }
}

#[derive(Clone, Debug, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct DropshipCostUpperBounds {
    pub(crate) actual: DropshipProfitCosts,
    pub(crate) upper: DropshipProfitCosts,
}

impl DropshipCostUpperBounds {
    pub fn try_new(actual: DropshipProfitCosts, upper: DropshipProfitCosts) -> DomainResult<Self> {
        let pairs = [
            (actual.supplier_goods, upper.supplier_goods),
            (actual.supplier_shipping, upper.supplier_shipping),
            (actual.marketplace_fee, upper.marketplace_fee),
            (actual.payment_fee, upper.payment_fee),
            (actual.ad_spend, upper.ad_spend),
            (actual.return_reserve, upper.return_reserve),
            (actual.tax, upper.tax),
            (actual.other_costs, upper.other_costs),
        ];
        if pairs.iter().any(|(a, u)| a > u) {
            return Err(ValidationError::Invariant(
                "actual dropship cost exceeds upper bound",
            ));
        }
        Ok(Self { actual, upper })
    }
}

pub fn ad_spend_safe_for_min_profit(
    revenue: Money,
    non_ad_costs: Money,
    ad_spend: Money,
    min_profit: Money,
) -> bool {
    non_ad_costs
        .checked_add(ad_spend)
        .and_then(|x| x.checked_add(min_profit))
        .is_some_and(|required| required <= revenue)
}

pub fn profit_after_ad_spend(
    revenue: Money,
    non_ad_costs: Money,
    ad_spend: Money,
) -> DomainResult<Money> {
    Ok(profit_amount(
        revenue,
        checked_add(non_ad_costs, ad_spend, "profit_after_ad_spend")?,
    ))
}

pub fn profit_loss_int(revenue: Money, total_costs: Money) -> DomainResult<i128> {
    let revenue = i128::try_from(revenue)
        .map_err(|_| ValidationError::Overflow("profit_loss_int revenue"))?;
    let total_costs = i128::try_from(total_costs)
        .map_err(|_| ValidationError::Overflow("profit_loss_int total costs"))?;
    revenue
        .checked_sub(total_costs)
        .ok_or(ValidationError::Overflow("profit_loss_int subtraction"))
}

pub(crate) fn _dropshipping_anchor(_: Option<DropshipPOStatus>) {}

impl_getters!(GuaranteedDropshipProfitQuote {
    revenue: Money,
    costs: DropshipProfitCosts,
    min_profit: Money,
    profit: Money,
});

impl_getters!(DropshipCostUpperBounds {
    actual: DropshipProfitCosts,
    upper: DropshipProfitCosts,
});
