"""Obfuscation detector — regex patterns and Shannon entropy analysis."""
from __future__ import annotations

import math
import re

from pyguard.models import Finding, Severity

_OBFUSCATION_PATTERNS: list[tuple[str, str, Severity]] = [
    # exec(compile(...)) or exec(base64.b64decode(...))
    (r"exec\s*\(\s*compile\s*\(", "exec(compile(…)) pattern", Severity.CRITICAL),
    (r"exec\s*\(\s*base64", "exec(base64…) pattern", Severity.CRITICAL),
    # __import__ called as a function with a string argument
    (r"__import__\s*\(\s*['\"]", "__import__('…') dynamic import", Severity.HIGH),
    # getattr to access dangerous attributes
    (r"getattr\s*\(.*,\s*['\"](?:system|popen|exec|eval)['\"]", "getattr(…, 'exec/system') pattern", Severity.HIGH),
    # chr-concatenation string building
    (r"chr\s*\(\s*\d+\s*\)\s*\+\s*chr\s*\(", "chr()+chr() string construction", Severity.HIGH),
    # globals()['...'] access
    (r"globals\s*\(\s*\)\s*\[", "globals()['…'] dynamic name lookup", Severity.HIGH),
    # Long hex-encoded string (≥8 consecutive \xNN)
    (r"(?:\\x[0-9a-fA-F]{2}){8,}", "Long hex-encoded string (≥8 bytes)", Severity.HIGH),
    # eval with non-trivial argument
    (r"\beval\s*\((?!\s*['\"]?\s*\))", "eval(…) with non-trivial argument", Severity.HIGH),
    # marshal.loads (deserialise bytecode)
    (r"marshal\.loads\s*\(", "marshal.loads() bytecode deserialisation", Severity.CRITICAL),
    # Suspicious lambda + exec combo
    (r"lambda\s*[^:]*:\s*exec\s*\(", "lambda: exec(…) pattern", Severity.HIGH),
]

# A line is suspicious if its Shannon entropy exceeds this threshold
_HIGH_ENTROPY_THRESHOLD = 5.2
# Minimum line length to bother computing entropy
_MIN_ENTROPY_LINE_LEN = 80


def _shannon_entropy(s: str) -> float:
    if not s:
        return 0.0
    freq: dict[str, int] = {}
    for ch in s:
        freq[ch] = freq.get(ch, 0) + 1
    total = len(s)
    return -sum((c / total) * math.log2(c / total) for c in freq.values())


def analyze_obfuscation(
    package_name: str,
    version: str,
    file_path: str,
    source: str,
) -> list[Finding]:
    findings: list[Finding] = []

    for pattern, label, severity in _OBFUSCATION_PATTERNS:
        for m in re.finditer(pattern, source):
            line_no = source[: m.start()].count("\n") + 1
            snippet = source[m.start() : m.start() + 120].split("\n")[0]
            findings.append(
                Finding(
                    severity=severity,
                    title=f"Obfuscation pattern: {label}",
                    description=(
                        f"File '{file_path}' contains a code pattern associated with obfuscation: "
                        f"'{label}'. This technique is commonly used to hide malicious payloads."
                    ),
                    layer="static",
                    package=package_name,
                    version=version,
                    evidence={"pattern": pattern, "snippet": snippet},
                    confidence=0.85,
                    file=file_path,
                    line=line_no,
                    category="obfuscation",
                    remediation="Inspect the flagged code manually before running this package.",
                )
            )

    # High-entropy lines
    high_entropy_lines: list[int] = []
    for lineno, line in enumerate(source.splitlines(), start=1):
        stripped = line.strip()
        if len(stripped) >= _MIN_ENTROPY_LINE_LEN and _shannon_entropy(stripped) >= _HIGH_ENTROPY_THRESHOLD:
            high_entropy_lines.append(lineno)
        if len(high_entropy_lines) >= 3:
            break  # cap at 3 examples per file

    if high_entropy_lines:
        findings.append(
            Finding(
                severity=Severity.MEDIUM,
                title="High-entropy lines (possible encoded payload)",
                description=(
                    f"File '{file_path}' contains {len(high_entropy_lines)} line(s) with "
                    "unusually high Shannon entropy, which may indicate encoded or encrypted payloads."
                ),
                layer="static",
                package=package_name,
                version=version,
                evidence={"high_entropy_lines": high_entropy_lines},
                confidence=0.6,
                file=file_path,
                line=high_entropy_lines[0],
                category="obfuscation",
                remediation="Decode and inspect these strings before trusting this package.",
            )
        )

    return findings
