# modules/file_manager.py - 文件管理模块（添加行编辑功能）

import os
import shutil
from pathlib import Path
import re
from bisect import bisect_right
from typing import Any, Optional, Dict, List, Set, Tuple, TYPE_CHECKING
from datetime import datetime
try:
    from config import (
        MAX_FILE_SIZE,
        FORBIDDEN_PATHS,
        FORBIDDEN_ROOT_PATHS,
        OUTPUT_FORMATS,
        READ_TOOL_MAX_FILE_SIZE,
        PROJECT_MAX_STORAGE_BYTES,
        TERMINAL_SANDBOX_MODE,
        LINUX_SAFETY,
    )
except ImportError:  # 兼容全局环境中存在同名包的情况
    import sys
    from pathlib import Path
    project_root = Path(__file__).resolve().parents[1]
    if str(project_root) not in sys.path:
        sys.path.insert(0, str(project_root))
    from config import (
        MAX_FILE_SIZE,
        FORBIDDEN_PATHS,
        FORBIDDEN_ROOT_PATHS,
        OUTPUT_FORMATS,
        READ_TOOL_MAX_FILE_SIZE,
        PROJECT_MAX_STORAGE_BYTES,
        TERMINAL_SANDBOX_MODE,
        LINUX_SAFETY,
    )
from modules.container_file_proxy import ContainerFileProxy
from modules.host_sandbox_policy import get_macos_writable_paths, get_macos_readable_paths
from utils.logger import setup_logger

if TYPE_CHECKING:
    from modules.user_container_manager import ContainerHandle

# 临时禁用长度检查
DISABLE_LENGTH_CHECK = True

logger = setup_logger(__name__)

