"""Compliance Profiles for MCP Security Audit.

Pre-built scan profiles for different environments and compliance
requirements. Each profile controls which rules are enabled,
minimum severity threshold, failure conditions, and output format.

Available profiles:
  - strict:       All rules, severity >= LOW, fail on any finding
  - standard:     Production rules, severity >= MEDIUM
  - ci:           Fast scan, severity >= HIGH, machine-readable
  - development:  Relaxed, severity >= HIGH, no MCP protocol rules
  - owasp-agentic: Only OWASP Agentic Top 10 related rules
  - mcp-only:     Only MCP protocol-specific rules (MCP-001+)

Zero external dependencies.
"""

from __future__ import annotations

from dataclasses import dataclass, field
from typing import Any

from .models import Severity


# ---------------------------------------------------------------------------
# Profile definition
# ---------------------------------------------------------------------------

@dataclass(frozen=True)
class ComplianceProfile:
    """A pre-built compliance profile for scanning."""

    name: str
    description: str
    enabled_rules: list[str] | None  # None = all rules
    disabled_rules: list[str] = field(default_factory=list)
    min_severity: Severity = Severity.LOW
    fail_threshold: int = 1  # fail if findings >= this count
    output_format: str = "text"

    def to_dict(self) -> dict[str, Any]:
        """Serialize profile to dictionary."""
        return {
            "name": self.name,
            "description": self.description,
            "enabled_rules": self.enabled_rules,
            "disabled_rules": list(self.disabled_rules),
            "min_severity": self.min_severity.value,
            "fail_threshold": self.fail_threshold,
            "output_format": self.output_format,
        }


# ---------------------------------------------------------------------------
# OWASP Agentic Top 10 rule IDs (Python + TS)
# ---------------------------------------------------------------------------

_OWASP_AGENTIC_RULE_IDS: list[str] = [
    # A01 - Prompt Injection
    "MCP-004",
    # A02 - Insecure Tool Implementation
    "CMD-001", "CMD-002", "SQL-001", "PATH-001", "DESER-001",
    # A03 - Excessive Agency
    "PERM-001", "PRIV-001",
    # A04 - Insecure Output Handling
    "INFO-001", "ERR-001",
    # A05 - Insufficient Access Control
    "AUTH-001", "AUTHZ-001",
    # A06 - Improper Input Validation
    "VAL-001", "IDOR-001",
    # A07 - SSRF
    "SSRF-001",
    # A08 - Data Exfiltration
    "SEC-001", "MCP-003", "FILE-001", "LOG-001",
    # A09 - Denial of Service
    "RATE-001", "RES-001", "REDOS-001",
    # A10 - Insecure Configuration
    "PATH-002", "TEMP-001", "CORS-001", "MCP-001", "MCP-002",
]

# ---------------------------------------------------------------------------
# MCP protocol-specific rule IDs
# ---------------------------------------------------------------------------

_MCP_RULE_IDS: list[str] = [
    "MCP-001", "MCP-002", "MCP-003", "MCP-004",
    "MCP-005", "MCP-006", "MCP-007", "MCP-008",
    "MCP-009", "MCP-010", "MCP-011", "MCP-012",
    "MCP-013", "MCP-014", "MCP-015", "MCP-016",
    "MCP-017", "MCP-018", "MCP-019",
]

# ---------------------------------------------------------------------------
# MCP protocol rules to disable in development profile
# ---------------------------------------------------------------------------

_MCP_DISABLED_IN_DEV: list[str] = list(_MCP_RULE_IDS)


# ---------------------------------------------------------------------------
# Built-in profiles
# ---------------------------------------------------------------------------

PROFILE_STRICT = ComplianceProfile(
    name="strict",
    description="All rules enabled, severity >= LOW, fail on any finding",
    enabled_rules=None,  # all rules
    min_severity=Severity.LOW,
    fail_threshold=1,
    output_format="text",
)

PROFILE_STANDARD = ComplianceProfile(
    name="standard",
    description="Production rules, severity >= MEDIUM",
    enabled_rules=None,  # all rules
    min_severity=Severity.MEDIUM,
    fail_threshold=3,
    output_format="text",
)

PROFILE_CI = ComplianceProfile(
    name="ci",
    description="Fast scan, severity >= HIGH only, machine-readable output",
    enabled_rules=None,  # all rules
    min_severity=Severity.HIGH,
    fail_threshold=1,
    output_format="json",
)

PROFILE_DEVELOPMENT = ComplianceProfile(
    name="development",
    description="Relaxed, severity >= HIGH, no MCP protocol rules",
    enabled_rules=None,  # all rules minus disabled
    disabled_rules=_MCP_DISABLED_IN_DEV,
    min_severity=Severity.HIGH,
    fail_threshold=5,
    output_format="text",
)

