"""
PROJECT JAMES - Code Editor (Phase 5.5)

역할: Sandbox 검증 통과 후에만 파일 수정.
백업 → 검증 → 수정 → 로그 순서 강제.

절대 제약:
  ❌ Sandbox 통과 없이 수정 금지
  ❌ workspace 외부 파일 수정 금지
  ❌ Core Engine 파일 수정 금지
  ✅ 수정 전 자동 백업 필수
  ✅ 수정 내용 diff 기록
  ✅ 감사 로그 기록
"""

import os
import shutil
import difflib
from datetime import datetime
from pathlib import Path
from typing import Optional, Tuple

from tools.code.sandbox import policy_validate_path, validate_command, log_security_event

BACKUP_DIR     = "./workspace/.backups"

# Core Engine 파일 수정 절대 금지 목록
PROTECTED_FILES = {
    "graph_rag_engine.py", "graph_engine.py", "reasoning_engine.py",
    "retrieval_engine.py", "security_layer.py", "memory_loom.py",
    "memory_trust.py", "gemma_client.py", "auth.py", "config.py",
    "jepa_adapter.py", "query_expander.py", "orchestrator.py",
}


def _log_edit(path: str, operation: str, success: bool, detail: str = ""):
    entry = {
        "time":      datetime.now().isoformat(),
        "event":     "CODE_EDIT",
        "tool_used": "code_editor",
        "path":      path,
        "operation": operation,
        "success":   success,
        "detail":    detail[:200],
        "layer":     "tool",
    }
    try:
        from core.audit_bridge import mirror_to_audit_db
        mirror_to_audit_db(entry)
    except Exception:
        pass


