"""GO-DESER-001: Unsafe Deserialization detection for Go.

Detects json.Unmarshal into interface{}/any without type checking,
and other unsafe deserialization patterns.
"""

from __future__ import annotations

import re

from . import GoRule
from ..models import Finding, Severity

# json.Unmarshal into interface{} or any
UNMARSHAL_INTERFACE_PATTERN = re.compile(
    r'json\.(?:Unmarshal|NewDecoder)\b[^)]*(?:interface\s*\{\s*\}|any)\b'
)
# json.Unmarshal with &map[string]interface{}
UNMARSHAL_MAP_INTERFACE_PATTERN = re.compile(
    r'json\.Unmarshal\s*\([^,]+,\s*&\s*(?:map\[string\]interface\s*\{\s*\}|map\[string\]any)'
)
# json.NewDecoder().Decode into interface{}
DECODER_INTERFACE_PATTERN = re.compile(
    r'\.Decode\s*\(\s*&?\s*(?:interface\s*\{\s*\}|any)\b'
)
# yaml.Unmarshal into interface{}
YAML_UNMARSHAL_PATTERN = re.compile(
    r'yaml\.Unmarshal\s*\([^,]+,\s*&?\s*(?:interface\s*\{\s*\}|any)'
)
# gob.Decode (always dangerous with untrusted input)
GOB_DECODE_PATTERN = re.compile(
    r'gob\.NewDecoder\b|\.Decode\s*\(&'
)
# encoding/xml Unmarshal into interface{}
XML_UNMARSHAL_PATTERN = re.compile(
    r'xml\.(?:Unmarshal|NewDecoder)\b[^)]*(?:interface\s*\{\s*\}|any)\b'
)


class GoUnsafeDeserializationRule(GoRule):
    """Detects unsafe deserialization in Go."""

    @property
    def rule_id(self) -> str:
        return "GO-DESER-001"

    @property
    def name(self) -> str:
        return "Unsafe Deserialization (json.Unmarshal)"

    @property
    def severity(self) -> Severity:
        return Severity.HIGH

    @property
    def cwe(self) -> str:
        return "CWE-502"

    @property
    def description(self) -> str:
        return (
            "json.Unmarshal into interface{}/any without type checking "
            "allows arbitrary data injection."
        )

    @property
    def detailed_description(self) -> str:
        return (
            "json.Unmarshal into interface{}/any without type checking "
            "allows arbitrary data injection.\n\n"
            "Unmarshaling into interface{} or any accepts any JSON structure,\n"
            "bypassing Go's type system. An attacker can inject unexpected\n"
            "data types or deeply nested structures to cause panics or\n"
            "logic errors.\n\n"
            "Examples of vulnerable code:\n"
            "  var data interface{}\n"
            "  json.Unmarshal(body, &data)\n\n"
            "  var m map[string]any\n"
            "  json.NewDecoder(r.Body).Decode(&m)\n\n"
            "How to fix:\n"
            "  - Define a struct with explicit fields and types\n"
            "  - Use json.Decoder with DisallowUnknownFields()\n"
            "  - Validate the unmarshaled data before use"
        )

    def check(self, source: str, filename: str = "") -> list[Finding]:
        """Check for unsafe deserialization patterns."""
        findings: list[Finding] = []

        # json.Unmarshal into interface{}
        for lineno, line, _match in self._search_lines(source, UNMARSHAL_INTERFACE_PATTERN):
            findings.append(self._make_finding(
                lineno, line,
                "json.Unmarshal into interface{}/any — use a typed struct.",
            ))

        # json.Unmarshal into map[string]interface{}
        for lineno, line, _match in self._search_lines(source, UNMARSHAL_MAP_INTERFACE_PATTERN):
            if any(f.line == lineno for f in findings):
                continue
            findings.append(self._make_finding(
                lineno, line,
                "json.Unmarshal into map[string]interface{} — use typed map or struct.",
            ))

        # json.Decoder.Decode into interface{}
        for lineno, line, _match in self._search_lines(source, DECODER_INTERFACE_PATTERN):
            if any(f.line == lineno for f in findings):
                continue
            findings.append(self._make_finding(
                lineno, line,
                "Decode into interface{}/any — use a typed struct.",
            ))

        # yaml.Unmarshal into interface{}
        for lineno, line, _match in self._search_lines(source, YAML_UNMARSHAL_PATTERN):
            findings.append(Finding(
                rule_id=self.rule_id,
                severity=Severity.HIGH,
                title="Unsafe YAML Deserialization",
                description="yaml.Unmarshal into interface{}/any.",
                file="",
                line=lineno,
                code_snippet=line.strip()[:80],
                recommendation="Use a typed struct for yaml.Unmarshal.",
                cwe=self.cwe,
            ))

        # encoding/gob (always risky with untrusted input)
        for lineno, line, _match in self._search_lines(source, GOB_DECODE_PATTERN):
            findings.append(Finding(
                rule_id=self.rule_id,
                severity=Severity.MEDIUM,
                title="Gob Deserialization",
                description=(
                    "encoding/gob decoding — dangerous with untrusted input."
                ),
                file="",
                line=lineno,
                code_snippet=line.strip()[:80],
                recommendation=(
                    "Never use gob with untrusted input. "
                    "Use JSON or Protocol Buffers instead."
                ),
                cwe=self.cwe,
            ))

        return findings

    def _make_finding(self, lineno: int, line: str, desc: str) -> Finding:
        """Create a deserialization finding."""
        return Finding(
            rule_id=self.rule_id,
            severity=self.severity,
            title="Unsafe Deserialization",
            description=desc,
            file="",
            line=lineno,
            code_snippet=line.strip()[:80],
            recommendation=(
                "Define a struct with explicit fields and use "
                "json.Decoder.DisallowUnknownFields()."
            ),
            cwe=self.cwe,
        )
