import subprocess
import os
from pathlib import Path
import structlog

from app.state import EngineeringState

logger = structlog.get_logger()

# 支持的测试框架及命令
TEST_RUNNERS = {
    "pytest": {"detect": ["pytest.ini", "setup.cfg", "pyproject.toml", "conftest.py", "tests/", "test_*.py"],
                "cmd": ["python", "-m", "pytest", "--tb=short", "-v", "--no-header"]},
    "npm_test": {"detect": ["package.json"], "cmd": ["npm", "test", "--", "--watchAll=false"]},
    "pnpm_test": {"detect": ["pnpm-lock.yaml"], "cmd": ["pnpm", "test"]},
    "go_test": {"detect": ["go.mod"], "cmd": ["go", "test", "./...", "-v"]},
    "cargo_test": {"detect": ["Cargo.toml"], "cmd": ["cargo", "test"]},
}


class TesterAgent:
    """
    Tester Agent
    职责：自动检测测试框架、执行测试、生成覆盖率报告
    """

    def __init__(self):
        self.logger = structlog.get_logger().bind(agent="tester")

    def run_tests(self, state: EngineeringState) -> dict:
        """自动检测并运行测试"""
        test_path = state.get("sandbox_path") or state.get("repo_path", ".")
        self.logger.info("Tester starting", path=test_path)

        runner_name, runner = self._detect_runner(test_path)
        if not runner:
            return {
                "passed": 0,
                "failed": 0,
                "skipped": 0,
                "coverage": "N/A",
                "runner": "none",
                "output": "No test runner detected",
                "success": False,
            }

        self.logger.info("Detected test runner", runner=runner_name)
        return self._execute_tests(runner_name, runner["cmd"], test_path)

    def _detect_runner(self, path: str):
        root = Path(path)
        for name, config in TEST_RUNNERS.items():
            for pattern in config["detect"]:
                if pattern.endswith("/"):
                    if (root / pattern.rstrip("/")).is_dir():
                        return name, config
                elif pattern.startswith("*"):
                    if list(root.glob(pattern)):
                        return name, config
                else:
                    if (root / pattern).exists():
                        return name, config
        return None, None

    def _execute_tests(self, runner_name: str, cmd: list, path: str) -> dict:
        env = {**os.environ}

        # pytest: add coverage
        if runner_name == "pytest":
            cmd = cmd + ["--cov=.", "--cov-report=term-missing"]

        try:
            result = subprocess.run(
                cmd,
                capture_output=True,
                text=True,
                cwd=path,
                timeout=120,
                env=env,
            )
            output = result.stdout + result.stderr
            return {
                "runner": runner_name,
                "success": result.returncode == 0,
                "passed": self._parse_passed(output, runner_name),
                "failed": self._parse_failed(output, runner_name),
                "skipped": self._parse_skipped(output, runner_name),
                "coverage": self._parse_coverage(output),
                "output": output[:5000],
                "exit_code": result.returncode,
            }
        except subprocess.TimeoutExpired:
            return {"runner": runner_name, "success": False, "output": "Test timeout (120s)",
                    "passed": 0, "failed": 0, "skipped": 0, "coverage": "N/A", "exit_code": -1}
        except FileNotFoundError:
            return {"runner": runner_name, "success": False,
                    "output": f"{cmd[0]} not found", "passed": 0, "failed": 0, "skipped": 0,
                    "coverage": "N/A", "exit_code": -1}

    def _parse_passed(self, output: str, runner: str) -> int:
        import re
        if runner == "pytest":
            m = re.search(r"(\d+) passed", output)
            return int(m.group(1)) if m else 0
        return 0

    def _parse_failed(self, output: str, runner: str) -> int:
        import re
        if runner == "pytest":
            m = re.search(r"(\d+) failed", output)
            return int(m.group(1)) if m else 0
        return 0

    def _parse_skipped(self, output: str, runner: str) -> int:
        import re
        if runner == "pytest":
            m = re.search(r"(\d+) skipped", output)
            return int(m.group(1)) if m else 0
        return 0

    def _parse_coverage(self, output: str) -> str:
        import re
        m = re.search(r"TOTAL\s+\d+\s+\d+\s+(\d+)%", output)
        return f"{m.group(1)}%" if m else "N/A"
