"""Regex scanner for Tru-Guard."""

import re
from typing import Any

from trusys.scanners import BaseScanner, register_scanner
from trusys.types import (
    MatchType,
    ScanContext,
    ScanResult,
    ScanType,
    Severity,
    Violation,
)


@register_scanner(
    name="regex",
    scan_types=["input", "output"],
    description="Scans content using regex patterns with whitelist or blacklist matching",
    validator_type="regex",
)
class RegexScanner(BaseScanner):
    """Scanner that uses regex patterns to validate content.

    Supports both whitelist (must match) and blacklist (must not match) modes.
    Can check multiple patterns in a single scan.

    Args:
        patterns: Array of pattern objects, each with:
                 - pattern (str, required): The regex pattern string
                 - match_type (str, optional): "whitelist" or "blacklist" (default: "blacklist")
                 - case_sensitive (bool, optional): Whether matching is case-sensitive (default: False)
                 - multiline (bool, optional): Whether to use multiline matching (default: False)
        on_fail: Action to take when scan fails
        enabled: Whether this scanner is enabled
    """

    def __init__(
        self,
        config: list[dict[str, Any]] | None = None,
        on_fail: str | None = None,
        enabled: bool | None = None,
    ) -> None:
        """Initialize the regex scanner.

        Args:
            config: Array of pattern objects, each containing:
                   - pattern (str, required): The regex pattern string
                   - match_type (str, optional): "whitelist" or "blacklist" (default: "blacklist")
                   - case_sensitive (bool, optional): Whether matching is case-sensitive (default: False)
                   - multiline (bool, optional): Whether to use multiline matching (default: False)
            on_fail: Action to take when scan fails
            enabled: Whether this scanner is enabled
        """
        # Config should be a list of pattern objects
        if config is None:
            raise ValueError("'config' is required for RegexScanner and must be a list of pattern objects")
        
        if not isinstance(config, list):
            raise ValueError("'config' must be a list of pattern objects for RegexScanner")
        
        if len(config) == 0:
            raise ValueError("'config' must contain at least one pattern object")
        
        patterns = config
        
        # Initialize base class with empty dict (we handle config separately)
        super().__init__(config={}, on_fail=on_fail, enabled=enabled)

        # Process and validate each pattern
        self.patterns: list[dict[str, Any]] = []
        for i, pattern_config in enumerate(patterns):
            if not isinstance(pattern_config, dict):
                raise ValueError(f"Pattern at index {i} must be a dictionary")
            
            pattern = pattern_config.get("pattern")
            if not pattern:
                raise ValueError(f"'pattern' is required in pattern object at index {i}")
            
            # Extract match_type (default: BLACKLIST)
            match_type = pattern_config.get("match_type", MatchType.BLACKLIST)
            if isinstance(match_type, str):
                match_type = MatchType(match_type)
            
            # Extract case_sensitive and multiline (defaults: False)
            case_sensitive = pattern_config.get("case_sensitive", False)
            multiline = pattern_config.get("multiline", False)
            
            # Build flags from boolean fields
            flags = 0
            if not case_sensitive:
                flags |= re.IGNORECASE
            if multiline:
                flags |= re.MULTILINE
            
            # Compile regex pattern
            try:
                compiled_regex = re.compile(pattern, flags)
            except re.error as e:
                raise ValueError(f"Invalid regex pattern '{pattern}' at index {i}: {e}") from e
            
            # Store pattern configuration
            self.patterns.append({
                "pattern": pattern,
                "match_type": match_type,
                "case_sensitive": case_sensitive,
                "multiline": multiline,
                "regex": compiled_regex,
            })

    def scan(self, content: dict[str, Any], context: ScanContext) -> ScanResult:
        """Perform regex scan on content.

        Args:
            content: JSON dictionary containing content to scan.
                   Should contain 'prompt' for input scans or 'response' for output scans.
                   The scanner extracts the appropriate field based on scan_type.
            context: The scan context

        Returns:
            ScanResult with the scan outcome
        """
        # Extract the actual text to scan based on scan type
        if context.scan_type == ScanType.INPUT:
            text_to_scan = content.get("prompt", "")
        elif context.scan_type == ScanType.OUTPUT:
            text_to_scan = content.get("response", "")
        else:
            # Fallback: try prompt first, then response
            text_to_scan = content.get("prompt") or content.get("response", "")
        
        all_violations = []
        all_metadata = []
        overall_passed = True

        # Check each pattern
        for pattern_config in self.patterns:
            pattern = pattern_config["pattern"]
            match_type = pattern_config["match_type"]
            regex = pattern_config["regex"]
            
            matches = list(regex.finditer(text_to_scan))
            has_match = len(matches) > 0

            # Determine if this pattern passes based on match type
            if match_type == MatchType.WHITELIST:
                # Whitelist: must have at least one match to pass
                pattern_passed = has_match
                if not pattern_passed:
                    overall_passed = False
                    all_violations.append(
                        Violation(
                            rule="regex_whitelist_no_match",
                            severity=Severity.HIGH,
                            message=f"Content does not match required pattern: {pattern}",
                        )
                    )
            else:  # BLACKLIST
                # Blacklist: must have no matches to pass
                pattern_passed = not has_match
                if not pattern_passed:
                    overall_passed = False
                    for match in matches:
                        start, end = match.span()
                        all_violations.append(
                            Violation(
                                rule="regex_blacklist_match",
                                severity=Severity.HIGH,
                                message=f"Content matches forbidden pattern: {pattern}",
                                span=(start, end),
                                metadata={"matched_text": match.group()},
                            )
                        )
            
            # Store metadata for this pattern
            all_metadata.append({
                "pattern": pattern,
                "match_type": match_type.value,
                "case_sensitive": pattern_config["case_sensitive"],
                "multiline": pattern_config["multiline"],
                "match_count": len(matches),
            })

        # Build reason from violations - list all matches
        reason = ""
        if not overall_passed:
            if all_violations:
                all_matched = [
                    v.metadata.get("matched_text", "")
                    for v in all_violations
                    if v.metadata.get("matched_text")
                ]
                if all_matched:
                    reason = f"content matches forbidden pattern: {', '.join(repr(m) for m in all_matched)}"
                else:
                    reason = all_violations[0].message
            else:
                reason = "Content does not match required pattern"
        
        # Build assertion output - collect all matched text from violations
        assertion_output = ""
        if all_violations:
            all_matched = [
                v.metadata.get("matched_text")
                for v in all_violations
                if v.metadata.get("matched_text")
            ]
            if all_matched:
                assertion_output = "matched_text': " + ", ".join(f"'{m}'" for m in all_matched)
            else:
                for pattern_config in self.patterns:
                    assertion_output = f"pattern: '{pattern_config['pattern']}'"
                    break
        elif not overall_passed:
            # Whitelist case - no match found
            for pattern_config in self.patterns:
                if pattern_config["match_type"] == MatchType.WHITELIST:
                    assertion_output = f"pattern: '{pattern_config['pattern']}'"
                    break
        
        # Calculate score (0.0 if failed, 1.0 if passed, or based on violations)
        score = 1.0 if overall_passed else 0.9  # Default score for failures
        
        # Build metadata in new format
        metadata = {
            "patterns": all_metadata,
            "assertion": {
                "type": "regex",
                "output": assertion_output
            },
            "score": score,
            "reason": reason,
            "latency": 0.0,  # Will be set by execution runner
            "action": {
                "type": self.on_fail.value if self.on_fail else "ignore"
            }
        }

        return ScanResult(
            passed=overall_passed,
            scanner_name=self.name,
            scan_type=context.scan_type,
            violations=all_violations,
            metadata=metadata,
        )

    def get_config(self) -> dict[str, Any]:
        """Get the scanner configuration.

        Returns:
            Dictionary of configuration values with 'patterns' key containing the array
        """
        config = super().get_config()
        patterns_config = []
        for pattern_config in self.patterns:
            patterns_config.append({
                "pattern": pattern_config["pattern"],
                "match_type": pattern_config["match_type"].value,
                "case_sensitive": pattern_config["case_sensitive"],
                "multiline": pattern_config["multiline"],
            })
        # Return the patterns array directly in the config dict
        config["patterns"] = patterns_config
        return config

    def __repr__(self) -> str:
        """String representation of the scanner."""
        patterns_repr = ", ".join(
            f"{{pattern={p['pattern']!r}, match_type={p['match_type'].value!r}}}"
            for p in self.patterns
        )
        return (
            f"{self.__class__.__name__}("
            f"patterns=[{patterns_repr}], "
            f"on_fail={self.on_fail.value!r})"
        )
