from dataclasses import dataclass, field
from pathlib import Path

import yaml

from .domain import Agents, Limits, Policy


class CommandNotAllowed(ValueError):
    pass


#: Substrings never permitted in a configured command, whatever the CLIs themselves allow.
FORBIDDEN = ("git push", "rm -rf /", "curl ", "wget ", ">/dev/sda")


@dataclass(frozen=True)
class CommandPolicy:
    commands: dict[str, str]
    limits: Limits = field(default_factory=Limits)
    policy: Policy = field(default_factory=Policy)
    agents: Agents = field(default_factory=Agents)

    @classmethod
    def from_mapping(cls, value: dict) -> "CommandPolicy":
        commands = value.get("commands", {})
        if not isinstance(commands, dict) or not all(isinstance(k, str) and isinstance(v, str) for k, v in commands.items()):
            raise CommandNotAllowed("commands must be a string mapping")
        for command in commands.values():
            for token in FORBIDDEN:
                if token in command.lower():
                    raise CommandNotAllowed(f"{token.strip()!r} is forbidden")
        policy = Policy.model_validate(value.get("policy") or {})
        if policy.allow_push:
            raise CommandNotAllowed("allow_push is forbidden in this release")
        return cls(
            dict(commands),
            Limits.model_validate(value.get("limits") or {}),
            policy,
            Agents.model_validate(value.get("agents") or {}),
        )

    @classmethod
    def from_file(cls, path: str | Path) -> "CommandPolicy":
        return cls.from_mapping(yaml.safe_load(Path(path).read_text(encoding="utf-8")) or {})

    @classmethod
    def discover(cls, repo_path: str | Path) -> "CommandPolicy":
        """Explicit `.dual-agent.yml` wins; otherwise infer a test command, never guessing a risky one."""
        repo = Path(repo_path)
        explicit = repo / ".dual-agent.yml"
        if explicit.is_file():
            return cls.from_file(explicit)
        for marker, command in (("pyproject.toml", "python -m pytest"), ("package.json", "npm test"), ("Cargo.toml", "cargo test"), ("go.mod", "go test ./...")):
            if (repo / marker).is_file():
                return cls({"test": command})
        return cls({})

    def command_for(self, name: str) -> str:
        if name not in self.commands:
            raise CommandNotAllowed(f"Command {name!r} is not configured")
        return self.commands[name]
