"""Scanner de dependências vulneráveis: Python (pip-audit) e Node (npm audit).

Detecta automaticamente qual ecossistema está presente no repositório
(requirements.txt/pyproject.toml para Python, package.json para Node)
e roda a ferramenta apropriada.
"""

import json
import shutil
import subprocess
from dataclasses import dataclass
from pathlib import Path
from typing import List


@dataclass
class DependencyFinding:
    package: str
    installed_version: str
    vulnerability_id: str
    severity: str
    fix_version: str = ""
    ecosystem: str = "python"


def _tool_available(name: str) -> bool:
    return shutil.which(name) is not None


def scan_python(repo_path: str) -> List[DependencyFinding]:
    """Roda pip-audit se houver requirements.txt ou pyproject.toml."""
    repo = Path(repo_path)
    requirements_file = repo / "requirements.txt"
    pyproject_file = repo / "pyproject.toml"

    if not requirements_file.exists() and not pyproject_file.exists():
        return []

    if not _tool_available("pip-audit"):
        return []

    # requirements.txt precisa da flag -r; pyproject.toml é lido via
    # project_path posicional (pip-audit entende PEP 621 automaticamente).
    if requirements_file.exists():
        command = ["pip-audit", "-r", str(requirements_file), "--format", "json"]
    else:
        command = ["pip-audit", str(repo), "--format", "json"]

    result = subprocess.run(
        command,
        capture_output=True,
        text=True,
        timeout=180,
    )

    findings = []
    try:
        data = json.loads(result.stdout) if result.stdout.strip() else {"dependencies": []}
    except json.JSONDecodeError:
        return []

    for dep in data.get("dependencies", []):
        for vuln in dep.get("vulns", []):
            findings.append(
                DependencyFinding(
                    package=dep.get("name", "unknown"),
                    installed_version=dep.get("version", ""),
                    vulnerability_id=vuln.get("id", ""),
                    severity=vuln.get("severity", "unknown") or "unknown",
                    fix_version=", ".join(vuln.get("fix_versions", [])),
                    ecosystem="python",
                )
            )
    return findings


def scan_node(repo_path: str) -> List[DependencyFinding]:
    """Roda npm audit se houver package.json."""
    repo = Path(repo_path)
    if not (repo / "package.json").exists():
        return []

    if not _tool_available("npm"):
        return []

    result = subprocess.run(
        ["npm", "audit", "--json"],
        capture_output=True,
        text=True,
        timeout=180,
        cwd=repo_path,
    )

    findings = []
    try:
        data = json.loads(result.stdout) if result.stdout.strip() else {}
    except json.JSONDecodeError:
        return []

    vulnerabilities = data.get("vulnerabilities", {})
    for pkg_name, vuln_info in vulnerabilities.items():
        via = vuln_info.get("via", [])
        vuln_id = ""
        for v in via:
            if isinstance(v, dict):
                vuln_id = str(v.get("source", ""))
                break

        findings.append(
            DependencyFinding(
                package=pkg_name,
                installed_version=vuln_info.get("range", ""),
                vulnerability_id=vuln_id or "n/a",
                severity=vuln_info.get("severity", "unknown"),
                fix_version=str(vuln_info.get("fixAvailable", "")),
                ecosystem="node",
            )
        )
    return findings


def scan_repo(repo_path: str) -> List[DependencyFinding]:
    """Roda todos os scanners de dependência aplicáveis ao repositório."""
    findings = []
    findings.extend(scan_python(repo_path))
    findings.extend(scan_node(repo_path))
    return findings