class ReplaceMixin:
    """FileManager replace mixin 能力 mixin。"""

    def replace_in_file(self, path: str, old_text: str, new_text: str, replace_all: bool = False) -> Dict:
        """替换文件中的内容"""
        # 先读取文件
        result = self.read_file(path)
        if not result["success"]:
            return result
        
        content = result["content"]
        
        # === 新增：替换操作的安全检查 ===
        if old_text and len(old_text) > 9999999999:
            return {
                "success": False,
                "error": "要替换的文本过长，可能导致性能问题",
                "suggestion": "请拆分内容或使用 edit_file/逐块写入完成修改"
            }
        
        if new_text and len(new_text) > 9999999999:
            return {
                "success": False,
                "error": "替换的新文本过长，建议分块处理",
                "suggestion": "请将大内容分成多个小的替换操作"
            }
        short_old_text_notice = bool(old_text and len(old_text.splitlines()) < 3)

        # 检查是否包含要替换的内容
        if old_text and old_text not in content:
            return {"success": False, "error": "未找到要替换的内容"}

        matched_lines = self._find_match_line_numbers(content, old_text) if old_text else []
        found_count = len(matched_lines) if old_text else 0

        # 替换内容
        if old_text:
            if replace_all:
                count = found_count
                new_content = content.replace(old_text, new_text)
            else:
                count = 1
                new_content = content.replace(old_text, new_text, 1)
        else:
            # 空文件直接写入新内容
            new_content = new_text
            count = 1
        
        # 写回文件
        result = self.write_file(path, new_content)
        if result["success"]:
            result["replacements"] = count
            message_parts: List[str] = []
            if old_text:
                result["found_matches"] = found_count
                result["matched_lines"] = matched_lines
                if found_count > 1:
                    line_text = ",".join(str(line_no) for line_no in matched_lines)
                    message_parts.append(f"发现{found_count}处，于{line_text}行共替换{count}处")
            if short_old_text_notice:
                message_parts.insert(
                    0,
                    "提示：old_string 少于3行，已继续执行；需要批量替换的场景可以单行或不足一行"
                )
            if message_parts:
                result["message"] = "；".join(message_parts)
            print(f"{OUTPUT_FORMATS['file']} 替换了 {count} 处内容")

        return result

    def replace_many_in_file(self, path: str, replacements: List[Dict[str, Any]]) -> Dict:
        """按顺序执行多组精确字符串替换；任意一组失败时不写入文件。"""
        result = self.read_file(path)
        if not result["success"]:
            return result

        if not isinstance(replacements, list) or not replacements:
            return {"success": False, "error": "replacements 必须是非空数组"}

        if len(replacements) > 100:
            return {
                "success": False,
                "error": "replacements 数量过多，最多支持 100 组",
                "suggestion": "请拆分为多次 edit_file 调用"
            }

        original_content = result["content"]
        current_content = original_content
        details: List[Dict[str, Any]] = []
        failed_details: List[Dict[str, Any]] = []
        total_replacements = 0
        total_found_matches = 0
        short_old_text_indices: List[int] = []

        for zero_based_index, item in enumerate(replacements):
            index = zero_based_index + 1
            detail: Dict[str, Any] = {
                "index": index,
                "status": "pending",
                "replace_all": False,
                "found_matches": 0,
                "replacements": 0,
                "matched_lines": [],
            }

            if not isinstance(item, dict):
                detail.update({
                    "status": "error",
                    "reason": "替换项必须是对象，包含 old_string 和 new_string"
                })
                failed_details.append(detail.copy())
                details.append(detail)
                break

            old_text = item.get("old_string")
            new_text = item.get("new_string")
            replace_all = item.get("replace_all", False)
            detail["replace_all"] = replace_all

            if old_text is None or new_text is None:
                detail.update({
                    "status": "error",
                    "reason": "缺少 old_string/new_string"
                })
                failed_details.append(detail.copy())
                details.append(detail)
                break
            if not isinstance(old_text, str) or not isinstance(new_text, str):
                detail.update({
                    "status": "error",
                    "reason": "old_string/new_string 必须是字符串"
                })
                failed_details.append(detail.copy())
                details.append(detail)
                break
            if not isinstance(replace_all, bool):
                detail.update({
                    "status": "error",
                    "reason": "replace_all 必须是 true 或 false；不提供时默认为 false"
                })
                failed_details.append(detail.copy())
                details.append(detail)
                break
            if old_text == new_text:
                detail.update({
                    "status": "error",
                    "reason": "old_string 与 new_string 相同，无法执行替换"
                })
                failed_details.append(detail.copy())
                details.append(detail)
                break
            if not old_text:
                detail.update({
                    "status": "error",
                    "reason": "old_string 不能为空，请从 read_file 内容中精确复制"
                })
                failed_details.append(detail.copy())
                details.append(detail)
                break

            if len(old_text) > 9999999999:
                detail.update({
                    "status": "error",
                    "reason": "要替换的文本过长，可能导致性能问题"
                })
                failed_details.append(detail.copy())
                details.append(detail)
                break
            if len(new_text) > 9999999999:
                detail.update({
                    "status": "error",
                    "reason": "替换的新文本过长，建议分块处理"
                })
                failed_details.append(detail.copy())
                details.append(detail)
                break

            if len(old_text.splitlines()) < 3:
                short_old_text_indices.append(index)

            matched_lines = self._find_match_line_numbers(current_content, old_text)
            found_count = len(matched_lines)
            if found_count == 0:
                detail.update({
                    "status": "not_found",
                    "reason": "未找到要替换的内容"
                })
                failed_details.append(detail.copy())
                details.append(detail)
                break

            replacement_count = found_count if replace_all else 1
            contexts = self._find_match_contexts(
                current_content,
                old_text,
                max_matches=replacement_count
            )

            if replace_all:
                current_content = current_content.replace(old_text, new_text)
            else:
                current_content = current_content.replace(old_text, new_text, 1)

            detail.update({
                "status": "success",
                "found_matches": found_count,
                "replacements": replacement_count,
                "matched_lines": matched_lines,
                "contexts": contexts,
                "old_string": old_text,
                "new_string": new_text,
                "old_lines": old_text.splitlines(),
                "new_lines": new_text.splitlines(),
                "old_line_count": len(old_text.splitlines()),
                "new_line_count": len(new_text.splitlines()),
            })
            details.append(detail)
            total_found_matches += found_count
            total_replacements += replacement_count

        if failed_details:
            return {
                "success": False,
                "path": result.get("path") or path,
                "error": f"第 {failed_details[0].get('index')} 组替换失败：{failed_details[0].get('reason')}",
                "failed_replacement_index": failed_details[0].get("index"),
                "failed_details": failed_details,
                "details": details,
                "write_performed": False,
                "completed_replacements": len([item for item in details if item.get("status") == "success"]),
            }

        write_result = self.write_file(path, current_content)
        if write_result["success"]:
            write_result["replacements"] = total_replacements
            write_result["found_matches"] = total_found_matches
            write_result["replacement_groups"] = len(replacements)
            write_result["details"] = details
            write_result["write_performed"] = True
            message_parts: List[str] = [f"共 {len(replacements)} 组替换，替换 {total_replacements} 处"]
            if short_old_text_indices:
                indices_text = ",".join(str(item) for item in short_old_text_indices)
                message_parts.append(f"提示：第 {indices_text} 组 old_string 少于3行，已继续执行")
            write_result["message"] = "；".join(message_parts)
            print(f"{OUTPUT_FORMATS['file']} 批量替换了 {total_replacements} 处内容")
        return write_result

    @staticmethod
    def _find_match_line_numbers(content: str, target: str) -> List[int]:
        """返回 target 在 content 中每个匹配起始位置对应的行号（1-based）。"""
        if not target:
            return []
        newline_positions = [idx for idx, ch in enumerate(content) if ch == "\n"]
        line_numbers: List[int] = []
        search_start = 0
        target_len = len(target)

        while True:
            idx = content.find(target, search_start)
            if idx < 0:
                break
            line_no = bisect_right(newline_positions, idx) + 1
            line_numbers.append(line_no)
            search_start = idx + max(1, target_len)

        return line_numbers

    @staticmethod
    def _find_match_contexts(content: str, target: str, max_matches: Optional[int] = None) -> List[Dict[str, Any]]:
        """返回每个实际替换匹配位置的上下文行，仅用于结果 metadata 展示。"""
        if not target:
            return []

        lines = content.splitlines()
        newline_positions = [idx for idx, ch in enumerate(content) if ch == "\n"]
        contexts: List[Dict[str, Any]] = []
        search_start = 0
        target_len = len(target)
        old_line_count = max(1, len(target.splitlines()))

        while True:
            if max_matches is not None and len(contexts) >= max_matches:
                break
            idx = content.find(target, search_start)
            if idx < 0:
                break

            start_line = bisect_right(newline_positions, idx) + 1
            end_line = start_line + old_line_count - 1
            before_start = max(1, start_line - 2)
            before_lines = [
                {"line": line_no, "text": lines[line_no - 1]}
                for line_no in range(before_start, start_line)
                if 0 <= line_no - 1 < len(lines)
            ]
            after_end = min(len(lines), end_line + 2)
            after_lines = [
                {"line": line_no, "text": lines[line_no - 1]}
                for line_no in range(end_line + 1, after_end + 1)
                if 0 <= line_no - 1 < len(lines)
            ]
            contexts.append({
                "old_start_line": start_line,
                "old_end_line": end_line,
                "before_lines": before_lines,
                "after_lines": after_lines,
            })
            search_start = idx + max(1, target_len)

        return contexts