PROFILE_OWASP_AGENTIC = ComplianceProfile(
    name="owasp-agentic",
    description="Only OWASP Agentic Top 10 related rules",
    enabled_rules=_OWASP_AGENTIC_RULE_IDS,
    min_severity=Severity.LOW,
    fail_threshold=1,
    output_format="text",
)

PROFILE_MCP_ONLY = ComplianceProfile(
    name="mcp-only",
    description="Only MCP protocol-specific rules (MCP-001+)",
    enabled_rules=_MCP_RULE_IDS,
    min_severity=Severity.LOW,
    fail_threshold=1,
    output_format="text",
)


# ---------------------------------------------------------------------------
# Profile registry
# ---------------------------------------------------------------------------

PROFILES: dict[str, ComplianceProfile] = {
    "strict": PROFILE_STRICT,
    "standard": PROFILE_STANDARD,
    "ci": PROFILE_CI,
    "development": PROFILE_DEVELOPMENT,
    "owasp-agentic": PROFILE_OWASP_AGENTIC,
    "mcp-only": PROFILE_MCP_ONLY,
}

PROFILE_NAMES: list[str] = sorted(PROFILES.keys())


def get_profile(name: str) -> ComplianceProfile:
    """Get a compliance profile by name.

    Args:
        name: Profile name (case-insensitive).

    Returns:
        ComplianceProfile instance.

    Raises:
        ValueError: If profile name is unknown.
    """
    key = name.lower().strip()
    if key not in PROFILES:
        valid = ", ".join(sorted(PROFILES.keys()))
        raise ValueError(f"Unknown profile '{name}'. Available: {valid}")
    return PROFILES[key]


def list_profiles() -> list[ComplianceProfile]:
    """Return all available profiles, sorted by name."""
    return [PROFILES[k] for k in sorted(PROFILES.keys())]


def format_profiles_text() -> str:
    """Format all profiles as a human-readable list.

    Returns:
        Formatted string describing each profile.
    """
    lines: list[str] = []
    lines.append(f"\n{'=' * 60}")
    lines.append("  MCP Security Audit — Compliance Profiles")
    lines.append(f"{'=' * 60}\n")

    for profile in list_profiles():
        rules_desc = "all" if profile.enabled_rules is None else str(len(profile.enabled_rules))
        disabled_desc = ""
        if profile.disabled_rules:
            disabled_desc = f", {len(profile.disabled_rules)} disabled"

        lines.append(f"  {profile.name}")
        lines.append(f"    {profile.description}")
        lines.append(
            f"    Rules: {rules_desc}{disabled_desc} | "
            f"Min severity: {profile.min_severity.value} | "
            f"Fail threshold: {profile.fail_threshold} | "
            f"Output: {profile.output_format}"
        )
        lines.append("")

    lines.append(f"{'=' * 60}")
    return "\n".join(lines)


def apply_profile_to_rules(
    profile: ComplianceProfile,
    rules: list[Any],
) -> list[Any]:
    """Filter rules according to a compliance profile.

    Args:
        profile: The compliance profile to apply.
        rules: List of Rule instances.

    Returns:
        Filtered list of rules matching the profile.
    """
    result = list(rules)

    # Filter by enabled_rules (whitelist)
    if profile.enabled_rules is not None:
        enabled_set = set(profile.enabled_rules)
        result = [r for r in result if r.rule_id in enabled_set]

    # Filter by disabled_rules (blacklist)
    if profile.disabled_rules:
        disabled_set = set(profile.disabled_rules)
        result = [r for r in result if r.rule_id not in disabled_set]

    return result


def apply_profile_severity_filter(
    profile: ComplianceProfile,
    findings: list[Any],
) -> list[Any]:
    """Filter findings by the profile's minimum severity threshold.

    Args:
        profile: The compliance profile to apply.
        findings: List of Finding instances.

    Returns:
        Filtered list of findings meeting the minimum severity.
    """
    severity_order = list(Severity)
    min_idx = severity_order.index(profile.min_severity)
    return [
        f for f in findings
        if severity_order.index(f.severity) <= min_idx
    ]


def check_fail_threshold(
    profile: ComplianceProfile,
    finding_count: int,
) -> bool:
    """Check if finding count exceeds the profile's fail threshold.

    Args:
        profile: The compliance profile to check.
        finding_count: Number of findings after filtering.

    Returns:
        True if the scan should be considered failed.
    """
    return finding_count >= profile.fail_threshold
