"""
Agent d'analyse de vulnérabilités des dépendances (DVA) utilisant l'API OSV
"""

import asyncio
import json
import os
import re
from typing import Dict
from typing import List
from typing import Optional
from typing import Tuple

from src.core.config import AnalysisConfig
from src.core.logger import log_print
from src.models.data_models import DependencyVulnerability

# Vérifier la disponibilité de requests
try:
    pass

    REQUESTS_AVAILABLE = True
except ImportError:
    REQUESTS_AVAILABLE = False

# Vérifier la disponibilité de yaml
try:
    pass

    YAML_AVAILABLE = True
except ImportError:
    YAML_AVAILABLE = False


class DependencyVulnerabilityAgent:
    """Agent d'analyse de vulnérabilités des dépendances (DVA) utilisant l'API OSV"""

    OSV_API_BASE = "https://api.osv.dev/v1"

    def __init__(self, config: AnalysisConfig, root_dir: str):
        self.config = config
        self.root_dir = root_dir
        self.osv_available = REQUESTS_AVAILABLE

    def _parse_package_json(self, file_path: str) -> Dict[str, str]:
        """Parse package.json pour extraire les dépendances"""
        try:
            with open(file_path, "r", encoding="utf-8") as f:
                data = json.load(f)

            deps = {}
            if "dependencies" in data:
                deps.update(data["dependencies"])
            if "devDependencies" in data:
                deps.update(data["devDependencies"])

            return deps
        except Exception as e:
            print(f"⚠️  Erreur lors du parsing de {file_path}: {e}")
            return {}

    def _parse_pubspec_yaml(self, file_path: str) -> Dict[str, str]:
        """Parse pubspec.yaml pour extraire les dépendances"""
        if not YAML_AVAILABLE:
            return {}

        try:
            import yaml

            with open(file_path, "r", encoding="utf-8") as f:
                data = yaml.safe_load(f)

            deps = {}
            if "dependencies" in data:
                for name, version in data["dependencies"].items():
                    if isinstance(version, str):
                        # Nettoyer la version (enlever ^, ~, etc.)
                        clean_version = re.sub(r"[\^~>=<]", "", version).strip()
                        deps[name] = clean_version
            if "dev_dependencies" in data:
                for name, version in data["dev_dependencies"].items():
                    if isinstance(version, str):
                        clean_version = re.sub(r"[\^~>=<]", "", version).strip()
                        deps[name] = clean_version

            return deps
        except Exception as e:
            print(f"⚠️  Erreur lors du parsing de {file_path}: {e}")
            return {}

    def _parse_requirements_txt(self, file_path: str) -> Dict[str, str]:
        """Parse requirements.txt pour extraire les dépendances"""
        deps = {}
        try:
            with open(file_path, "r", encoding="utf-8") as f:
                for line in f:
                    line = line.strip()
                    if not line or line.startswith("#"):
                        continue
                    # Format: package==version ou package>=version, etc.
                    # Supporte aussi: package @ git+https://...
                    if "@" in line:
                        continue  # Ignorer les dépendances git

                    match = re.match(
                        r"([a-zA-Z0-9_-]+(?:\[[^\]]+\])?)(?:[=<>!]+)([0-9.]+[a-zA-Z0-9._-]*)", line
                    )
                    if match:
                        package_name = match.group(1).split("[")[0]  # Enlever les extras [dev]
                        deps[package_name] = match.group(2)
        except Exception as e:
            print(f"⚠️  Erreur lors du parsing de {file_path}: {e}")
        return deps

    def _parse_pyproject_toml(self, file_path: str) -> Dict[str, str]:
        """Parse pyproject.toml pour extraire les dépendances"""
        deps = {}
        try:
            # Parser TOML basique (pour une implémentation complète, utiliser tomli ou tomllib)
            with open(file_path, "r", encoding="utf-8") as f:
                content = f.read()

            # Recherche simple dans [project.dependencies] ou [tool.poetry.dependencies]
            in_deps_section = False
            for line in content.split("\n"):
                line = line.strip()
                if "[project.dependencies]" in line or "[tool.poetry.dependencies]" in line:
                    in_deps_section = True
                    continue
                if line.startswith("[") and in_deps_section:
                    break

                if in_deps_section and "=" in line:
                    match = re.match(r'([a-zA-Z0-9_-]+)\s*=\s*["\']([^"\']+)["\']', line)
                    if match:
                        package_name = match.group(1)
                        version = match.group(2)
                        # Nettoyer la version
                        clean_version = re.sub(r"[\^~>=<]", "", version).strip()
                        deps[package_name] = clean_version
        except Exception as e:
            print(f"⚠️  Erreur lors du parsing de {file_path}: {e}")
        return deps

    def _normalize_version(self, version: str) -> str:
        """Normalise une version pour l'API OSV"""
        # Enlever les préfixes comme ^, ~, >=, etc.
        version = re.sub(r"^[\^~>=<]+", "", version)
        # Enlever les suffixes comme -alpha, -beta, etc. pour simplifier
        version = re.sub(r"[-_][a-zA-Z]+.*$", "", version)
        return version.strip()

    def _get_ecosystem(self, package_manager: str) -> Optional[str]:
        """Mappe le gestionnaire de paquets vers l'écosystème OSV"""
        ecosystem_map = {
            "npm": "npm",
            "dart": "Pub",
            "python": "PyPI",
            "pip": "PyPI",
        }
        return ecosystem_map.get(package_manager)

    async def _query_osv_api(self, package_name: str, version: str, ecosystem: str) -> List[Dict]:
        """Interroge l'API OSV pour une vulnérabilité spécifique"""
        if not self.osv_available:
            return []

        try:
            import requests

            # Utiliser l'endpoint QueryVulnerabilities
            url = f"{self.OSV_API_BASE}/query"

            payload = {
                "version": version,
                "package": {"name": package_name, "ecosystem": ecosystem},
            }

            response = await asyncio.to_thread(
                requests.post,
                url,
                json=payload,
                headers={"Content-Type": "application/json"},
                timeout=10,
            )

            if response.status_code == 200:
                data = response.json()
                return data.get("vulns", [])
            else:
                print(f"⚠️  Erreur API OSV pour {package_name}@{version}: {response.status_code}")
                return []

        except Exception as e:
            print(f"⚠️  Erreur lors de la requête OSV pour {package_name}: {e}")
            return []

    async def _check_package_vulnerabilities(
        self, package_name: str, version: str, ecosystem: str, file_path: str
    ) -> List[DependencyVulnerability]:
        """Vérifie les vulnérabilités d'un package spécifique"""
        vulnerabilities = []

        # Normaliser la version
        normalized_version = self._normalize_version(version)

        # Interroger l'API OSV
        vulns = await self._query_osv_api(package_name, normalized_version, ecosystem)

        for vuln in vulns:
            # Extraire les informations de la vulnérabilité
            vuln_id = vuln.get("id", "UNKNOWN")
            summary = vuln.get("summary", "Vulnérabilité non spécifiée")

            # Déterminer la sévérité
            severity = "medium"  # Par défaut
            if "database_specific" in vuln:
                db_specific = vuln["database_specific"]
                if "severity" in db_specific:
                    severity_str = str(db_specific["severity"]).upper()
                    if "CRITICAL" in severity_str:
                        severity = "critical"
                    elif "HIGH" in severity_str:
                        severity = "high"
                    elif "LOW" in severity_str:
                        severity = "low"

            # Extraire les versions affectées
            affected_versions = "Toutes les versions"
            fixed_version = None

            if "affected" in vuln and len(vuln["affected"]) > 0:
                affected = vuln["affected"][0]
                if "ranges" in affected:
                    for range_item in affected["ranges"]:
                        if "events" in range_item:
                            events = range_item["events"]
                            if "fixed" in events:
                                fixed_version = events["fixed"]
                            if "introduced" in events:
                                affected_versions = f"Depuis {events['introduced']}"

            # Extraire CVE si disponible
            cve_id = None
            if "aliases" in vuln:
                for alias in vuln["aliases"]:
                    if alias.startswith("CVE-"):
                        cve_id = alias
                        break

            vulnerabilities.append(
                DependencyVulnerability(
                    package_name=package_name,
                    version=version,
                    vulnerability_id=vuln_id,
                    severity=severity,
                    description=summary,
                    affected_versions=affected_versions,
                    fixed_version=fixed_version,
                    cve_id=cve_id,
                )
            )

        return vulnerabilities

    def _find_dependency_files(self) -> List[Tuple[str, str]]:
        """Trouve tous les fichiers de dépendances"""
        dependency_files = []

        excluded_dirs = {
            ".git",
            "node_modules",
            ".venv",
            "venv",
            "build",
            "dist",
            ".dart_tool",
            "__pycache__",
            ".pytest_cache",
        }

        for root, dirs, files in os.walk(self.root_dir):
            # Exclure les dossiers
            dirs[:] = [d for d in dirs if d not in excluded_dirs and not d.startswith(".")]

            for file in files:
                file_path = os.path.join(root, file)
                rel_path = os.path.relpath(file_path, self.root_dir)

                if file == "package.json":
                    dependency_files.append((rel_path, "npm"))
                elif file == "pubspec.yaml":
                    dependency_files.append((rel_path, "dart"))
                elif file == "requirements.txt":
                    dependency_files.append((rel_path, "python"))
                elif file == "pyproject.toml":
                    dependency_files.append((rel_path, "python"))

        return dependency_files

    async def analyze_dependencies(self) -> List[DependencyVulnerability]:
        """Analyse toutes les dépendances pour les vulnérabilités via l'API OSV"""
        vulnerabilities = []

        if not self.config.enable_dependency_vulnerability:
            return vulnerabilities

        if not self.osv_available:
            log_print(
                "⚠️  L'analyse des vulnérabilités nécessite 'requests'. Installez avec: pip install requests"
            )
            return vulnerabilities

        dependency_files = self._find_dependency_files()

        if not dependency_files:
            log_print("📦 Aucun fichier de dépendances trouvé")
            return vulnerabilities

        log_print(f"📦 Analyse de {len(dependency_files)} fichier(s) de dépendances...")

        total_packages = 0
        for file_path, package_manager in dependency_files:
            full_path = os.path.join(self.root_dir, file_path)

            deps = {}
            if package_manager == "npm":
                deps = self._parse_package_json(full_path)
            elif package_manager == "dart":
                deps = self._parse_pubspec_yaml(full_path)
            elif package_manager == "python":
                if file_path.endswith("requirements.txt"):
                    deps = self._parse_requirements_txt(full_path)
                elif file_path.endswith("pyproject.toml"):
                    deps = self._parse_pyproject_toml(full_path)

            if not deps:
                continue

            ecosystem = self._get_ecosystem(package_manager)
            if not ecosystem:
                log_print(f"⚠️  Écosystème non supporté: {package_manager}")
                continue

            log_print(f"  🔍 Analyse de {len(deps)} dépendance(s) dans {file_path}...")
            total_packages += len(deps)

            # Vérifier chaque dépendance
            for package_name, version in deps.items():
                # Ignorer les dépendances avec des URLs (git, etc.)
                if "@" in version or "://" in version:
                    continue

                package_vulns = await self._check_package_vulnerabilities(
                    package_name, version, ecosystem, file_path
                )

                if package_vulns:
                    vulnerabilities.extend(package_vulns)
                    for vuln in package_vulns:
                        severity_emoji = {
                            "critical": "🔴",
                            "high": "🟠",
                            "medium": "🟡",
                            "low": "🟢",
                        }.get(vuln.severity, "⚪")
                        log_print(
                            f"    {severity_emoji} {package_name}@{version}: {vuln.vulnerability_id} ({vuln.severity})"
                        )

                # Petite pause pour éviter de surcharger l'API
                await asyncio.sleep(0.2)

        log_print(
            f"✅ Analyse terminée: {total_packages} package(s) vérifié(s), {len(vulnerabilities)} vulnérabilité(s) trouvée(s)"
        )

        return vulnerabilities
