"""Trust Score calculator for MCP Security Audit.

Computes a composite score 0-100 for a scan target based on
finding severities, with capped deductions per severity level
and bonus points for clean/read-only scans.

Designed for CI/CD gates via --min-score flag.
"""

from __future__ import annotations

from dataclasses import dataclass
from typing import Any

from .models import Finding, ScanResult, ScanSummary, Severity


# ---------------------------------------------------------------------------
# Scoring constants
# ---------------------------------------------------------------------------

# Deduction per finding by severity (points subtracted)
SEVERITY_DEDUCTIONS: dict[Severity, int] = {
    Severity.CRITICAL: 25,
    Severity.HIGH: 15,
    Severity.MEDIUM: 8,
    Severity.LOW: 3,
    Severity.INFO: 1,
}

# Maximum number of deductions applied per severity
MAX_DEDUCTIONS: dict[Severity, int] = {
    Severity.CRITICAL: 2,
    Severity.HIGH: 3,
    Severity.MEDIUM: 4,
    Severity.LOW: 5,
    Severity.INFO: 10,
}

# Bonus points
BONUS_ZERO_FINDINGS: int = 5
BONUS_READ_ONLY: int = 3

# Score bounds
MIN_SCORE: int = 0
MAX_SCORE: int = 100
BASE_SCORE: int = 100

# Read-only patterns that indicate a server does not mutate state
READ_ONLY_PATTERNS: tuple[str, ...] = (
    "read_only",
    "readonly",
    "read-only",
    "GET",
    "list_tools",
    "list_resources",
    "describe",
    "schema",
)


# ---------------------------------------------------------------------------
# Trust score result model
# ---------------------------------------------------------------------------

@dataclass
class TrustScoreResult:
    """Result of a trust score calculation."""

    score: int
    base: int
    deductions: dict[str, int]  # severity -> total deducted
    bonuses: dict[str, int]     # bonus_name -> points added
    finding_counts: dict[str, int]  # severity -> count
    capped_counts: dict[str, int]   # severity -> capped count applied
    label: str

    def to_dict(self) -> dict[str, Any]:
        return {
            "score": self.score,
            "label": self.label,
            "base": self.base,
            "deductions": self.deductions,
            "bonuses": self.bonuses,
            "finding_counts": self.finding_counts,
            "capped_counts": self.capped_counts,
        }


# ---------------------------------------------------------------------------
# Core calculation
# ---------------------------------------------------------------------------

def _score_label(score: int) -> str:
    """Map a trust score to a human-readable label.

    Args:
        score: Trust score 0-100.

    Returns:
        Label string.
    """
    if score >= 90:
        return "EXCELLENT"
    if score >= 75:
        return "GOOD"
    if score >= 50:
        return "MODERATE"
    if score >= 25:
        return "LOW"
    return "CRITICAL"


def _has_read_only_patterns(findings: list[Finding], source_hint: str = "") -> bool:
    """Check if the scan target appears to be read-only.

    Looks for read-only patterns in source context or finding descriptions.
    A target with no findings also qualifies for the read-only bonus
    if source patterns match.

    Args:
        findings: All findings from the scan.
        source_hint: Optional source code snippet or file content hint.

    Returns:
        True if read-only patterns are detected.
    """
    search_text = source_hint.lower()
    for pattern in READ_ONLY_PATTERNS:
        if pattern.lower() in search_text:
            return True
    return False


def compute_trust_score(
    findings: list[Finding],
    source_hint: str = "",
) -> TrustScoreResult:
    """Compute a trust score from a list of findings.

    Scoring algorithm:
    - Start at 100
    - Deduct per finding: CRITICAL -25 (max 2), HIGH -15 (max 3),
      MEDIUM -8 (max 4), LOW -3 (max 5), INFO -1 (max 10)
    - Bonus: 0 findings = +5, read-only patterns = +3
    - Clamp to [0, 100]

    Args:
        findings: List of Finding objects.
        source_hint: Optional source code for read-only pattern detection.

    Returns:
        TrustScoreResult with detailed breakdown.
    """
    score = BASE_SCORE
    deductions: dict[str, int] = {}
    finding_counts: dict[str, int] = {s.value: 0 for s in Severity}
    capped_counts: dict[str, int] = {s.value: 0 for s in Severity}
    bonuses: dict[str, int] = {}

    # Count findings by severity
    for f in findings:
        finding_counts[f.severity.value] += 1

    # Apply capped deductions
    for severity in Severity:
        count = finding_counts[severity.value]
        max_cap = MAX_DEDUCTIONS[severity]
        applied = min(count, max_cap)
        capped_counts[severity.value] = applied
        deduction = applied * SEVERITY_DEDUCTIONS[severity]
        if deduction > 0:
            deductions[severity.value] = deduction
            score -= deduction

    # Bonuses
    if len(findings) == 0:
        bonuses["zero_findings"] = BONUS_ZERO_FINDINGS
        score += BONUS_ZERO_FINDINGS

    if _has_read_only_patterns(findings, source_hint):
        bonuses["read_only"] = BONUS_READ_ONLY
        score += BONUS_READ_ONLY

    # Clamp
    score = max(MIN_SCORE, min(MAX_SCORE, score))

    return TrustScoreResult(
        score=score,
        base=BASE_SCORE,
        deductions=deductions,
        bonuses=bonuses,
        finding_counts=finding_counts,
        capped_counts=capped_counts,
        label=_score_label(score),
    )


def compute_summary_trust_score(
    summary: ScanSummary,
    source_hint: str = "",
) -> TrustScoreResult:
    """Compute a trust score for an entire scan summary.

    Aggregates all findings across all scanned files.

    Args:
        summary: ScanSummary from a scan run.
        source_hint: Optional source code for read-only pattern detection.

    Returns:
        TrustScoreResult for the entire scan.
    """
    all_findings: list[Finding] = []
    for result in summary.results:
        all_findings.extend(result.findings)
    return compute_trust_score(all_findings, source_hint)


# ---------------------------------------------------------------------------
# Formatting helpers
# ---------------------------------------------------------------------------

def format_trust_score_text(result: TrustScoreResult) -> str:
    """Format a trust score result as human-readable text.

    Args:
        result: TrustScoreResult to format.

    Returns:
        Formatted text string.
    """
    lines: list[str] = []
    lines.append(f"  Trust Score: {result.score}/100 ({result.label})")

    if result.deductions:
        parts = [f"{sev}: -{pts}" for sev, pts in result.deductions.items()]
        lines.append(f"  Deductions: {', '.join(parts)}")

    if result.bonuses:
        parts = [f"{name}: +{pts}" for name, pts in result.bonuses.items()]
        lines.append(f"  Bonuses: {', '.join(parts)}")

    return "\n".join(lines)
