"""Version diff analyzer — detects suspicious changes between package versions."""
from __future__ import annotations

import difflib
import re
import tempfile
from pathlib import Path
from typing import Any

from pyguard.models import Finding, Severity

# Imports that are benign in existing code but suspicious when *newly added*
_SUSPICIOUS_NEW_IMPORTS = {
    "requests",
    "urllib",
    "urllib3",
    "httpx",
    "aiohttp",
    "socket",
    "subprocess",
    "base64",
    "codecs",
    "zlib",
    "ctypes",
    "cffi",
}

_CREDENTIAL_STRINGS = [
    ".ssh",
    ".aws",
    ".kube",
    "id_rsa",
    "id_ed25519",
    ".azure",
    ".config/gcloud",
    ".git-credentials",
    ".gitconfig",
    "wallet",
    "keystore",
    ".bash_history",
    ".zsh_history",
    "environ",
    "getenv",
]

_BINARY_EXTENSIONS = {".so", ".pyd", ".dll", ".dylib"}


def _added_lines(diff_text: str) -> list[str]:
    return [line[1:] for line in diff_text.splitlines() if line.startswith("+") and not line.startswith("+++")]


def _new_imports(added: list[str]) -> list[str]:
    found: list[str] = []
    import_re = re.compile(r"^\s*(?:import|from)\s+([\w.]+)")
    for line in added:
        m = import_re.match(line)
        if m:
            top = m.group(1).split(".")[0]
            if top in _SUSPICIOUS_NEW_IMPORTS:
                found.append(top)
    return list(set(found))


def _new_credential_refs(added: list[str]) -> list[str]:
    found: list[str] = []
    for line in added:
        for cred in _CREDENTIAL_STRINGS:
            if cred in line:
                found.append(cred)
    return list(set(found))


def analyze_diff(
    package_name: str,
    version: str,
    old_files: dict[str, str],
    new_files: dict[str, str],
) -> list[Finding]:
    """Compare *old_files* to *new_files* and return suspicious diff findings."""
    findings: list[Finding] = []

    added_file_names = set(new_files) - set(old_files)
    removed_file_names = set(old_files) - set(new_files)

    # Binary/extension files added
    binary_added = [f for f in added_file_names if Path(f).suffix in _BINARY_EXTENSIONS]
    if binary_added:
        findings.append(
            Finding(
                severity=Severity.HIGH,
                title="New binary extension files added",
                description=(
                    f"New binary files were added in this version: {', '.join(binary_added)}. "
                    "Binary extensions are harder to audit and can hide malicious code."
                ),
                layer="static",
                package=package_name,
                version=version,
                evidence={"files": binary_added},
                confidence=0.7,
                category="version_diff",
                remediation="Review these binary files or build from source.",
            )
        )

    # Aggregate diff across changed .py files
    all_added: list[str] = []
    # New Python files
    new_py = [f for f in added_file_names if f.endswith(".py")]
    for fname in new_py:
        all_added.extend(new_files[fname].splitlines())

    # Changed Python files
    changed = set(old_files) & set(new_files)
    for fname in changed:
        if not fname.endswith(".py"):
            continue
        diff = difflib.unified_diff(
            old_files[fname].splitlines(),
            new_files[fname].splitlines(),
            lineterm="",
        )
        all_added.extend(_added_lines("\n".join(diff)))

    new_imp = _new_imports(all_added)
    if new_imp:
        findings.append(
            Finding(
                severity=Severity.MEDIUM,
                title="Suspicious new imports in updated version",
                description=(
                    f"The following potentially risky modules were newly imported "
                    f"in this version: {', '.join(sorted(new_imp))}."
                ),
                layer="static",
                package=package_name,
                version=version,
                evidence={"new_imports": sorted(new_imp)},
                confidence=0.65,
                category="version_diff",
                remediation="Audit the full diff between versions before upgrading.",
            )
        )

    cred_refs = _new_credential_refs(all_added)
    if cred_refs:
        findings.append(
            Finding(
                severity=Severity.HIGH,
                title="Credential path references added in updated version",
                description=(
                    f"New code in this version references credential-related paths: "
                    f"{', '.join(sorted(cred_refs))}."
                ),
                layer="static",
                package=package_name,
                version=version,
                evidence={"credential_refs": sorted(cred_refs)},
                confidence=0.8,
                category="version_diff",
                remediation="Review the new code before upgrading.",
            )
        )

    # Check if setup.py or pyproject.toml was modified
    build_files_changed = [
        f
        for f in (set(old_files) & set(new_files))
        if Path(f).name in {"setup.py", "setup.cfg", "pyproject.toml"}
        and old_files[f] != new_files[f]
    ]
    if build_files_changed:
        findings.append(
            Finding(
                severity=Severity.HIGH,
                title="Build/install configuration changed",
                description=(
                    f"The following build files were modified: {', '.join(build_files_changed)}. "
                    "Changes to install hooks can execute arbitrary code during `pip install`."
                ),
                layer="static",
                package=package_name,
                version=version,
                evidence={"changed_files": build_files_changed},
                confidence=0.8,
                category="version_diff",
                remediation="Review the exact changes in these files before installing.",
            )
        )

    return findings
