"""Isolated per-attempt workspaces and sandboxed pytest execution."""

from __future__ import annotations

import re
import subprocess
import sys
from dataclasses import dataclass
from pathlib import Path

from .challenges import Challenge


@dataclass
class TestOutcome:
    __test__ = False  # keep pytest from collecting this as a test class
    passed: int
    failed: int
    errored: bool
    output: str

    @property
    def total(self) -> int:
        return self.passed + self.failed

    @property
    def green(self) -> bool:
        return not self.errored and self.failed == 0 and self.passed > 0


def setup(root: Path, challenge: Challenge, files: dict[str, str] | None = None) -> Path:
    """Create a workspace seeded with starter files + tests (+ optional files)."""
    root.mkdir(parents=True, exist_ok=True)
    for rel, content in {**challenge.starter_files, **challenge.tests, **(files or {})}.items():
        write_file(root, rel, content)
    return root


def write_file(root: Path, rel: str, content: str) -> Path:
    dest = (root / rel).resolve()
    if not dest.is_relative_to(root.resolve()):
        raise ValueError(f"path escapes workspace: {rel}")
    dest.parent.mkdir(parents=True, exist_ok=True)
    dest.write_text(content)
    return dest


def run_tests(root: Path, timeout: int = 120) -> TestOutcome:
    try:
        proc = subprocess.run(
            [sys.executable, "-m", "pytest", "-q", "--tb=short", "-p", "no:cacheprovider"],
            cwd=root,
            capture_output=True,
            text=True,
            timeout=timeout,
        )
    except subprocess.TimeoutExpired as err:
        return TestOutcome(0, 0, True, f"pytest timed out after {timeout}s\n{err.stdout or ''}")
    output = proc.stdout + proc.stderr
    passed = _summary_count(output, "passed")
    failed = _summary_count(output, "failed") + _summary_count(output, "error")
    # Exit codes: 0 ok, 1 failures; anything else (2 usage, 3 internal, 4 no tests) is an error.
    errored = proc.returncode not in (0, 1)
    return TestOutcome(passed, failed, errored, output[-8000:])


def _summary_count(output: str, word: str) -> int:
    match = re.search(rf"(\d+) {word}", output)
    return int(match.group(1)) if match else 0
