import asyncio
import fnmatch
import logging
import re
import shutil
import sys
from pathlib import Path
from typing import Any, Dict, List, Optional

from nexus_agent.core.workspace_manager import workspace_manager, IGNORED_DIRS, IGNORED_FILES

logger = logging.getLogger(__name__)

# Windows 대소문자 무시를 위한 소문자 변환 집합
IGNORED_DIRS_LOWER = {d.lower() for d in IGNORED_DIRS}

WORKSPACE_TOOL_NAMES = {
    "workspace_read_file",
    "workspace_write_file",
    "workspace_edit_file",
    "workspace_list_dir",
    "workspace_glob",
    "workspace_grep",
    "workspace_bash",
}

# bash 안전장치: 시스템 파괴 명령 패턴 (절대 실행 금지)
DANGEROUS_PATTERNS = [
    r"rm\s+-[^\s]*r[^\s]*f\s+/\s*$",  # rm -rf /
    r"rm\s+-[^\s]*f[^\s]*r\s+/\s*$",  # rm -fr /
    r":\(\)\s*\{\s*:\|:\s*&\s*\}\s*;",  # fork bomb
    r"dd\s+if=/dev/(zero|random|urandom)\s+of=/dev/[hs]d",  # dd overwrite disk
    r"mkfs\.",  # format filesystem
    r">\s*/dev/[hs]d",  # overwrite disk
    r"curl\s+.*\|\s*(ba)?sh",  # pipe-to-shell
    r"wget\s+.*\|\s*(ba)?sh",  # pipe-to-shell
    r"chmod\s+-R\s+777\s+/\s*$",  # chmod 777 /
    r"chown\s+-R\s+.*\s+/\s*$",  # chown /
    # Windows 전용 위험 패턴
    r"(?i)del\s+/[sS]\s+/[qQ]\s+[A-Za-z]:\\",  # del /s /q C:\
    r"(?i)format\s+[A-Za-z]:",  # format C:
    r"(?i)diskpart",  # 디스크 파티션 조작
    r"(?i)cipher\s+/[wW]:",  # 보안 와이프
    r"(?i)reg\s+delete\s+HK(LM|CU|CR)",  # 레지스트리 삭제
    r"(?i)Remove-Item\s+-Recurse\s+-Force\s+[A-Za-z]:\\",  # PowerShell rm -rf
    r"(?i)Clear-Disk",  # PowerShell 디스크 초기화
    r"(?i)Format-Volume",  # PowerShell 볼륨 포맷
]

# bash 안전장치: 제한된 작업 패턴 (파일 삭제, 이동 등 파괴적 작업)
RESTRICTED_PATTERNS = [
    (r"\brm\b", "파일 삭제(rm)"),
    (r"\brmdir\b", "디렉토리 삭제(rmdir)"),
    (r"\bunlink\b", "파일 삭제(unlink)"),
    (r"\bshred\b", "파일 완전 삭제(shred)"),
    (r"\bmv\b", "파일 이동/이름변경(mv)"),
    (r"\btrash\b", "파일 휴지통 이동(trash)"),
    (r"\bgit\s+clean\b", "git 파일 정리(git clean)"),
    (r"\bgit\s+reset\s+--hard\b", "git 강제 리셋(git reset --hard)"),
    # Windows 전용 제한 패턴
    (r"(?i)\bdel\b", "파일 삭제(del)"),
    (r"(?i)\berase\b", "파일 삭제(erase)"),
    (r"(?i)\bmove\b", "파일 이동(move)"),
    (r"(?i)Remove-Item", "파일 삭제(Remove-Item)"),
    (r"(?i)Move-Item", "파일 이동(Move-Item)"),
]


def _is_dangerous_command(command: str) -> Optional[str]:
    """시스템 파괴 명령 검사"""
    for pattern in DANGEROUS_PATTERNS:
        if re.search(pattern, command):
            return f"Blocked: dangerous command pattern detected ({pattern})"
    return None


