"""Layer 3 — Runtime sandbox using Docker with honeypot credential injection."""
from __future__ import annotations

import json
import logging
import os
import tempfile
import uuid
from pathlib import Path
from typing import Any

from pyguard.layers.base import BaseLayer
from pyguard.models import Finding, PackageInfo, Severity

logger = logging.getLogger(__name__)

_SANDBOX_IMAGE = "python:3.12-slim"
_INSTALL_TIMEOUT = 120  # seconds
_MEM_LIMIT = "256m"

# Honeypot markers embedded in fake credential files
_SSH_MARKER = "PYGUARD_HONEYPOT_SSH_KEY"
_AWS_KEY_ID = "AKIAPYGUARDHONEYPOT01"
_AWS_SECRET = "pyguard/honeypot/do-not-use"
_KUBE_MARKER = "pyguard-honeypot-cluster"
_OPENAI_MARKER = "sk-pyguard-honeypot"

# Credential file paths inside the container home directory
_HONEYPOT_FILES: dict[str, str] = {
    ".ssh/id_rsa": (
        f"-----BEGIN RSA PRIVATE KEY-----\n{_SSH_MARKER}\n-----END RSA PRIVATE KEY-----\n"
    ),
    ".aws/credentials": (
        f"[default]\naws_access_key_id = {_AWS_KEY_ID}\n"
        f"aws_secret_access_key = {_AWS_SECRET}/{uuid.uuid4()}\n"
    ),
    ".kube/config": (
        f"apiVersion: v1\nclusters:\n- cluster:\n    server: https://127.0.0.1:6443\n"
        f"  name: {_KUBE_MARKER}\n"
    ),
    ".env": f"OPENAI_API_KEY={_OPENAI_MARKER}-{uuid.uuid4()}\n",
}

_ALL_MARKERS = [_SSH_MARKER, _AWS_KEY_ID, _AWS_SECRET, _KUBE_MARKER, _OPENAI_MARKER]


def _write_honeypot(home_dir: Path) -> None:
    for rel_path, content in _HONEYPOT_FILES.items():
        dest = home_dir / rel_path
        dest.parent.mkdir(parents=True, exist_ok=True)
        dest.write_text(content, encoding="utf-8")


def _check_output_for_leaks(output: str) -> list[str]:
    return [m for m in _ALL_MARKERS if m in output]


