"""
Strategy Domain Service — KPI Cascade Engine.
"""

from __future__ import annotations

from dataclasses import dataclass
from typing import Optional


@dataclass
class KPIScore:
    """نتیجه محاسبه امتیاز KPI."""

    kpi_id: object  # UUID
    actual_value: float
    target_value: float
    achievement_rate: float  # 0-100%
    rag_status: str  # red / yellow / green / unknown


@dataclass
class TrendPoint:
    """یک نقطه از روند KPI."""

    period: str
    value: float


@dataclass
class TrendAnalysis:
    """تحلیل روند KPI."""

    kpi_id: object
    points: list[TrendPoint]
    slope: float
    direction: str  # improving / declining / stable
    forecast_next: Optional[float] = None


class KPICascadeEngine:
    """
    موتور آبشاری KPI:
    - انتشار هدف به سطوح پایین‌تر (cascade)
    - تجمیع مقادیر واقعی از سطوح پایین‌تر (rollup)
    - محاسبه امتیاز و RAG
    - تحلیل روند
    """

    @staticmethod
    def calculate_score(
        actual_value: float,
        target_value: float,
        direction: str = "higher_is_better",
        threshold_red: Optional[float] = None,
        threshold_yellow: Optional[float] = None,
        threshold_green: Optional[float] = None,
    ) -> KPIScore:
        """محاسبه امتیاز و RAG status."""
        if target_value == 0:
            achievement_rate = 100.0 if actual_value == 0 else 0.0
        else:
            if direction == "lower_is_better":
                # Lower actual is better
                if actual_value == 0:
                    achievement_rate = 100.0
                else:
                    achievement_rate = min((target_value / actual_value) * 100, 100.0)
            else:
                achievement_rate = min((actual_value / target_value) * 100, 100.0)

        # RAG status
        rag_status = "unknown"
        if all(v is not None for v in [threshold_red, threshold_yellow, threshold_green]):
            if direction == "higher_is_better":
                if actual_value >= threshold_green:
                    rag_status = "green"
                elif actual_value >= threshold_yellow:
                    rag_status = "yellow"
                else:
                    rag_status = "red"
            elif direction == "lower_is_better":
                if actual_value <= threshold_green:
                    rag_status = "green"
                elif actual_value <= threshold_yellow:
                    rag_status = "yellow"
                else:
                    rag_status = "red"
            else:
                # target: within threshold range
                diff_green = abs(actual_value - threshold_green)
                diff_yellow = abs(actual_value - threshold_yellow)
                if diff_green <= diff_yellow:
                    rag_status = "green"
                else:
                    rag_status = "yellow" if diff_yellow < abs(actual_value - threshold_red) else "red"

        return KPIScore(
            kpi_id=None,
            actual_value=actual_value,
            target_value=target_value,
            achievement_rate=round(achievement_rate, 2),
            rag_status=rag_status,
        )

    @staticmethod
    def cascade_targets(
        parent_target: float,
        child_weights: list[float],
    ) -> list[float]:
        """
        انتشار هدف از سطح بالا به پایین بر اساس وزن‌ها.
        مجموع child_weights باید > 0 باشد.
        """
        total_weight = sum(child_weights)
        if total_weight == 0:
            return [0.0] * len(child_weights)
        return [round(parent_target * (w / total_weight), 2) for w in child_weights]

    @staticmethod
    def aggregate_actuals(
        child_actuals: list[float],
        child_weights: list[float],
    ) -> float:
        """
        تجمیع مقادیر واقعی از سطوح پایین‌تر (rollup).
        میانگین وزنی.
        """
        if not child_actuals or not child_weights:
            return 0.0
        total_weight = sum(child_weights)
        if total_weight == 0:
            return 0.0
        weighted_sum = sum(a * w for a, w in zip(child_actuals, child_weights))
        return round(weighted_sum / total_weight, 2)

    @staticmethod
    def trend_analysis(
        points: list[TrendPoint],
    ) -> TrendAnalysis:
        """
        تحلیل روند KPI بر اساس نقاط داده.
        از رگرسیون خطی ساده برای محاسبه slope استفاده می‌شود.
        """
        n = len(points)
        if n < 2:
            return TrendAnalysis(
                kpi_id=None,
                points=points,
                slope=0.0,
                direction="stable",
                forecast_next=points[0].value if points else None,
            )

        # Simple linear regression: y = mx + b
        x_vals = list(range(n))
        y_vals = [p.value for p in points]

        x_mean = sum(x_vals) / n
        y_mean = sum(y_vals) / n

        numerator = sum((x - x_mean) * (y - y_mean) for x, y in zip(x_vals, y_vals))
        denominator = sum((x - x_mean) ** 2 for x in x_vals)

        if denominator == 0:
            slope = 0.0
        else:
            slope = numerator / denominator

        # Direction
        threshold = 0.01 * abs(y_mean) if y_mean != 0 else 0.01
        if slope > threshold:
            direction = "improving"
        elif slope < -threshold:
            direction = "declining"
        else:
            direction = "stable"

        # Forecast next point
        b = y_mean - slope * x_mean
        forecast_next = round(slope * n + b, 2)

        return TrendAnalysis(
            kpi_id=None,
            points=points,
            slope=round(slope, 4),
            direction=direction,
            forecast_next=forecast_next,
        )
