"""Error translation and solution finding."""

from __future__ import annotations

import re
from dataclasses import dataclass


@dataclass(slots=True)
class ParsedError:
    """Represents a parsed error message."""

    error_type: str
    message: str
    language: str | None
    framework: str | None
    file_path: str | None
    line_number: int | None
    key_terms: list[str]


class ErrorParser:
    """Parses error messages and stack traces to extract key information."""

    # Language detection patterns (order matters - most specific first)
    LANGUAGE_PATTERNS = {
        "rust": [
            r"error\[E\d{4}\]:",
            r"cannot borrow",
            r"--> .+\.rs:\d+:\d+",
        ],
        "typescript": [
            r"at .+\.tsx?:\d+:\d+",
            r"\.tsx?:",
            r"TS\d{4}:",
        ],
        "javascript": [
            r"at .+\.jsx?:\d+:\d+",
            r"\.jsx?:",
            r"node_modules",
            r"Cannot read property",
            r"is not defined",
        ],
        "python": [
            r"File \"(.+)\.py\"",
            r"Traceback \(most recent call last\)",
            r"(ImportError|AttributeError|ModuleNotFoundError)",
        ],
        "java": [
            r"at .+\.java:\d+",
            r"Exception in thread",
            r"(NullPointerException|IllegalArgumentException)",
        ],
        "go": [
            r"panic:",
            r"goroutine \d+",
            r".+\.go:\d+",
        ],
    }

    # Framework detection patterns
    FRAMEWORK_PATTERNS = {
        "react": [r"react", r"jsx", r"useState", r"useEffect"],
        "vue": [r"vue", r"@vue", r"composition-api"],
        "angular": [r"angular", r"@angular", r"ngOnInit"],
        "django": [r"django", r"django\."],
        "flask": [r"flask", r"werkzeug"],
        "fastapi": [r"fastapi", r"pydantic", r"starlette"],
        "express": [r"express", r"app\.get", r"app\.post"],
        "nextjs": [r"next", r"getServerSideProps", r"getStaticProps"],
    }

    # Common error patterns (checked in order - most specific first)
    ERROR_TYPE_PATTERNS = {
        "python": {
            "AttributeError": r"AttributeError: '(.+)' object has no attribute '(.+)'",
            "TypeError": r"TypeError: (.+)",
            "ImportError": r"(ImportError|ModuleNotFoundError): (.+)",
            "ValueError": r"ValueError: (.+)",
            "KeyError": r"KeyError: (.+)",
        },
        "javascript": {
            # Web-specific errors (check first - most specific)
            "CORS Error": r"CORS policy|Access-Control-Allow-Origin|No.*Access-Control",
            "Fetch Error": r"fetch.*failed|Failed to fetch|NetworkError",
            "Cannot read property": r"Cannot read propert(?:y|ies) ['\"](.+?)['\"] of",
            "undefined is not": r"undefined is not (a function|an object)",
            "null is not": r"null is not (a function|an object)",
            # Standard errors (check after specific ones)
            "TypeError": r"TypeError: (.+)",
            "ReferenceError": r"ReferenceError: (.+)",
            "SyntaxError": r"SyntaxError: (.+)",
            "RangeError": r"RangeError: (.+)",
        },
        "typescript": {
            "CORS Error": r"CORS policy|Access-Control-Allow-Origin",
            "Fetch Error": r"fetch.*failed|Failed to fetch",
            "Cannot read property": r"Cannot read propert(?:y|ies) ['\"](.+?)['\"] of",
            "TypeError": r"TypeError: (.+)",
            "ReferenceError": r"ReferenceError: (.+)",
            "SyntaxError": r"SyntaxError: (.+)",
        },
        "rust": {
            "borrow error": r"borrow of moved value",
            "cannot borrow": r"cannot borrow",
            "lifetime error": r"lifetime (.+) may not live long enough",
            "type mismatch": r"expected (.+), found (.+)",
            "E0382": r"error\[E0382\]",
            "E0502": r"error\[E0502\]",
            "E0308": r"error\[E0308\]",
        },
    }

    def parse(
        self,
        error_message: str,
        language: str | None = None,
        framework: str | None = None,
    ) -> ParsedError:
        """
        Parse an error message to extract key information.

        Args:
            error_message: The error text or stack trace
            language: Programming language (auto-detected if None)
            framework: Framework name (auto-detected if None)

        Returns:
            ParsedError with extracted information
        """
        # Detect language if not provided
        if not language:
            language = self._detect_language(error_message)

        # Detect framework if not provided
        if not framework:
            framework = self._detect_framework(error_message)

        # Extract error type
        error_type = self._extract_error_type(error_message, language)

        # Extract file path and line number
        file_path, line_number = self._extract_location(error_message)

        # Extract key terms
        key_terms = self._extract_key_terms(error_message, error_type)

        # Clean message
        message = self._clean_message(error_message)

        return ParsedError(
            error_type=error_type or "Unknown Error",
            message=message,
            language=language,
            framework=framework,
            file_path=file_path,
            line_number=line_number,
            key_terms=key_terms,
        )

    def _detect_language(self, text: str) -> str | None:
        """Detect programming language from error message."""
        scores = {}
        for lang, patterns in self.LANGUAGE_PATTERNS.items():
            score = sum(1 for pattern in patterns if re.search(pattern, text, re.IGNORECASE))
            if score > 0:
                scores[lang] = score

        if scores:
            return max(scores, key=scores.get)
        return None

    def _detect_framework(self, text: str) -> str | None:
        """Detect framework from error message."""
        text_lower = text.lower()

        for framework, patterns in self.FRAMEWORK_PATTERNS.items():
            if any(re.search(pattern, text_lower) for pattern in patterns):
                return framework
        return None

    def _extract_error_type(self, text: str, language: str | None) -> str | None:
        """Extract the error type/name."""
        # Check for common web errors first (language-agnostic)
        web_error_patterns = {
            "CORS Error": r"CORS policy|Access-Control-Allow-Origin|No.*Access-Control",
            "Fetch Error": r"fetch.*failed|Failed to fetch|NetworkError",
            "Cannot read property": r"Cannot read propert(?:y|ies) ['\"](.+?)['\"] of",
        }

        for error_name, pattern in web_error_patterns.items():
            if re.search(pattern, text, re.IGNORECASE):
                return error_name

        # Language-specific extraction if language is detected
        if language and language in self.ERROR_TYPE_PATTERNS:
            for error_name, pattern in self.ERROR_TYPE_PATTERNS[language].items():
                if re.search(pattern, text, re.IGNORECASE):
                    return error_name

        # Generic error type extraction as fallback
        match = re.search(r"([\w]+Error|[\w]+Exception):", text)
        if match:
            return match.group(1)

        return None

    def _extract_location(self, text: str) -> tuple[str | None, int | None]:
        """Extract file path and line number."""
        # Python format: File "path.py", line 123
        match = re.search(r'File "(.+?)", line (\d+)', text)
        if match:
            return match.group(1), int(match.group(2))

        # JavaScript/TypeScript format: at path.js:123:45
        match = re.search(r"at (.+?):(\d+):\d+", text)
        if match:
            return match.group(1), int(match.group(2))

        # Rust format: --> path.rs:123:45
        match = re.search(r"--> (.+?):(\d+):\d+", text)
        if match:
            return match.group(1), int(match.group(2))

        return None, None

    def _extract_key_terms(self, text: str, error_type: str | None) -> list[str]:
        """Extract key terms for searching."""
        terms = set()  # Use set to avoid duplicates

        # Important web/tech terms to always capture
        important_terms = [
            "CORS",
            "cors",
            "fetch",
            "async",
            "await",
            "Promise",
            "undefined",
            "null",
            "map",
            "filter",
            "reduce",
            "Access-Control-Allow-Origin",
            "XMLHttpRequest",
            "module",
            "import",
            "export",
            "require",
        ]

        # Extract important terms first
        text_lower = text.lower()
        for term in important_terms:
            if term.lower() in text_lower:
                terms.add(term)
                if len(terms) >= 3:  # Limit important terms to 3
                    break

        # Extract quoted strings (often property names or specific values)
        quoted = re.findall(r"'([^']+)'|\"([^\"]+)\"", text)
        for match in quoted:
            term = match[0] or match[1]
            # Keep short important terms, ignore paths
            if term and "/" not in term and "\\" not in term:
                # Allow short terms for property names like "map", "id", etc.
                if len(term) <= 15:  # Reasonable property name length
                    if not error_type or term.lower() != error_type.lower():
                        terms.add(term)
                        if len(terms) >= 5:
                            break

        # Extract technical terms (CamelCase, snake_case)
        technical = re.findall(r"\b[A-Z][a-z]+(?:[A-Z][a-z]+)+\b|\b\w+_\w+\b", text)
        # Don't exclude undefined/null - they're useful search terms
        for term in technical:
            if not error_type or term != error_type:
                terms.add(term)
                if len(terms) >= 5:
                    break

        return list(terms)[:5]  # Return max 5 unique key terms

    def _clean_message(self, text: str) -> str:
        """Clean and extract the core error message."""
        # Try to find the main error line
        lines = text.strip().split("\n")

        # Look for the error line (usually contains "Error:" or "Exception:")
        for line in reversed(lines):
            if re.search(r"(Error|Exception):", line):
                return line.strip()

        # If no error line found, return first non-empty line
        for line in lines:
            if line.strip():
                return line.strip()

        return text.strip()

    def build_search_query(self, parsed: ParsedError) -> str:
        """Build an optimized search query from parsed error."""
        parts = []

        # Add language
        if parsed.language:
            parts.append(parsed.language)

        # Add framework
        if parsed.framework:
            parts.append(parsed.framework)

        # Add error type
        if parsed.error_type and parsed.error_type != "Unknown Error":
            parts.append(parsed.error_type)

        # Add key terms (limit to avoid overly specific queries)
        parts.extend(parsed.key_terms[:2])

        # Build query - don't use site filter as SearXNG handles this better
        # with its category-based engine selection
        query = " ".join(parts)

        # Add "error" or "fix" to help guide results
        if query:  # Only add if we have content
            query += " error fix"

        return query