class SandboxLayer(BaseLayer):
    name = "sandbox"

    def __init__(self) -> None:
        self._docker_available: bool | None = None

    def _check_docker(self) -> bool:
        if self._docker_available is not None:
            return self._docker_available
        try:
            import docker  # noqa: PLC0415

            client = docker.from_env()
            client.ping()
            self._docker_available = True
        except Exception:  # noqa: BLE001
            self._docker_available = False
        return self._docker_available

    async def analyze(self, package: PackageInfo) -> list[Finding]:
        if not self._check_docker():
            logger.warning(
                "Sandbox layer disabled: Docker is not available on this system."
            )
            return []

        if package.version in ("latest", "unknown", ""):
            logger.info("Sandbox skipped: no pinned version for '%s'", package.name)
            return []

        try:
            return await self._run_sandbox(package)
        except Exception as exc:  # noqa: BLE001
            logger.warning("Sandbox layer failed for %s: %s", package.name, exc)
            return [
                Finding(
                    severity=Severity.INFO,
                    title="Sandbox execution failed",
                    description=f"Could not complete sandbox analysis: {exc}",
                    layer=self.name,
                    package=package.name,
                    version=package.version,
                    evidence={"error": str(exc)},
                    confidence=1.0,
                    category="sandbox_error",
                )
            ]

    async def _run_sandbox(self, package: PackageInfo) -> list[Finding]:
        import docker  # noqa: PLC0415

        findings: list[Finding] = []
        with tempfile.TemporaryDirectory(prefix="pyguard_sandbox_") as tmp:
            tmp_path = Path(tmp)
            home_dir = tmp_path / "home" / "testuser"
            home_dir.mkdir(parents=True)
            _write_honeypot(home_dir)

            monitor_dir = tmp_path / "monitor"
            monitor_dir.mkdir()

            client = docker.from_env()

            # Build install command with strace to log file/network access
            install_cmd = (
                f"pip install --no-deps --quiet "
                f"{package.name}=={package.version} 2>&1"
            )
            full_cmd = f"sh -c '{install_cmd}'"

            volumes: dict[str, dict[str, str]] = {
                str(home_dir): {"bind": "/root", "mode": "rw"},
                str(monitor_dir): {"bind": "/monitor", "mode": "rw"},
            }

            try:
                container = client.containers.run(
                    _SANDBOX_IMAGE,
                    full_cmd,
                    remove=True,
                    detach=False,
                    mem_limit=_MEM_LIMIT,
                    network_mode="bridge",
                    security_opt=["no-new-privileges"],
                    cap_drop=["ALL"],
                    volumes=volumes,
                    timeout=_INSTALL_TIMEOUT,
                    stdout=True,
                    stderr=True,
                )
            except Exception as exc:  # noqa: BLE001
                raise RuntimeError(f"Docker run failed: {exc}") from exc

            output: str = (
                container.decode("utf-8", errors="replace")
                if isinstance(container, bytes)
                else str(container)
            )

            # Check for honeypot credential leaks in stdout
            leaked = _check_output_for_leaks(output)
            if leaked:
                findings.append(
                    Finding(
                        severity=Severity.CRITICAL,
                        title="Honeypot credential exfiltration confirmed",
                        description=(
                            f"Package '{package.name}' accessed and leaked honeypot "
                            f"credential markers during installation: {', '.join(leaked)}. "
                            "This is definitive proof of credential theft behaviour."
                        ),
                        layer=self.name,
                        package=package.name,
                        version=package.version,
                        evidence={"leaked_markers": leaked, "output_snippet": output[:500]},
                        confidence=0.99,
                        category="credential_theft",
                        remediation="Do NOT install this package. Report to PyPI security.",
                    )
                )

            # Check if honeypot files were modified/read (via mtime change)
            findings.extend(self._check_honeypot_tampering(package, home_dir))

            # Check for network-related output patterns
            findings.extend(self._check_network_patterns(package, output))

        return findings

    def _check_honeypot_tampering(
        self, package: PackageInfo, home_dir: Path
    ) -> list[Finding]:
        findings: list[Finding] = []
        for rel_path in _HONEYPOT_FILES:
            fpath = home_dir / rel_path
            if not fpath.exists():
                continue
            original = _HONEYPOT_FILES[rel_path]
            try:
                current = fpath.read_text(encoding="utf-8")
            except OSError:
                continue
            if current != original:
                findings.append(
                    Finding(
                        severity=Severity.CRITICAL,
                        title=f"Honeypot file modified: {rel_path}",
                        description=(
                            f"The honeypot credential file '~/{rel_path}' was modified "
                            f"during installation of '{package.name}'. "
                            "This indicates the package accessed and tampered with credentials."
                        ),
                        layer=self.name,
                        package=package.name,
                        version=package.version,
                        evidence={"honeypot_file": rel_path},
                        confidence=0.95,
                        category="credential_theft",
                        remediation="Do NOT install this package. Report to PyPI security.",
                    )
                )
        return findings

    def _check_network_patterns(
        self, package: PackageInfo, output: str
    ) -> list[Finding]:
        findings: list[Finding] = []
        lower = output.lower()
        # Patterns that suggest outbound HTTP during install
        network_hints = [
            ("curl ", "curl command executed during install"),
            ("wget ", "wget command executed during install"),
            ("requests.post", "requests.post call detected in install output"),
            ("urllib.request", "urllib.request call detected in install output"),
        ]
        for hint, label in network_hints:
            if hint in lower:
                findings.append(
                    Finding(
                        severity=Severity.HIGH,
                        title=f"Network activity during install: {label}",
                        description=(
                            f"Installation of '{package.name}' produced output suggesting "
                            f"network activity: '{label}'. Packages should not make "
                            "outbound connections during installation."
                        ),
                        layer=self.name,
                        package=package.name,
                        version=package.version,
                        evidence={"hint": hint, "output_snippet": output[:300]},
                        confidence=0.7,
                        category="exfiltration",
                        remediation="Review the package's setup.py and install hooks.",
                    )
                )
        return findings
