"""
Security Scanner for SparkNet.

Scans content for sensitive information before allowing it to be shared.
All findings are reported to the user for review.
"""

from dataclasses import dataclass, field
from datetime import datetime, timezone
from typing import List, Optional, Dict, Any
from enum import Enum

from .patterns import (
    PatternRule,
    FindingType,
    FindingSeverity,
    BUILTIN_PATTERNS,
    get_all_patterns,
    load_custom_patterns,
)


@dataclass
class ScanFinding:
    """A single finding from a security scan."""
    rule_name: str
    finding_type: FindingType
    severity: FindingSeverity
    description: str
    matched_text: str
    start_position: int
    end_position: int
    redacted_text: str = ""  # Partially hidden version

    def __post_init__(self):
        if not self.redacted_text:
            self.redacted_text = self._redact(self.matched_text)

    def _redact(self, text: str) -> str:
        """Create a partially redacted version of the matched text."""
        if len(text) <= 8:
            return "*" * len(text)
        # Show first 4 and last 4 characters
        return text[:4] + "*" * (len(text) - 8) + text[-4:]

    def to_dict(self) -> Dict[str, Any]:
        return {
            "rule_name": self.rule_name,
            "finding_type": self.finding_type.value,
            "severity": self.severity.value,
            "description": self.description,
            "matched_text": self.redacted_text,  # Never expose full text
            "position": {"start": self.start_position, "end": self.end_position},
        }


@dataclass
class ScanResult:
    """Result of a security scan."""
    content_hash: str
    scanned_at: str
    findings: List[ScanFinding] = field(default_factory=list)
    is_clean: bool = True
    should_block: bool = False
    highest_severity: Optional[FindingSeverity] = None

    def __post_init__(self):
        if not self.scanned_at:
            self.scanned_at = datetime.now(timezone.utc).isoformat()

        if self.findings:
            self.is_clean = False
            # Determine highest severity
            severity_order = [
                FindingSeverity.CRITICAL,
                FindingSeverity.HIGH,
                FindingSeverity.MEDIUM,
                FindingSeverity.LOW,
                FindingSeverity.INFO,
            ]
            for sev in severity_order:
                if any(f.severity == sev for f in self.findings):
                    self.highest_severity = sev
                    break

            # Block if any CRITICAL or HIGH findings
            self.should_block = any(
                f.severity in [FindingSeverity.CRITICAL, FindingSeverity.HIGH]
                for f in self.findings
            )

    @property
    def critical_count(self) -> int:
        return sum(1 for f in self.findings if f.severity == FindingSeverity.CRITICAL)

    @property
    def high_count(self) -> int:
        return sum(1 for f in self.findings if f.severity == FindingSeverity.HIGH)

    @property
    def warning_count(self) -> int:
        return sum(1 for f in self.findings if f.severity == FindingSeverity.MEDIUM)

    def summary(self) -> str:
        """Human-readable summary."""
        if self.is_clean:
            return "No sensitive content detected"

        parts = []
        if self.critical_count:
            parts.append(f"{self.critical_count} critical")
        if self.high_count:
            parts.append(f"{self.high_count} high")
        if self.warning_count:
            parts.append(f"{self.warning_count} warnings")

        return f"Found: {', '.join(parts)}"

    def to_dict(self) -> Dict[str, Any]:
        return {
            "content_hash": self.content_hash,
            "scanned_at": self.scanned_at,
            "is_clean": self.is_clean,
            "should_block": self.should_block,
            "highest_severity": self.highest_severity.value if self.highest_severity else None,
            "summary": self.summary(),
            "findings": [f.to_dict() for f in self.findings],
            "counts": {
                "critical": self.critical_count,
                "high": self.high_count,
                "warnings": self.warning_count,
                "total": len(self.findings),
            },
        }


