"""
Risk scoring engine for dependency confusion candidates.

Scores are 0-10. Higher = more likely to be exploitable.
"""

from __future__ import annotations

import re
from dataclasses import dataclass, field
from pathlib import Path
from typing import List

from ..scanner.confusion_checker import VulnerableDependency


# Prefixes that strongly suggest internal/private packages
_INTERNAL_PREFIXES = (
    "internal-", "private-", "corp-", "company-", "local-",
    "int-", "priv-", "my-", "test-", "dev-",
)

# CI/CD indicator files
_CI_FILES = (
    ".github/workflows",
    ".gitlab-ci.yml",
    "Jenkinsfile",
    "azure-pipelines.yml",
    ".circleci",
    ".travis.yml",
    "bitbucket-pipelines.yml",
)


@dataclass
class RiskResult:
    """Risk assessment for a single vulnerable dependency."""
    package: str
    ecosystem: str
    score: float
    reasons: List[str] = field(default_factory=list)

    def to_dict(self) -> dict:
        return {
            "package": self.package,
            "ecosystem": self.ecosystem,
            "risk_score": round(self.score, 1),
            "reasons": self.reasons,
        }


def score_vulnerability(
    vuln: VulnerableDependency,
    project_path: Path | None = None,
) -> RiskResult:
    """
    Compute a risk score for a vulnerable dependency.

    Args:
        vuln: The detected vulnerable dependency.
        project_path: Optional path to the project root for CI detection.

    Returns:
        RiskResult with score and reasons.
    """
    score = 0.0
    reasons: List[str] = []

    # Base: not in public registry
    score += 5.0
    reasons.append("Not found in public registry")

    name = vuln.dependency.name.lower()

    # Internal naming pattern
    if any(name.startswith(p) for p in _INTERNAL_PREFIXES):
        score += 2.0
        reasons.append("Internal naming pattern detected")

    # Wildcard / unpinned version
    if vuln.dependency.version in ("*", "", "latest", "^", "~"):
        score += 1.0
        reasons.append("Unpinned / wildcard version")

    # CI/CD usage detected
    if project_path and _has_ci(project_path):
        score += 1.5
        reasons.append("CI/CD pipeline detected in project")

    # Short name (more likely to be squatted)
    if len(name) <= 6:
        score += 0.5
        reasons.append("Short package name (higher squatting risk)")

    return RiskResult(
        package=vuln.dependency.name,
        ecosystem=vuln.dependency.ecosystem,
        score=min(score, 10.0),
        reasons=reasons,
    )


def score_all(
    vulnerabilities: List[VulnerableDependency],
    project_path: Path | None = None,
) -> List[RiskResult]:
    """Score all vulnerabilities and return sorted results (highest first)."""
    results = [score_vulnerability(v, project_path) for v in vulnerabilities]
    results.sort(key=lambda r: r.score, reverse=True)
    return results


def _has_ci(project_path: Path) -> bool:
    """Return True if any CI/CD indicator exists in the project."""
    for indicator in _CI_FILES:
        if (project_path / indicator).exists():
            return True
    return False