def _is_restricted_command(command: str) -> Optional[str]:
    """제한된 작업 검사 — 명확한 한글 메시지 반환"""
    for pattern, desc in RESTRICTED_PATTERNS:
        if re.search(pattern, command):
            return (
                f"🚫 보안상 실행할 수 없습니다.\n\n"
                f"차단된 작업: {desc}\n"
                f"요청한 명령: {command}\n\n"
                f"워크스페이스에서는 파일 삭제, 이동 등 파괴적인 작업이 제한됩니다. "
                f"파일 읽기, 쓰기, 편집, 검색 작업만 가능합니다."
            )
    return None


def get_workspace_tools() -> List[Dict[str, Any]]:
    """활성 워크스페이스가 있을 때만 도구 목록 반환"""
    active = workspace_manager.get_active()
    if not active:
        return []

    return [
        {
            "type": "function",
            "function": {
                "name": "workspace_read_file",
                "description": "워크스페이스의 파일을 읽습니다. 라인 번호가 포함된 내용을 반환합니다.",
                "parameters": {
                    "type": "object",
                    "properties": {
                        "path": {"type": "string", "description": "워크스페이스 루트 기준 상대 경로"},
                        "offset": {"type": "integer", "description": "시작 라인 (0-based)", "default": 0},
                        "limit": {"type": "integer", "description": "읽을 라인 수 (생략 시 전체)"},
                    },
                    "required": ["path"],
                },
            },
        },
        {
            "type": "function",
            "function": {
                "name": "workspace_write_file",
                "description": "워크스페이스에 파일을 생성하거나 덮어씁니다.",
                "parameters": {
                    "type": "object",
                    "properties": {
                        "path": {"type": "string", "description": "워크스페이스 루트 기준 상대 경로"},
                        "content": {"type": "string", "description": "파일 내용"},
                    },
                    "required": ["path", "content"],
                },
            },
        },
        {
            "type": "function",
            "function": {
                "name": "workspace_edit_file",
                "description": "파일에서 특정 문자열을 찾아 교체합니다. old_string이 유일해야 합니다.",
                "parameters": {
                    "type": "object",
                    "properties": {
                        "path": {"type": "string", "description": "워크스페이스 루트 기준 상대 경로"},
                        "old_string": {"type": "string", "description": "교체할 기존 문자열"},
                        "new_string": {"type": "string", "description": "새 문자열"},
                        "replace_all": {"type": "boolean", "description": "모든 매치 교체 여부", "default": False},
                    },
                    "required": ["path", "old_string", "new_string"],
                },
            },
        },
        {
            "type": "function",
            "function": {
                "name": "workspace_list_dir",
                "description": "워크스페이스의 디렉토리 목록을 반환합니다.",
                "parameters": {
                    "type": "object",
                    "properties": {
                        "path": {"type": "string", "description": "워크스페이스 루트 기준 상대 경로 (기본: 루트)", "default": "."},
                        "recursive": {"type": "boolean", "description": "재귀 탐색 여부", "default": False},
                        "max_depth": {"type": "integer", "description": "최대 깊이 (recursive 시)", "default": 3},
                    },
                },
            },
        },
        {
            "type": "function",
            "function": {
                "name": "workspace_glob",
                "description": "워크스페이스에서 파일 패턴(glob)으로 검색합니다.",
                "parameters": {
                    "type": "object",
                    "properties": {
                        "pattern": {"type": "string", "description": "glob 패턴 (예: **/*.py, src/**/*.ts)"},
                        "limit": {"type": "integer", "description": "최대 결과 수", "default": 100},
                    },
                    "required": ["pattern"],
                },
            },
        },
        {
            "type": "function",
            "function": {
                "name": "workspace_grep",
                "description": "워크스페이스에서 파일 내용을 정규식으로 검색합니다.",
                "parameters": {
                    "type": "object",
                    "properties": {
                        "pattern": {"type": "string", "description": "검색 정규식 패턴"},
                        "path": {"type": "string", "description": "검색 시작 경로 (워크스페이스 루트 기준 상대 경로, 기본: 루트)", "default": "."},
                        "glob_filter": {"type": "string", "description": "파일 필터 (예: *.py)"},
                        "case_insensitive": {"type": "boolean", "description": "대소문자 무시", "default": False},
                        "context": {"type": "integer", "description": "매치 전후 표시할 라인 수", "default": 0},
                        "limit": {"type": "integer", "description": "최대 매치 수", "default": 50},
                    },
                    "required": ["pattern"],
                },
            },
        },
        {
            "type": "function",
            "function": {
                "name": "workspace_bash",
                "description": "워크스페이스에서 셸 명령을 실행합니다.",
                "parameters": {
                    "type": "object",
                    "properties": {
                        "command": {"type": "string", "description": "실행할 셸 명령"},
                        "cwd": {"type": "string", "description": "작업 디렉토리 (워크스페이스 루트 기준)"},
                        "timeout": {"type": "integer", "description": "타임아웃 (초, 기본 30, 최대 120)", "default": 30},
                    },
                    "required": ["command"],
                },
            },
        },
    ]


