"""
Terminal Service - bezpečné spouštění příkazů
"""
import json
import os
import shlex
import signal
import subprocess
import time
import uuid
from datetime import datetime
from pathlib import Path

# Whitelist povolených příkazů (první slovo)
ALLOWED_COMMANDS = {
    "ls", "cat", "head", "tail", "grep", "find", "wc", "pwd", "echo",
    "file", "tree", "du", "df", "stat", "which",
    "git",  # git bude dále validován
    "python3",  # python3 bude dále validován
    "pip", "pip3",
    "pytest",
}

# Zakázané vzory (regex) - vždy blokováno
FORBIDDEN_PATTERNS = [
    r"\brm\s+-rf\b",
    r"\bsudo\b",
    r"\bsu\b",
    r"\bchmod\s+777\b",
    r"\bcurl\b",
    r"\bwget\b",
    r"\bssh\b",
    r"\bscp\b",
    r"\bnc\b",
    r"\bncat\b",
    r"\bmkfs\b",
    r"\bdd\b",
    r"\bshutdown\b",
    r"\breboot\b",
    r"\bkill\b",
    r"\bpkill\b",
    r"\bmv\s+/",
    r"\bcp\s+.*\s+/etc",
    r">\s*/dev/",
    r">\s*/etc/",
    r"\|\s*bash\b",
    r"\|\s*sh\b",
    r"`.*`",
    r"\$\(.*\)",
]

# Zakázané git subcommandy (kromě bezpečných)
FORBIDDEN_GIT = {
    "push", "reset", "clean", "filter-branch", "rebase",
    "cherry-pick", "revert", "rm", "gc",
}


