import os
import shutil
import uuid
from pathlib import Path
from typing import Optional
import structlog

from app.configs import settings

logger = structlog.get_logger()


class SandboxManager:
    """
    Sandbox 管理器
    每个任务在独立的 sandbox 目录中运行，永远不直接修改原项目。
    """

    def __init__(self, base_dir: Optional[str] = None):
        self.base_dir = Path(base_dir or settings.sandbox_base_dir).resolve()
        self.base_dir.mkdir(parents=True, exist_ok=True)

    def create_sandbox(self, task_id: str, repo_path: str) -> str:
        """
        复制项目到 sandbox 目录

        Returns:
            sandbox_path: 独立的 sandbox 路径
        """
        sandbox_name = f"task_{task_id[:8]}"
        sandbox_path = self.base_dir / sandbox_name

        if sandbox_path.exists():
            shutil.rmtree(sandbox_path)

        src = Path(repo_path).resolve()
        if not src.exists():
            raise FileNotFoundError(f"Repo path not found: {repo_path}")

        shutil.copytree(src, sandbox_path, symlinks=True, ignore=shutil.ignore_patterns(
            ".git", "__pycache__", "*.pyc", ".venv", "venv", "node_modules", ".pytest_cache"
        ))

        logger.info("Sandbox created", task_id=task_id, path=str(sandbox_path))
        return str(sandbox_path)

    def cleanup_sandbox(self, sandbox_path: str) -> bool:
        """清理 sandbox 目录"""
        path = Path(sandbox_path)
        if path.exists() and self._is_under_base(path):
            shutil.rmtree(path)
            logger.info("Sandbox cleaned", path=str(sandbox_path))
            return True
        return False

    def get_diff(self, sandbox_path: str, repo_path: str) -> dict:
        """
        比较 sandbox 和原始项目，生成 diff 摘要

        Returns:
            dict with added, modified, deleted file lists and line counts
        """
        sandbox = Path(sandbox_path)
        original = Path(repo_path)
        result = {"added": [], "modified": [], "deleted": [], "diff_lines": {"added": 0, "removed": 0}}

        try:
            import subprocess
            proc = subprocess.run(
                ["git", "diff", "--stat", "--no-index", str(original), str(sandbox)],
                capture_output=True,
                text=True,
            )
            result["raw_diff"] = proc.stdout

            # parse added/removed lines from summary line e.g. "5 files changed, +124, -28"
            for line in proc.stdout.splitlines():
                if "insertion" in line or "deletion" in line:
                    parts = line.split(",")
                    for p in parts:
                        p = p.strip()
                        if "insertion" in p:
                            result["diff_lines"]["added"] = int(p.split()[0])
                        elif "deletion" in p:
                            result["diff_lines"]["removed"] = int(p.split()[0])

        except Exception as e:
            logger.warning("git diff failed", error=str(e))

        # File-level comparison
        for f in sandbox.rglob("*"):
            if f.is_file():
                rel = f.relative_to(sandbox)
                orig = original / rel
                if not orig.exists():
                    result["added"].append(str(rel))
                elif f.read_bytes() != orig.read_bytes():
                    result["modified"].append(str(rel))

        for f in original.rglob("*"):
            if f.is_file():
                rel = f.relative_to(original)
                if not (sandbox / rel).exists():
                    result["deleted"].append(str(rel))

        return result

    def _is_under_base(self, path: Path) -> bool:
        try:
            path.relative_to(self.base_dir)
            return True
        except ValueError:
            return False

    def list_sandboxes(self) -> list:
        return [str(p) for p in self.base_dir.iterdir() if p.is_dir()]