def get_workspace_system_prompt() -> Optional[str]:
    """활성 워크스페이스 정보를 시스템 프롬프트로"""
    active = workspace_manager.get_active()
    if not active:
        return None

    return (
        f"## Active Workspace\n"
        f"- Name: {active.name}\n"
        f"- Path: {active.path}\n"
        f"- Description: {active.description}\n\n"
        f"You have access to workspace tools (workspace_read_file, workspace_write_file, "
        f"workspace_edit_file, workspace_list_dir, workspace_glob, workspace_grep, workspace_bash) "
        f"to interact with files in this workspace. "
        f"All file paths are relative to the workspace root.\n"
        f"**스킬 스크립트에 워크스페이스 파일을 전달할 때는 반드시 절대 경로를 사용하세요.** "
        f"절대 경로 = Workspace Path + 상대 경로 (예: {active.path}/파일명)\n\n"
        f"## Workspace Security Rules\n"
        f"다음 작업은 워크스페이스에서 **금지**되어 있습니다:\n"
        f"- 파일 삭제 (rm, rmdir, unlink, shred)\n"
        f"- 파일 이동/이름변경 (mv)\n"
        f"- git clean, git reset --hard 등 파괴적 git 명령\n"
        f"- 시스템 파괴 명령 (rm -rf /, fork bomb, dd, mkfs 등)\n\n"
        f"사용자가 금지된 작업을 요청하면 도구를 호출하지 말고 즉시 다음과 같이 응답하세요:\n"
        f"\"🚫 보안상 해당 작업을 실행할 수 없습니다. 워크스페이스에서는 파일 삭제, 이동 등 "
        f"파괴적인 작업이 제한됩니다.\"\n"
        f"그 후 허용된 대안이 있다면 안내하세요."
    )


async def handle_workspace_tool_call(name: str, args: Dict[str, Any]) -> str:
    active = workspace_manager.get_active()
    if not active:
        return "Error: No active workspace. Please activate a workspace first."

    try:
        if name == "workspace_read_file":
            return _handle_read_file(active.id, args)
        elif name == "workspace_write_file":
            return _handle_write_file(active.id, args)
        elif name == "workspace_edit_file":
            return _handle_edit_file(active.id, args)
        elif name == "workspace_list_dir":
            return _handle_list_dir(active.id, args)
        elif name == "workspace_glob":
            return _handle_glob(active.id, args)
        elif name == "workspace_grep":
            return _handle_grep(active.id, args)
        elif name == "workspace_bash":
            return await _handle_bash(active.id, args)
        else:
            return f"Error: Unknown workspace tool '{name}'"
    except ValueError as e:
        return f"Error: {e}"
    except Exception as e:
        logger.error(f"Workspace tool error ({name}): {e}")
        return f"Error: {e}"


