"""
PPM Domain — Financial Evaluator Service

سرویس دامنه محاسبات مالی (NPV, IRR, Payback, ROI, BCR).
"""

from decimal import Decimal, InvalidOperation
from typing import List

from ..entities.business_case import CashFlowProjection


class FinancialEvaluator:
    """محاسبه‌گر مالی — stateless domain service."""

    @staticmethod
    def calculate_npv(
        cash_flows: List[CashFlowProjection],
        discount_rate: Decimal,
    ) -> Decimal:
        """
        محاسبه ارزش فعلی خالص (NPV).

        NPV = Σ (net_cash_flow_t / (1 + r)^t)
        """
        if not cash_flows:
            return Decimal("0")

        rate = float(discount_rate)
        npv = 0.0
        for cf in sorted(cash_flows, key=lambda c: c.year):
            ncf = float(cf.net_cash_flow)
            npv += ncf / ((1 + rate) ** cf.year)
        return Decimal(str(round(npv, 2)))

    @staticmethod
    def calculate_irr(
        cash_flows: List[CashFlowProjection],
        max_iterations: int = 1000,
        tolerance: float = 1e-6,
    ) -> Decimal:
        """
        محاسبه نرخ بازده داخلی (IRR) با روش Newton-Raphson.

        IRR = نرخ تنزیلی که NPV = 0 می‌کند.
        """
        if not cash_flows:
            return Decimal("0")

        flows = [float(cf.net_cash_flow) for cf in sorted(cash_flows, key=lambda c: c.year)]
        years = [cf.year for cf in sorted(cash_flows, key=lambda c: c.year)]

        # Initial guess
        guess = 0.1

        for _ in range(max_iterations):
            npv = sum(f / ((1 + guess) ** y) for f, y in zip(flows, years))
            d_npv = sum(-y * f / ((1 + guess) ** (y + 1)) for f, y in zip(flows, years))

            if abs(d_npv) < 1e-12:
                break

            new_guess = guess - npv / d_npv

            if abs(new_guess - guess) < tolerance:
                return Decimal(str(round(new_guess * 100, 2)))  # as percentage

            guess = new_guess

        return Decimal(str(round(guess * 100, 2)))

    @staticmethod
    def calculate_payback_period(
        cash_flows: List[CashFlowProjection],
    ) -> Decimal:
        """
        محاسبه دوره بازگشت سرمایه (Payback Period).

        سال‌هایی که جمع تجمعی جریان نقدی خالص مثبت می‌شود.
        """
        if not cash_flows:
            return Decimal("0")

        sorted_cfs = sorted(cash_flows, key=lambda c: c.year)
        cumulative = Decimal("0")
        for cf in sorted_cfs:
            cumulative += cf.net_cash_flow
            if cumulative >= 0:
                # Interpolation
                prev_cum = cumulative - cf.net_cash_flow
                if cf.net_cash_flow != 0:
                    fraction = float(-prev_cum) / float(cf.net_cash_flow)
                else:
                    fraction = 0
                return Decimal(str(round(cf.year - 1 + fraction, 2)))

        return Decimal(str(len(sorted_cfs)))  # Never pays back within projection

    @staticmethod
    def calculate_roi(
        total_investment: Decimal,
        net_benefit: Decimal,
    ) -> Decimal:
        """
        محاسبه نرخ بازگشت سرمایه (ROI).

        ROI = (net_benefit / total_investment) × 100
        """
        if total_investment == 0:
            return Decimal("0")
        try:
            return Decimal(str(round(float(net_benefit / total_investment) * 100, 2)))
        except (InvalidOperation, ZeroDivisionError):
            return Decimal("0")

    @staticmethod
    def calculate_benefit_cost_ratio(
        cash_flows: List[CashFlowProjection],
        discount_rate: Decimal,
    ) -> Decimal:
        """
        محاسبه نسبت منفعت به هزینه (BCR).

        BCR = PV(benefits) / PV(costs)
        """
        if not cash_flows:
            return Decimal("0")

        rate = float(discount_rate)
        pv_benefits = 0.0
        pv_costs = 0.0

        for cf in sorted(cash_flows, key=lambda c: c.year):
            discount_factor = (1 + rate) ** cf.year
            benefits = float(cf.revenue + cf.savings)
            costs = float(cf.investment + cf.operational_cost)
            pv_benefits += benefits / discount_factor
            pv_costs += costs / discount_factor

        if pv_costs == 0:
            return Decimal("0")

        return Decimal(str(round(pv_benefits / pv_costs, 2)))