class SecurityScanner:
    """
    Scans content for sensitive information.

    Usage:
        scanner = SecurityScanner()
        result = scanner.scan("My API key is sk-abc123...")

        if result.should_block:
            print("Content blocked - contains secrets!")
            for finding in result.findings:
                print(f"  - {finding.description}: {finding.redacted_text}")
    """

    def __init__(
        self,
        load_custom: bool = True,
        custom_patterns_path: Optional[str] = None,
        disabled_rules: Optional[List[str]] = None,
        min_severity: FindingSeverity = FindingSeverity.INFO,
    ):
        """
        Initialize the security scanner.

        Args:
            load_custom: Whether to load custom patterns from config
            custom_patterns_path: Path to custom patterns JSON file
            disabled_rules: List of rule names to disable
            min_severity: Minimum severity to report (default: all)
        """
        self.disabled_rules = set(disabled_rules or [])
        self.min_severity = min_severity
        self._patterns: List[PatternRule] = []

        # Load patterns
        self._patterns = list(BUILTIN_PATTERNS)
        if load_custom:
            custom = load_custom_patterns(custom_patterns_path)
            self._patterns.extend(custom)

    def scan(self, content: str) -> ScanResult:
        """
        Scan content for sensitive information.

        Args:
            content: The text content to scan

        Returns:
            ScanResult with all findings
        """
        import hashlib
        content_hash = hashlib.sha256(content.encode()).hexdigest()[:16]

        findings: List[ScanFinding] = []

        # Check severity order for early exit optimization
        severity_order = {
            FindingSeverity.CRITICAL: 0,
            FindingSeverity.HIGH: 1,
            FindingSeverity.MEDIUM: 2,
            FindingSeverity.LOW: 3,
            FindingSeverity.INFO: 4,
        }
        min_severity_order = severity_order[self.min_severity]

        for pattern in self._patterns:
            # Skip disabled rules
            if pattern.name in self.disabled_rules:
                continue

            # Skip if below minimum severity
            if severity_order[pattern.severity] > min_severity_order:
                continue

            # Find matches
            matches = pattern.match(content)
            for matched_text, start, end in matches:
                finding = ScanFinding(
                    rule_name=pattern.name,
                    finding_type=pattern.finding_type,
                    severity=pattern.severity,
                    description=pattern.description,
                    matched_text=matched_text,
                    start_position=start,
                    end_position=end,
                )
                findings.append(finding)

        return ScanResult(
            content_hash=content_hash,
            scanned_at=datetime.now(timezone.utc).isoformat(),
            findings=findings,
        )

    def quick_check(self, content: str) -> bool:
        """
        Quick check if content is safe (no CRITICAL or HIGH findings).

        Returns True if safe, False if blocked.
        """
        result = self.scan(content)
        return not result.should_block

    def get_findings_by_severity(
        self,
        result: ScanResult,
        severity: FindingSeverity,
    ) -> List[ScanFinding]:
        """Get findings of a specific severity."""
        return [f for f in result.findings if f.severity == severity]

    def add_pattern(self, pattern: PatternRule):
        """Add a pattern at runtime."""
        self._patterns.append(pattern)

    def disable_rule(self, rule_name: str):
        """Disable a rule by name."""
        self.disabled_rules.add(rule_name)

    def enable_rule(self, rule_name: str):
        """Enable a previously disabled rule."""
        self.disabled_rules.discard(rule_name)

    def list_rules(self) -> List[Dict[str, Any]]:
        """List all available rules."""
        return [
            {
                "name": p.name,
                "type": p.finding_type.value,
                "severity": p.severity.value,
                "description": p.description,
                "enabled": p.name not in self.disabled_rules,
            }
            for p in self._patterns
        ]


# Singleton instance for convenience
_default_scanner: Optional[SecurityScanner] = None


def get_scanner() -> SecurityScanner:
    """Get the default scanner instance."""
    global _default_scanner
    if _default_scanner is None:
        _default_scanner = SecurityScanner()
    return _default_scanner


def scan_content(content: str) -> ScanResult:
    """Convenience function to scan content with default scanner."""
    return get_scanner().scan(content)


def is_safe(content: str) -> bool:
    """Convenience function to check if content is safe to share."""
    return get_scanner().quick_check(content)