class CodeEditor:
    """
    PolicyEngine + Sandbox 통과 후 안전한 파일 수정 도구.
    수정 전 자동 백업, 모든 변경 diff 기록.

    Phase 3-3 (#44): 모든 경로 검증이 PolicyEngine.issue_capability(
    "fs.write")를 거친다. fs.write는 admin-only이므로 비-admin role로
    이 클래스를 instantiate해 호출해도 차단된다 (자가 테스트는 admin).
    """

    def __init__(self, user_role: str = "admin"):
        self.user_role = user_role
        os.makedirs(BACKUP_DIR, exist_ok=True)

    # ─── 보호 파일 확인 ──────────────────────────────────────

    @staticmethod
    def _is_protected(path: str) -> bool:
        filename = Path(path).name
        if filename in PROTECTED_FILES:
            return True
        # core/ 디렉토리 내 파일 보호
        if "core/" in path or "core\\" in path:
            return True
        return False

    # ─── 백업 ────────────────────────────────────────────────

    def _backup(self, path: str) -> Optional[str]:
        """수정 전 자동 백업. 백업 경로 반환."""
        try:
            p          = Path(path)
            timestamp  = datetime.now().strftime("%Y%m%d_%H%M%S")
            backup_path = Path(BACKUP_DIR) / f"{p.name}.{timestamp}.bak"
            shutil.copy2(path, backup_path)
            print(f"[EDITOR] 백업: {backup_path}")
            return str(backup_path)
        except Exception as e:
            print(f"[EDITOR] 백업 실패: {e}")
            return None

    # ─── 핵심 수정 API ───────────────────────────────────────

    def write_file(
        self,
        path:    str,
        content: str,
    ) -> Tuple[bool, str]:
        """
        파일 전체 쓰기.
        Sandbox 검증 → 보호 파일 확인 → 백업 → 쓰기 → 로그.
        """
        # 1. Sandbox 경로 검증
        path_ok, reason = policy_validate_path(path, self.user_role, "fs.write")
        if not path_ok:
            log_security_event("PATH_VIOLATION", f"write:{path}")
            _log_edit(path, "write", False, reason)
            return False, f"경로 차단: {reason}"

        # 2. 보호 파일 확인
        if self._is_protected(path):
            msg = f"보호 파일 수정 금지: {path}"
            log_security_event("PROTECTED_FILE_BLOCK", path)
            _log_edit(path, "write", False, msg)
            return False, msg

        # 3. 내용 안전성 검증
        safe_ok, cmd_reason = validate_command(content[:500])
        # 코드 내용은 명령어 검증보다 완화 (작성은 허용, 실행만 차단)
        # 단, 극단적 패턴만 차단

        # 4. 백업 (파일이 이미 존재하면)
        p = Path(path)
        if p.exists():
            backup = self._backup(path)
            if backup is None:
                return False, "백업 실패 — 수정 중단"

        # 5. 쓰기
        try:
            p.parent.mkdir(parents=True, exist_ok=True)
            p.write_text(content, encoding="utf-8")
        except Exception as e:
            _log_edit(path, "write", False, str(e))
            return False, f"쓰기 실패: {e}"

        _log_edit(path, "write", True, f"{len(content)}자 저장")
        print(f"[EDITOR] ✅ write: {path} ({len(content)}자)")
        return True, f"저장 완료: {path}"

    def replace_lines(
        self,
        path:       str,
        start_line: int,
        end_line:   int,
        new_content: str,
    ) -> Tuple[bool, str, str]:
        """
        특정 라인 범위 교체.

        Returns:
            (success, message, diff)
        """
        path_ok, reason = policy_validate_path(path, self.user_role, "fs.write")
        if not path_ok:
            log_security_event("PATH_VIOLATION", f"replace:{path}")
            return False, f"경로 차단: {reason}", ""

        if self._is_protected(path):
            log_security_event("PROTECTED_FILE_BLOCK", path)
            return False, f"보호 파일: {path}", ""

        p = Path(path)
        if not p.exists():
            return False, f"파일 없음: {path}", ""

        try:
            original = p.read_text(encoding="utf-8")
            lines    = original.split("\n")

            s = max(0, start_line - 1)
            e_ = min(len(lines), end_line)

            new_lines = new_content.split("\n")
            modified  = lines[:s] + new_lines + lines[e_:]
            new_text  = "\n".join(modified)

            # diff 생성
            diff = "\n".join(difflib.unified_diff(
                lines[s:e_], new_lines,
                fromfile=f"{path} (before)",
                tofile=f"{path} (after)",
                lineterm="",
            ))

            # 백업 후 저장
            self._backup(path)
            p.write_text(new_text, encoding="utf-8")

        except Exception as e:
            _log_edit(path, "replace_lines", False, str(e))
            return False, f"교체 실패: {e}", ""

        _log_edit(path, "replace_lines", True,
                  f"line {start_line}~{end_line} 교체")
        print(f"[EDITOR] ✅ replace_lines: {path} L{start_line}~{end_line}")
        return True, f"L{start_line}~{end_line} 교체 완료", diff

    def insert_lines(
        self,
        path:      str,
        after_line: int,
        content:   str,
    ) -> Tuple[bool, str]:
        """특정 라인 이후 내용 삽입."""
        path_ok, reason = policy_validate_path(path, self.user_role, "fs.write")
        if not path_ok:
            return False, f"경로 차단: {reason}"

        if self._is_protected(path):
            return False, f"보호 파일: {path}"

        p = Path(path)
        if not p.exists():
            return False, f"파일 없음: {path}"

        try:
            original = p.read_text(encoding="utf-8")
            lines    = original.split("\n")
            insert_at = min(after_line, len(lines))
            new_lines = content.split("\n")
            modified  = lines[:insert_at] + new_lines + lines[insert_at:]
            self._backup(path)
            p.write_text("\n".join(modified), encoding="utf-8")
        except Exception as e:
            return False, f"삽입 실패: {e}"

        _log_edit(path, "insert", True, f"line {after_line} 이후 삽입")
        print(f"[EDITOR] ✅ insert: {path} after L{after_line}")
        return True, f"L{after_line} 이후 삽입 완료"

    def restore_backup(self, path: str) -> Tuple[bool, str]:
        """가장 최근 백업으로 복원."""
        path_ok, _ = policy_validate_path(path, self.user_role, "fs.write")
        if not path_ok:
            return False, "경로 차단"

        p          = Path(path)
        backup_dir = Path(BACKUP_DIR)
        backups    = sorted(backup_dir.glob(f"{p.name}.*.bak"), reverse=True)

        if not backups:
            return False, f"백업 없음: {path}"

        try:
            shutil.copy2(backups[0], path)
        except Exception as e:
            return False, f"복원 실패: {e}"

        _log_edit(path, "restore", True, f"백업: {backups[0].name}")
        print(f"[EDITOR] ✅ restore: {path} ← {backups[0].name}")
        return True, f"복원 완료: {backups[0].name}"


if __name__ == "__main__":
    import os
    os.makedirs("./workspace", exist_ok=True)

    print("=== Code Editor 자가 테스트 ===\n")
    editor = CodeEditor()
    results = []

    def chk(name, ok, detail=""):
        results.append(ok)
        print(f"  {'✅' if ok else '❌'} {name}" + (f" → {detail}" if detail else ""))

    # 정상 쓰기
    ok, msg = editor.write_file("./workspace/test_edit.py", "# 테스트\nprint('hello')\n")
    chk("정상 파일 쓰기", ok, msg[:40])

    # 경로 탈출 차단
    ok2, msg2 = editor.write_file("../evil.py", "malicious")
    chk("경로 탈출 차단", not ok2, msg2[:40])

    # 보호 파일 차단
    ok3, msg3 = editor.write_file("./workspace/../core/security_layer.py", "hack")
    chk("Core Engine 수정 차단", not ok3, msg3[:40])

    # 라인 교체
    ok4, msg4, diff = editor.replace_lines("./workspace/test_edit.py", 2, 2, "print('world')")
    chk("라인 교체", ok4, msg4[:40])

    # 백업 복원
    ok5, msg5 = editor.restore_backup("./workspace/test_edit.py")
    chk("백업 복원", ok5, msg5[:40])

    print(f"\n  결과: {sum(results)}/{len(results)} PASS")
