import subprocess
from pathlib import Path

SHELL_TOOL = {
    "type": "function",
    "function": {
        "name": "shell",
        "description": "Run a read-only shell command inside the repository. Terminal outputs longer than 1024 characters will be truncated. Prefer concise commands that produce manageable output lengths.",
        "parameters": {
            "type": "object",
            "properties": {"cmd": {"type": "string"}},
            "required": ["cmd"]
        }
    }
}

PATCH_TOOL = {
    "type":"function",
    "function":{
        "name":"apply_patch",
        "description": "Apply a single, exact, literal SEARCH/REPLACE operation to a file. The SEARCH string is matched exactly as given, without regex or pattern matching. Ensure the search string uniquely matches exactly one location in the file, including whitespace and indentation.",
        "parameters":{
            "type":"object",
            "properties":{
                "search":{"type":"string"},
                "replace":{"type":"string"},
                "file":{"type":"string"}
            },
            "required":["search","replace","file"]
        }
    }
}

CREATE_TOOL = {
    "type":"function",
    "function":{
        "name":"create",
        "description":"Create a new file and write the given content to it",
        "parameters":{
            "type":"object",
            "properties":{
                "path":{"type":"string"},
                "content":{"type":"string"}
            },
            "required":["path","content"]
        }
    }
}

def shell(args: dict, repo_root: Path, timeout: int = 4) -> tuple[bool, str]:
    """Run a shell command using rbash with timeout and output limits.
    Returns (success, output) tuple."""
    if "cmd" not in args:
        return (False, "[invalid `shell` arguments]")
    
    cmd = args["cmd"]

    try:
        res = subprocess.run(
            ["bash", "-rc", cmd], cwd=repo_root,
            timeout=timeout, text=True, errors="ignore", stderr=subprocess.STDOUT, stdout=subprocess.PIPE
        )
    except Exception as e:
        return (False, f"[shell failed: {e}]")

    out = res.stdout or ""

    if res.returncode != 0:
        return (False, f"[command failed: exit {res.returncode}]\n{out or '[no output]'}")
    
    return (True, out.strip() or "[no output]")

def apply_patch(args: dict, repo_root: Path) -> tuple[bool, str]:
    """
    Apply a literal search/replace to one file.
    Returns (success, message) tuple.
    """
    if "search" not in args or "replace" not in args or "file" not in args:
        return (False, "[invalid `apply_patch` arguments]")
    
    search, replace, file = args["search"], args["replace"], args["file"]

    try:
        target = repo_root / file

        if not target.exists():
            return (False, f"[file {target} not found]")
        
        text = target.read_text()
        search_count = text.count(search)

        if search_count == 0:
            return (False, "[search string not found]")
        
        if search_count > 1:
            return (False, f"[ambiguous search string: {search_count} occurrences]")
        
        new_text = text.replace(search, replace, 1)
        target.write_text(new_text)
        return (True, "[patch applied successfully]")

    except Exception as e:
        return (False, f"[failed to apply patch: {e}]")

def create(path: str, content: str) -> tuple[bool, str]:
    """Create a new file and write the given content to it.
    Returns (success, message) tuple."""
    path = Path(path)
    if path.exists():
        return (False, f"[file {path} already exists]")
    
    try:
        path.touch()
        path.write_text(content)
        return (True, f"[created {path}]")
    
    except Exception as e:
        return (False, f"[failed to create {path}: {e}]")
