"""AP016: Insecure Dependencies Detection."""

from collections.abc import Iterator
from pathlib import Path

from apiposture.core.models.endpoint import Endpoint
from apiposture.core.models.enums import Severity
from apiposture.core.models.finding import Finding

from apiposture_pro.rules.base import ProSecurityRule

# Known vulnerable package patterns (simplified - in production would use a CVE database)
VULNERABLE_PATTERNS = {
    "django": {
        "<2.2.28": "Multiple security vulnerabilities including SQL injection",
        "<3.2.18": "Security vulnerabilities in older versions",
    },
    "flask": {
        "<2.3.0": "Security vulnerabilities in older versions",
    },
    "requests": {
        "<2.31.0": "Proxy authentication header leak vulnerability",
    },
    "pyyaml": {
        "<6.0": "Arbitrary code execution via unsafe yaml.load()",
    },
    "pillow": {
        "<9.3.0": "Multiple image processing vulnerabilities",
    },
    "cryptography": {
        "<41.0.0": "Security vulnerabilities in older versions",
    },
    "urllib3": {
        "<1.26.18": "Cookie leakage and request smuggling vulnerabilities",
    },
    "jinja2": {
        "<3.1.0": "Template injection vulnerabilities",
    },
    "sqlalchemy": {
        "<1.4.46": "SQL injection vulnerabilities in specific use cases",
    },
}

# Known insecure packages that should never be used
INSECURE_PACKAGES = {
    "pickle": "Use JSON or safer serialization formats instead",
    "eval": "Never use eval() with user input",
    "md5": "MD5 is cryptographically broken, use SHA256 or better",
}


class AP016InsecureDependencies(ProSecurityRule):
    """
    AP016: Insecure Dependencies Detection.

    Scans dependency files for:
    - Known vulnerable package versions
    - Deprecated/insecure packages
    - Missing security updates
    """

    # Track if we've already scanned the project
    _scanned_projects: set[Path] = set()

    @property
    def rule_id(self) -> str:
        return "AP016"

    @property
    def name(self) -> str:
        return "Insecure Dependencies"

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

    @property
    def description(self) -> str:
        return (
            "Detects vulnerable or insecure dependencies in project requirements"
        )

    def evaluate(self, endpoint: Endpoint) -> Iterator[Finding]:
        """Scan project dependencies for known vulnerabilities."""
        # Get project root (scan once per project)
        project_root = endpoint.file_path.parent
        while project_root.parent != project_root:
            if (project_root / "requirements.txt").exists():
                break
            if (project_root / "pyproject.toml").exists():
                break
            if (project_root / "setup.py").exists():
                break
            project_root = project_root.parent

        # Only scan once per project
        if project_root in self._scanned_projects:
            return

        self._scanned_projects.add(project_root)

        # Check requirements.txt
        requirements_file = project_root / "requirements.txt"
        if requirements_file.exists():
            yield from self._scan_requirements(requirements_file, endpoint)

        # Check Pipfile
        pipfile = project_root / "Pipfile"
        if pipfile.exists():
            yield from self._scan_pipfile(pipfile, endpoint)

        # Check pyproject.toml
        pyproject = project_root / "pyproject.toml"
        if pyproject.exists():
            yield from self._scan_pyproject(pyproject, endpoint)

    def _scan_requirements(self, req_file: Path, endpoint: Endpoint) -> Iterator[Finding]:
        """Scan requirements.txt for vulnerabilities."""
        try:
            content = req_file.read_text(encoding="utf-8", errors="ignore")
        except Exception:
            return

        for line_num, line in enumerate(content.split("\n"), start=1):
            line = line.strip()

            # Skip comments and empty lines
            if not line or line.startswith("#"):
                continue

            # Parse package and version
            # Format: package==version or package>=version
            if "==" in line:
                parts = line.split("==")
                if len(parts) == 2:
                    package = parts[0].strip().lower()
                    version = parts[1].strip().split(";")[0].strip()  # Remove extras

                    # Check for known vulnerabilities
                    if package in VULNERABLE_PATTERNS:
                        for vuln_version, description in VULNERABLE_PATTERNS[package].items():
                            if vuln_version.startswith("<"):
                                # Simple version comparison (in production would use packaging.version)
                                max_version = vuln_version[1:]
                                if version < max_version:
                                    yield self.create_finding(
                                        endpoint,
                                        message=(
                                            f"Vulnerable dependency: {package}=={version} in "
                                            f"{req_file}:{line_num}. {description}"
                                        ),
                                        recommendation=(
                                            f"Update {package} to {max_version} or later. "
                                            "Run 'pip install --upgrade {package}' and test thoroughly. "
                                            "Check release notes for breaking changes."
                                        ),
                                        severity=Severity.HIGH,
                                    )

            # Check for insecure packages
            package_name = line.split("==")[0].split(">=")[0].split("<=")[0].strip().lower()
            if package_name in INSECURE_PACKAGES:
                yield self.create_finding(
                    endpoint,
                    message=(
                        f"Insecure package '{package_name}' found in {req_file}:{line_num}. "
                        f"{INSECURE_PACKAGES[package_name]}"
                    ),
                    recommendation=(
                        f"Remove {package_name} and use secure alternatives. "
                        "Refer to OWASP recommendations for secure package selection."
                    ),
                    severity=Severity.CRITICAL,
                )

    def _scan_pipfile(self, pipfile: Path, endpoint: Endpoint) -> Iterator[Finding]:
        """Scan Pipfile for vulnerabilities."""
        # Basic Pipfile scanning (would need toml parser for full support)
        try:
            content = pipfile.read_text(encoding="utf-8", errors="ignore")
        except Exception:
            return

        # Simple pattern matching for packages
        for line_num, line in enumerate(content.split("\n"), start=1):
            line = line.strip()
            if not line or line.startswith("#"):
                continue

            # Look for package definitions: package = "==version"
            if "=" in line and '"' in line:
                parts = line.split("=")
                if len(parts) >= 2:
                    package = parts[0].strip().lower()
                    if package in VULNERABLE_PATTERNS:
                        yield self.create_finding(
                            endpoint,
                            message=(
                                f"Check dependency {package} in {pipfile}:{line_num} "
                                "for known vulnerabilities"
                            ),
                            recommendation=(
                                f"Review {package} version for security updates. "
                                "Run 'pipenv update' to upgrade dependencies."
                            ),
                            severity=Severity.MEDIUM,
                        )

    def _scan_pyproject(self, pyproject_file: Path, endpoint: Endpoint) -> Iterator[Finding]:
        """Scan pyproject.toml for vulnerabilities."""
        # Basic pyproject.toml scanning
        try:
            content = pyproject_file.read_text(encoding="utf-8", errors="ignore")
        except Exception:
            return

        in_dependencies = False
        for line_num, line in enumerate(content.split("\n"), start=1):
            line = line.strip()

            # Track if we're in dependencies section
            if line.startswith("[tool.poetry.dependencies]") or line.startswith("dependencies"):
                in_dependencies = True
                continue
            elif line.startswith("["):
                in_dependencies = False

            if in_dependencies and "=" in line:
                parts = line.split("=")
                if len(parts) >= 2:
                    package = parts[0].strip().lower()
                    if package in VULNERABLE_PATTERNS:
                        yield self.create_finding(
                            endpoint,
                            message=(
                                f"Check dependency {package} in {pyproject_file}:{line_num} "
                                "for known vulnerabilities"
                            ),
                            recommendation=(
                                f"Review {package} version for security updates. "
                                "Run 'poetry update' or update version constraints in pyproject.toml."
                            ),
                            severity=Severity.MEDIUM,
                        )
