"""
neurocli tools — agent action toolkit with undo snapshots
"""
import os
import shutil
import subprocess
import difflib
from pathlib import Path
from datetime import datetime
from .config import SNAPSHOTS_DIR, ensure_dirs, get_permission
from .context import is_sensitive
from .session import log_action, track


# ─── Snapshot / Undo ──────────────────────────────────────────────────────────

def _snapshot(file_path: str, session_id: int) -> str:
    """Save a snapshot of a file before editing. Returns snapshot path."""
    ensure_dirs()
    ts = datetime.now().strftime("%Y%m%d_%H%M%S_%f")
    safe_name = file_path.replace("/", "__").replace("\\", "__")
    snap_path = SNAPSHOTS_DIR / f"{session_id}_{ts}_{safe_name}"
    try:
        shutil.copy2(file_path, str(snap_path))
        return str(snap_path)
    except Exception:
        return ""


def undo_last(session_id: int) -> tuple[bool, str]:
    """Restore last snapshotted file. Returns (success, message)."""
    from .session import get_last_action
    action = get_last_action(session_id)
    if not action or not action.get("snapshot_path"):
        return False, "No action to undo."
    snap = action["snapshot_path"]
    target = action["target"]
    if not Path(snap).exists():
        return False, f"Snapshot not found: {snap}"
    try:
        shutil.copy2(snap, target)
        return True, f"Restored {target} from snapshot."
    except Exception as e:
        return False, f"Undo failed: {e}"


# ─── File Tools ───────────────────────────────────────────────────────────────

def read_file(path: str, cfg: dict) -> dict:
    if is_sensitive(path):
        return {"ok": False, "error": f"🔒 Blocked: {path} looks like a sensitive file (env/secrets)."}
    if not get_permission(cfg, "read"):
        return {"ok": False, "error": "Permission denied: read"}
    try:
        content = Path(path).read_text(errors="replace")
        track("files_read" if False else "ai_requests", 0)  # just access
        return {"ok": True, "content": content, "path": path}
    except FileNotFoundError:
        return {"ok": False, "error": f"File not found: {path}"}
    except Exception as e:
        return {"ok": False, "error": str(e)}


def write_file(path: str, content: str, cfg: dict, session_id: int, dry_run: bool = False) -> dict:
    if is_sensitive(path):
        return {"ok": False, "error": f"🔒 Blocked: will not write to {path} (sensitive file)."}
    if not get_permission(cfg, "write"):
        return {"ok": False, "error": "Permission denied: write (tier: read-only)"}

    # generate diff
    old_content = ""
    if Path(path).exists():
        try:
            old_content = Path(path).read_text(errors="replace")
        except Exception:
            pass
    diff = _make_diff(old_content, content, path)

    if dry_run or cfg.get("dry_run"):
        return {"ok": True, "dry_run": True, "diff": diff, "path": path}

    # snapshot before writing
    snap_path = _snapshot(path, session_id) if Path(path).exists() else ""

    try:
        Path(path).parent.mkdir(parents=True, exist_ok=True)
        Path(path).write_text(content)
        lines_written = len(content.splitlines())
        track("lines_written", lines_written)
        track("files_edited", 1, sub_key=path)
        log_action(session_id, "write_file", path, snap_path)
        return {"ok": True, "diff": diff, "path": path, "lines": lines_written}
    except Exception as e:
        return {"ok": False, "error": str(e)}


def list_files(directory: str = ".", cfg: dict = None) -> dict:
    try:
        from .context import get_file_tree
        tree = get_file_tree(directory)
        return {"ok": True, "tree": tree}
    except Exception as e:
        return {"ok": False, "error": str(e)}


def search_files(pattern: str, directory: str = ".", cfg: dict = None) -> dict:
    results = []
    try:
        for root, dirs, files in os.walk(directory):
            dirs[:] = [d for d in dirs if d not in {".git", "node_modules", "__pycache__"}]
            for file in files:
                fpath = os.path.join(root, file)
                try:
                    content = Path(fpath).read_text(errors="replace")
                    for i, line in enumerate(content.splitlines(), 1):
                        if pattern.lower() in line.lower():
                            results.append({"file": fpath, "line": i, "text": line.strip()})
                except Exception:
                    pass
        return {"ok": True, "results": results[:50]}  # cap at 50
    except Exception as e:
        return {"ok": False, "error": str(e)}


# ─── Shell Tool ───────────────────────────────────────────────────────────────

def run_command(cmd: str, cfg: dict, session_id: int, dry_run: bool = False) -> dict:
    if not get_permission(cfg, "exec"):
        return {
            "ok": False,
            "error": f"Permission denied: exec (tier: {cfg.get('permission_tier')}). Use --tier full-auto to allow."
        }
    if dry_run or cfg.get("dry_run"):
        return {"ok": True, "dry_run": True, "cmd": cmd}
    try:
        result = subprocess.run(
            cmd, shell=True, capture_output=True, text=True, timeout=30
        )
        track("commands_run")
        log_action(session_id, "run_command", cmd)
        return {
            "ok": True,
            "stdout": result.stdout,
            "stderr": result.stderr,
            "returncode": result.returncode,
        }
    except subprocess.TimeoutExpired:
        return {"ok": False, "error": "Command timed out (30s)"}
    except Exception as e:
        return {"ok": False, "error": str(e)}


# ─── Git Tool ─────────────────────────────────────────────────────────────────

def git_commit(message: str, cfg: dict, session_id: int, dry_run: bool = False) -> dict:
    if not get_permission(cfg, "exec"):
        return {"ok": False, "error": "Permission denied: exec required for git commit"}
    if dry_run or cfg.get("dry_run"):
        return {"ok": True, "dry_run": True, "message": message}
    try:
        subprocess.run(["git", "add", "-A"], check=True, capture_output=True)
        result = subprocess.run(
            ["git", "commit", "-m", message],
            capture_output=True, text=True
        )
        track("commits_made")
        log_action(session_id, "git_commit", message)
        return {
            "ok": result.returncode == 0,
            "output": result.stdout or result.stderr
        }
    except Exception as e:
        return {"ok": False, "error": str(e)}


# ─── Diff Helper ──────────────────────────────────────────────────────────────

def _make_diff(old: str, new: str, filename: str) -> str:
    diff = list(difflib.unified_diff(
        old.splitlines(keepends=True),
        new.splitlines(keepends=True),
        fromfile=f"a/{filename}",
        tofile=f"b/{filename}",
        lineterm=""
    ))
    return "".join(diff) if diff else "(no changes)"
