"""
PPM Domain Service — Scoring Engine

موتور امتیازدهی و اولویت‌بندی پروژه‌ها.
"""

from dataclasses import dataclass
from decimal import Decimal, ROUND_HALF_UP
from typing import List, Optional, Dict
from uuid import UUID

from ..entities.scoring import ScoringCriterion, ProjectScore


@dataclass
class ProjectRanking:
    """نتیجه رتبه‌بندی یک پروژه."""
    project_id: UUID
    weighted_score: Decimal
    rank: int = 0
    scores_by_category: dict = None

    def __post_init__(self):
        if self.scores_by_category is None:
            self.scores_by_category = {}


class ScoringEngine:
    """
    موتور امتیازدهی — سرویس دامنه خالص (بدون وابستگی خارجی).

    مسئولیت‌ها:
    - محاسبه امتیاز وزن‌دار پروژه
    - رتبه‌بندی پروژه‌ها در یک پرتفولیو
    - مقایسه سناریوها (دو مدل مختلف)
    """

    @staticmethod
    def calculate_weighted_score(
        criteria: List[ScoringCriterion],
        scores: List[ProjectScore],
    ) -> Decimal:
        """
        محاسبه امتیاز وزن‌دار یک پروژه.

        فرمول: Σ(normalized_score × weight) / Σ(weights)
        normalized_score = (score - min) / (max - min)
        """
        if not criteria or not scores:
            return Decimal("0")

        # ساخت map معیار → امتیاز
        score_map: Dict[UUID, Decimal] = {
            s.criterion_id: s.score for s in scores
        }
        criterion_map: Dict[UUID, ScoringCriterion] = {
            c.id: c for c in criteria
        }

        total_weight = Decimal("0")
        weighted_sum = Decimal("0")

        for criterion in criteria:
            raw_score = score_map.get(criterion.id)
            if raw_score is None:
                continue

            scale_range = Decimal(criterion.scale_max - criterion.scale_min)
            if scale_range == 0:
                normalized = Decimal("1")
            else:
                normalized = (raw_score - Decimal(criterion.scale_min)) / scale_range

            weighted_sum += normalized * criterion.weight
            total_weight += criterion.weight

        if total_weight == 0:
            return Decimal("0")

        result = (weighted_sum / total_weight) * Decimal("100")
        return result.quantize(Decimal("0.01"), rounding=ROUND_HALF_UP)

    @staticmethod
    def calculate_category_scores(
        criteria: List[ScoringCriterion],
        scores: List[ProjectScore],
    ) -> Dict[str, Decimal]:
        """محاسبه امتیاز وزن‌دار per category."""
        category_map: Dict[str, List] = {}
        score_map = {s.criterion_id: s.score for s in scores}

        for c in criteria:
            cat = c.category
            if cat not in category_map:
                category_map[cat] = []
            category_map[cat].append(c)

        result = {}
        for cat, cat_criteria in category_map.items():
            total_w = Decimal("0")
            weighted = Decimal("0")
            for c in cat_criteria:
                raw = score_map.get(c.id)
                if raw is None:
                    continue
                scale_range = Decimal(c.scale_max - c.scale_min)
                if scale_range == 0:
                    norm = Decimal("1")
                else:
                    norm = (raw - Decimal(c.scale_min)) / scale_range
                weighted += norm * c.weight
                total_w += c.weight
            if total_w > 0:
                result[cat] = ((weighted / total_w) * Decimal("100")).quantize(
                    Decimal("0.01"), rounding=ROUND_HALF_UP
                )
            else:
                result[cat] = Decimal("0")
        return result

    def rank_projects(
        self,
        criteria: List[ScoringCriterion],
        all_scores: Dict[UUID, List[ProjectScore]],
    ) -> List[ProjectRanking]:
        """
        رتبه‌بندی پروژه‌ها بر اساس امتیاز وزن‌دار.

        Args:
            criteria: لیست معیارهای یک مدل امتیازدهی
            all_scores: dict(project_id → list of ProjectScore)

        Returns:
            لیست ProjectRanking مرتب‌شده (بالاترین اول)
        """
        rankings = []
        for project_id, scores in all_scores.items():
            ws = self.calculate_weighted_score(criteria, scores)
            cat_scores = self.calculate_category_scores(criteria, scores)
            rankings.append(ProjectRanking(
                project_id=project_id,
                weighted_score=ws,
                scores_by_category=cat_scores,
            ))

        # مرتب‌سازی نزولی
        rankings.sort(key=lambda r: r.weighted_score, reverse=True)

        # تخصیص رتبه
        for idx, ranking in enumerate(rankings, start=1):
            ranking.rank = idx

        return rankings

    def compare_scenarios(
        self,
        criteria_a: List[ScoringCriterion],
        criteria_b: List[ScoringCriterion],
        all_scores_a: Dict[UUID, List[ProjectScore]],
        all_scores_b: Dict[UUID, List[ProjectScore]],
    ) -> Dict[str, List[ProjectRanking]]:
        """
        مقایسه دو سناریوی امتیازدهی.

        Returns:
            dict با کلیدهای 'scenario_a' و 'scenario_b'
        """
        return {
            "scenario_a": self.rank_projects(criteria_a, all_scores_a),
            "scenario_b": self.rank_projects(criteria_b, all_scores_b),
        }