class TerminalService:
    """Servis pro bezpečné spouštění příkazů"""

    def __init__(self, audit_file: str = "data-lab/terminal_audit.jsonl"):
        self.audit_file = Path(audit_file)
        self.audit_file.parent.mkdir(parents=True, exist_ok=True)
        self.running: dict[str, subprocess.Popen] = {}

    def _validate_command(self, command: str) -> tuple:
        """Zkontroluje příkaz. Vrací (ok, chyba)."""
        command = command.strip()
        if not command:
            return False, "Prázdný příkaz"

        # Kontrola zakázaných vzorů
        import re
        for pattern in FORBIDDEN_PATTERNS:
            if re.search(pattern, command, re.IGNORECASE):
                return False, f"Zakázaný vzor: {pattern}"

        # Parsování
        try:
            parts = shlex.split(command)
        except ValueError as e:
            return False, f"Chyba v syntaxi: {e}"

        if not parts:
            return False, "Prázdný příkaz"

        # Kontrola whitelistu
        first = parts[0]
        if first not in ALLOWED_COMMANDS:
            return False, f"Příkaz '{first}' není v whitelistu"

        # Speciální validace pro git
        if first == "git":
            if len(parts) < 2:
                return False, "git vyžaduje subcommand"
            subcmd = parts[1]
            if subcmd in FORBIDDEN_GIT:
                return False, f"git {subcmd} je zakázán"
            # Povolené: status, log, diff, branch, show, rev-parse, add, commit, merge, worktree, checkout
            allowed_git = {
                "status", "log", "diff", "branch", "show",
                "rev-parse", "add", "commit", "merge",
                "worktree", "checkout", "stash", "ls-files",
            }
            if subcmd not in allowed_git:
                return False, f"git {subcmd} není povolen"

        # Speciální validace pro python3
        if first == "python3":
            # Povolené: --version, -m pytest, -m json.tool, konkrétní skripty v worktree
            if len(parts) >= 2:
                second = parts[1]
                if not (second == "--version" or second.startswith("-m") or second.endswith(".py")):
                    return False, f"python3 {second} není povoleno"

        return True, ""

    def _log_audit(self, entry: dict):
        """Zapíše záznam do audit logu"""
        with open(self.audit_file, "a", encoding="utf-8") as f:
            f.write(json.dumps(entry, ensure_ascii=False) + "\n")

    def execute(
        self,
        task_id: str,
        worktree_path: str,
        command: str,
        timeout: int = 30
    ) -> dict:
        """Spustí příkaz v worktree s timeoutem."""
        # Validace
        ok, error = self._validate_command(command)
        if not ok:
            result = {
                "success": False,
                "error": error,
                "command": command,
                "exit_code": -1,
                "stdout": "",
                "stderr": "",
                "duration_ms": 0,
                "execution_id": None,
            }
            self._log_audit({
                "timestamp": datetime.utcnow().isoformat(),
                "task_id": task_id,
                "command": command,
                "allowed": False,
                "error": error,
            })
            return result

        # Ověříme worktree
        wt = Path(worktree_path)
        if not wt.exists() or not wt.is_dir():
            return {
                "success": False,
                "error": f"Worktree neexistuje: {worktree_path}",
                "command": command,
                "exit_code": -1,
                "stdout": "",
                "stderr": "",
                "duration_ms": 0,
                "execution_id": None,
            }

        # Spustíme
        execution_id = str(uuid.uuid4())[:8]
        start = time.time()
        try:
            proc = subprocess.Popen(
                shlex.split(command), shell=False,
                cwd=str(wt),
                stdout=subprocess.PIPE,
                stderr=subprocess.PIPE,
                text=True,
                preexec_fn=os.setsid if hasattr(os, "setsid") else None,
            )
            self.running[execution_id] = proc

            try:
                stdout, stderr = proc.communicate(timeout=timeout)
                exit_code = proc.returncode
                timed_out = False
            except subprocess.TimeoutExpired:
                # Timeout – zabijeme proces
                try:
                    os.killpg(os.getpgid(proc.pid), signal.SIGTERM)
                except Exception:
                    proc.kill()
                stdout, stderr = proc.communicate()
                exit_code = -1
                timed_out = True

            duration_ms = int((time.time() - start) * 1000)
            self.running.pop(execution_id, None)

            result = {
                "success": exit_code == 0,
                "error": "Timeout" if timed_out else "",
                "command": command,
                "exit_code": exit_code,
                "stdout": stdout[:10000],  # limit
                "stderr": stderr[:10000],
                "duration_ms": duration_ms,
                "execution_id": execution_id,
                "timed_out": timed_out,
            }

            self._log_audit({
                "timestamp": datetime.utcnow().isoformat(),
                "task_id": task_id,
                "command": command,
                "allowed": True,
                "exit_code": exit_code,
                "duration_ms": duration_ms,
                "execution_id": execution_id,
                "timed_out": timed_out,
            })

            return result

        except Exception as e:
            duration_ms = int((time.time() - start) * 1000)
            self.running.pop(execution_id, None)
            self._log_audit({
                "timestamp": datetime.utcnow().isoformat(),
                "task_id": task_id,
                "command": command,
                "allowed": True,
                "error": str(e),
                "duration_ms": duration_ms,
            })
            return {
                "success": False,
                "error": str(e),
                "command": command,
                "exit_code": -1,
                "stdout": "",
                "stderr": "",
                "duration_ms": duration_ms,
                "execution_id": execution_id,
            }

    def stop(self, execution_id: str) -> dict:
        """Zastaví běžící příkaz"""
        proc = self.running.get(execution_id)
        if not proc:
            return {"stopped": False, "error": "Execution nenalezena"}

        try:
            if hasattr(os, "killpg"):
                os.killpg(os.getpgid(proc.pid), signal.SIGTERM)
            else:
                proc.terminate()
            time.sleep(0.5)
            if proc.poll() is None:
                proc.kill()
            self.running.pop(execution_id, None)
            return {"stopped": True, "execution_id": execution_id}
        except Exception as e:
            return {"stopped": False, "error": str(e)}

    def history(self, task_id: str, limit: int = 50) -> list[dict]:
        """Vrátí historii příkazů z auditu"""
        if not self.audit_file.exists():
            return []
        items = []
        with open(self.audit_file, encoding="utf-8") as f:
            for line in f:
                try:
                    entry = json.loads(line)
                    if entry.get("task_id") == task_id:
                        items.append(entry)
                except Exception:
                    continue
        return items[-limit:]