def _handle_read_file(ws_id: str, args: Dict[str, Any]) -> str:
    fc = workspace_manager.read_file(
        ws_id,
        args["path"],
        offset=args.get("offset", 0),
        limit=args.get("limit"),
    )
    lines = fc.content.split("\n")
    offset = fc.offset
    numbered = "\n".join(
        f"{offset + i + 1:>6}\t{line}" for i, line in enumerate(lines)
    )
    header = f"File: {fc.path} ({fc.total_lines} lines total)"
    if fc.offset > 0 or fc.limit:
        header += f" [showing lines {fc.offset + 1}-{fc.offset + len(lines)}]"
    return f"{header}\n{numbered}"


def _handle_write_file(ws_id: str, args: Dict[str, Any]) -> str:
    return workspace_manager.write_file(ws_id, args["path"], args["content"])


def _handle_edit_file(ws_id: str, args: Dict[str, Any]) -> str:
    from nexus_agent.models.workspace import EditFileRequest
    req = EditFileRequest(
        path=args["path"],
        old_string=args["old_string"],
        new_string=args["new_string"],
        replace_all=args.get("replace_all", False),
    )
    return workspace_manager.edit_file(ws_id, req)


def _handle_list_dir(ws_id: str, args: Dict[str, Any]) -> str:
    path = args.get("path", ".")
    recursive = args.get("recursive", False)
    max_depth = args.get("max_depth", 3) if recursive else 1

    nodes = workspace_manager.get_file_tree(ws_id, path, max_depth)
    lines: List[str] = []
    _format_tree(nodes, lines, "")

    if not lines:
        return f"Directory '{path}' is empty."
    return "\n".join(lines)


def _format_tree(nodes: List[Any], lines: List[str], prefix: str) -> None:
    for node in nodes:
        if node.type == "dir":
            lines.append(f"{prefix}{node.name}/")
            if node.children:
                _format_tree(node.children, lines, prefix + "  ")
        else:
            size_str = _format_size(node.size)
            lines.append(f"{prefix}{node.name}  ({size_str})")


def _format_size(size: int) -> str:
    if size < 1024:
        return f"{size}B"
    elif size < 1024 * 1024:
        return f"{size / 1024:.1f}KB"
    else:
        return f"{size / (1024 * 1024):.1f}MB"


def _handle_glob(ws_id: str, args: Dict[str, Any]) -> str:
    pattern = args["pattern"]
    limit = args.get("limit", 100)
    ws = workspace_manager.get_workspace(ws_id)
    if not ws:
        return "Error: Workspace not found"

    root = Path(ws.path)
    matches: List[str] = []
    for p in root.rglob(pattern):
        if any(part.lower() in IGNORED_DIRS_LOWER for part in p.relative_to(root).parts):
            continue
        if p.name in IGNORED_FILES:
            continue
        rel = p.relative_to(root).as_posix()
        matches.append(rel)
        if len(matches) >= limit:
            break

    if not matches:
        return f"No files matching '{pattern}'"
    result = f"Found {len(matches)} file(s) matching '{pattern}':\n"
    result += "\n".join(matches)
    return result


