"""Risk score calculator."""

from collections import Counter

from apiposture.core.models.scan_result import ScanResult

from apiposture_pro.features.risk_scoring.models import RiskScore


class RiskScoreCalculator:
    """Calculates risk scores for scan results."""

    # Severity weights for findings score
    SEVERITY_WEIGHTS = {
        "critical": 10,
        "high": 7,
        "medium": 4,
        "low": 2,
        "info": 1,
    }

    # Maximum values for normalization
    MAX_FINDINGS_SCORE = 50
    MAX_EXPOSURE_SCORE = 30
    MAX_SURFACE_AREA_SCORE = 20

    def calculate(self, result: ScanResult) -> RiskScore:
        """
        Calculate comprehensive risk score.

        Args:
            result: Scan result to score

        Returns:
            RiskScore with detailed breakdown
        """
        # Count findings by severity
        severity_counts = Counter(
            f.severity.value if hasattr(f.severity, "value") else str(f.severity).lower()
            for f in result.findings
        )

        critical_count = severity_counts.get("critical", 0)
        high_count = severity_counts.get("high", 0)

        # Calculate component scores
        findings_score = self._calculate_findings_score(severity_counts)
        exposure_score = self._calculate_exposure_score(result)
        surface_area_score = self._calculate_surface_area_score(result)

        # Total score
        total_score = findings_score + exposure_score + surface_area_score

        # Determine risk level
        risk_level = RiskScore.get_risk_level(total_score)

        # Count public endpoints
        public_count = sum(
            1
            for ep in result.endpoints
            if hasattr(ep, "classification")
            and (
                ep.classification.value if hasattr(ep.classification, "value") else str(ep.classification)
            ).lower()
            == "public"
        )

        return RiskScore(
            total_score=total_score,
            risk_level=risk_level,
            findings_score=findings_score,
            exposure_score=exposure_score,
            surface_area_score=surface_area_score,
            total_findings=len(result.findings),
            critical_findings=critical_count,
            high_findings=high_count,
            total_endpoints=len(result.endpoints),
            public_endpoints=public_count,
        )

    def _calculate_findings_score(self, severity_counts: Counter) -> int:
        """
        Calculate findings score based on severity.

        Weighted sum of findings by severity, normalized to 0-50.

        Args:
            severity_counts: Counter of findings by severity

        Returns:
            Findings score (0-50)
        """
        # Calculate weighted sum
        weighted_sum = sum(
            count * self.SEVERITY_WEIGHTS.get(severity, 0)
            for severity, count in severity_counts.items()
        )

        # Normalize to 0-50
        # Use logarithmic scale to prevent saturation
        # Score grows quickly for first few findings, then slows down
        if weighted_sum == 0:
            return 0

        # log10(1) = 0, log10(10) = 1, log10(100) = 2
        # Multiply by 20 to scale up, allowing room for growth
        # 10 critical findings ~= 40 points, 20 critical ~= 46 points
        import math

        score = min(self.MAX_FINDINGS_SCORE, int(math.log10(weighted_sum + 1) * 20))

        return score

    def _calculate_exposure_score(self, result: ScanResult) -> int:
        """
        Calculate exposure score based on public endpoints ratio.

        Higher score means more endpoints are publicly accessible.

        Args:
            result: Scan result

        Returns:
            Exposure score (0-30)
        """
        if not result.endpoints:
            return 0

        # Count public endpoints
        public_count = sum(
            1
            for ep in result.endpoints
            if hasattr(ep, "classification")
            and (
                ep.classification.value if hasattr(ep.classification, "value") else str(ep.classification)
            ).lower()
            == "public"
        )

        # Calculate ratio
        public_ratio = public_count / len(result.endpoints)

        # Scale to 0-30
        # Higher public ratio = higher risk
        score = int(public_ratio * self.MAX_EXPOSURE_SCORE)

        return score

    def _calculate_surface_area_score(self, result: ScanResult) -> int:
        """
        Calculate surface area score based on total endpoints.

        More endpoints = larger attack surface.

        Args:
            result: Scan result

        Returns:
            Surface area score (0-20)
        """
        endpoint_count = len(result.endpoints)

        if endpoint_count == 0:
            return 0

        # Logarithmic scale to prevent saturation
        # 1 endpoint = ~5 points, 10 endpoints = ~10 points, 100 endpoints = ~15 points
        import math

        score = min(
            self.MAX_SURFACE_AREA_SCORE, int(math.log10(endpoint_count + 1) * 10)
        )

        return score