def _handle_grep(ws_id: str, args: Dict[str, Any]) -> str:
    pattern = args["pattern"]
    search_path = args.get("path", ".")
    glob_filter = args.get("glob_filter")
    case_insensitive = args.get("case_insensitive", False)
    context_lines = args.get("context", 0)
    limit = args.get("limit", 50)

    ws = workspace_manager.get_workspace(ws_id)
    if not ws:
        return "Error: Workspace not found"

    root = Path(ws.path).resolve()
    target = (root / search_path).resolve()
    if not target.is_relative_to(root):
        return "Error: Path traversal detected"

    flags = re.IGNORECASE if case_insensitive else 0
    try:
        regex = re.compile(pattern, flags)
    except re.error as e:
        return f"Error: Invalid regex pattern: {e}"

    results: List[str] = []
    match_count = 0

    # Iterate files
    if target.is_file():
        files = [target]
    else:
        files_iter = target.rglob("*") if not glob_filter else target.rglob(glob_filter)
        files = []
        for f in files_iter:
            if not f.is_file():
                continue
            if any(part.lower() in IGNORED_DIRS_LOWER for part in f.relative_to(root).parts):
                continue
            if f.name in IGNORED_FILES:
                continue
            files.append(f)

    for file_path in sorted(files):
        try:
            content = file_path.read_text(encoding="utf-8", errors="replace")
        except (OSError, UnicodeDecodeError):
            continue

        file_lines = content.split("\n")
        file_matches: List[str] = []

        for i, line in enumerate(file_lines):
            if regex.search(line):
                rel = file_path.relative_to(root).as_posix()
                start = max(0, i - context_lines)
                end = min(len(file_lines), i + context_lines + 1)
                for j in range(start, end):
                    marker = ">" if j == i else " "
                    file_matches.append(f"  {marker} {j + 1:>4}\t{file_lines[j]}")
                match_count += 1
                if match_count >= limit:
                    break

        if file_matches:
            rel = file_path.relative_to(root).as_posix()
            results.append(f"\n{rel}:")
            results.extend(file_matches)

        if match_count >= limit:
            break

    if not results:
        return f"No matches for pattern '{pattern}'"
    header = f"Found {match_count} match(es) for '{pattern}':"
    return header + "\n".join(results)


async def _handle_bash(ws_id: str, args: Dict[str, Any]) -> str:
    command = args["command"]
    cwd_rel = args.get("cwd")
    timeout = min(args.get("timeout", 30), 120)

    # 안전장치: 위험 명령 검사
    danger = _is_dangerous_command(command)
    if danger:
        return danger

    # 안전장치: 제한 명령 검사
    restricted = _is_restricted_command(command)
    if restricted:
        return restricted

    ws = workspace_manager.get_workspace(ws_id)
    if not ws:
        return "Error: Workspace not found"

    root = Path(ws.path).resolve()

    if cwd_rel:
        cwd = (root / cwd_rel).resolve()
        if not cwd.is_relative_to(root):
            return "Error: cwd must be within workspace"
        if not cwd.is_dir():
            return f"Error: Directory not found: {cwd_rel}"
    else:
        cwd = root

    try:
        # 플랫폼별 셸 감지
        if sys.platform == "win32":
            shell_exe = shutil.which("pwsh") or shutil.which("powershell") or shutil.which("cmd")
        else:
            shell_exe = shutil.which("sh") or shutil.which("bash")

        shell_kwargs: dict = {}
        if shell_exe:
            shell_kwargs["executable"] = shell_exe

        proc = await asyncio.create_subprocess_shell(
            command,
            stdout=asyncio.subprocess.PIPE,
            stderr=asyncio.subprocess.PIPE,
            cwd=str(cwd),
            **shell_kwargs,
        )
        stdout, stderr = await asyncio.wait_for(proc.communicate(), timeout=timeout)

        output_parts: List[str] = []
        stdout_text = stdout.decode(errors="replace")
        stderr_text = stderr.decode(errors="replace")

        if stdout_text:
            # 출력 절삭
            if len(stdout_text) > 30000:
                stdout_text = stdout_text[:30000] + "\n... (output truncated at 30000 chars)"
            output_parts.append(stdout_text)

        if stderr_text:
            if len(stderr_text) > 5000:
                stderr_text = stderr_text[:5000] + "\n... (stderr truncated)"
            output_parts.append(f"[stderr]\n{stderr_text}")

        result = "\n".join(output_parts) if output_parts else "(no output)"
        exit_info = f"[exit code: {proc.returncode}]"
        return f"{result}\n{exit_info}"

    except asyncio.TimeoutError:
        return f"Error: Command timed out after {timeout}s"
    except Exception as e:
        return f"Error executing command: {e}"
