import os
from pathlib import Path
from typing import Dict, Any, Optional, List, Tuple
import typing
from autocoder.common import AutoCoderArgs
from autocoder.common.v2.agent.agentic_edit_tools.linter_enabled_tool_resolver import (
    LinterEnabledToolResolver,
)
from autocoder.common.v2.agent.agentic_edit_types import ReplaceInFileTool, ToolResult
from autocoder.common.file_checkpoint.models import FileChange as CheckpointFileChange
from loguru import logger
from autocoder.common.auto_coder_lang import get_message_with_format
from autocoder.common.search_replace_patch import SearchReplaceManager
from autocoder.common import files as FileUtils

# 从重构后的子模块导入
from autocoder.common.v2.agent.agentic_edit_tools.replace_in_file import (
    # parsing
    parse_search_replace_blocks as parse_blocks_impl,
    parse_multi_file_edits as parse_multi_file_impl,
    find_line_numbers,
    SEARCH_MARKER,
    DIVIDER_MARKER,
    REPLACE_MARKER,
    # apply
    apply_replacements,
    ApplyResult,
    # failure_report
    ReplacementFailureBlockAnalysis,
    ReplacementFailureReport,
    build_failure_report,
    format_failure_message_detailed,
    report_to_content_dict,
    build_multi_file_failure_content,
    # paths
    PathPolicy,
    validate_file_path,
    validate_multiple_file_paths,
    get_plans_directory,
)

if typing.TYPE_CHECKING:
    from autocoder.common.v2.agent.agentic_edit import AgenticEdit


class ReplaceInFileToolResolver(LinterEnabledToolResolver):
    def __init__(
        self,
        agent: Optional["AgenticEdit"],
        tool: ReplaceInFileTool,
        args: AutoCoderArgs,
    ):
        super().__init__(agent, tool, args)
        self.tool: ReplaceInFileTool = tool  # For type hinting
        self.args = args

        # 初始化智能替换管理器
        self.search_replace_manager = SearchReplaceManager()

        # Get fence parameters from tool - 使用强类型访问，带默认值
        self.fence_0: str = tool.fence_0 if tool.fence_0 else "```"
        self.fence_1: str = tool.fence_1 if tool.fence_1 else "```"

        # 创建路径安全策略
        source_dir = args.source_dir or "."
        self._path_policy = PathPolicy.from_args(args, source_dir)

    def parse_search_replace_blocks(self, diff_content: str) -> List[Tuple[str, str]]:
        """Parse diff content using configured markers into (search, replace) tuples.

        委托给 replace_in_file.parsing 模块的实现。
        """
        from autocoder.common.v2.agent.agentic_edit_tools.replace_in_file import (
            parse_search_replace_blocks_as_tuples,
        )

        return parse_search_replace_blocks_as_tuples(diff_content)

    def _find_line_numbers(self, content: str, text_block: str) -> Tuple[int, int]:
        """Find the line numbers for a given text block in the content.

        委托给 replace_in_file.parsing 模块的实现。
        """
        return find_line_numbers(content, text_block)

    def _intelligent_replace(
        self, content: str, search_blocks: List[Tuple[str, str]]
    ) -> Tuple[bool, str, List[str]]:
        """使用智能替换策略进行文本替换（兼容旧签名）。

        委托给 replace_in_file.apply 模块的实现。
        """
        result = apply_replacements(
            content, search_blocks, manager=self.search_replace_manager
        )
        return result.success, result.new_content, result.errors

    def _apply_replacements_with_fallback(
        self,
        content: str,
        search_blocks: List[Tuple[str, str]],
        file_path: Optional[str] = None,
    ) -> Tuple[bool, str, List[str], Optional[ReplacementFailureReport]]:
        """Apply replacements using the advanced fallback strategy.

        委托给 replace_in_file.apply 和 failure_report 模块的实现。
        Returns (success, new_content, error_messages, failure_report).
        """
        result = apply_replacements(
            content, search_blocks, manager=self.search_replace_manager
        )

        if result.success:
            logger.info(
                f"Intelligent replacement succeeded using {result.used_strategy or 'unknown'} strategy"
            )
            return True, result.new_content, [], None

        # Build structured, extensible feedback
        failure_report = build_failure_report(
            content,
            search_blocks,
            used_strategy=result.used_strategy,
            tried_strategies=result.tried_strategies,
            file_path=file_path,
        )
        error_message = format_failure_message_detailed(failure_report)
        logger.warning(f"Intelligent replacement failed: {error_message}")
        return False, content, [error_message], failure_report

    def get_edits(self, content: str) -> List[Tuple[str, str, str]]:
        """解析多文件编辑格式，返回 (path, search, replace) 元组列表。

        委托给 replace_in_file.parsing 模块的实现。
        """
        from autocoder.common.v2.agent.agentic_edit_tools.replace_in_file import (
            parse_multi_file_edits_as_tuples,
        )

        return parse_multi_file_edits_as_tuples(
            content, fence_open=self.fence_0, fence_close=self.fence_1
        )

    def replace_in_multiple_files(
        self, diff_content: str, source_dir: str, abs_project_dir: str
    ) -> ToolResult:
        """Replace content in multiple files when path is '*'.

        Enhanced with structured failure feedback in result.content when any block fails.
        """
        try:
            # 使用新的解析方法解析多文件格式
            codes = self.get_edits(diff_content)
            if not codes:
                return ToolResult(
                    success=False,
                    message=(
                        "No valid edit blocks found in diff content. "
                        "Each edit block must follow this format:\n"
                        "```\n##File: path/to/file.ext\n<<<<<<< SEARCH\n[exact content to find]\n=======\n[new content]\n>>>>>>> REPLACE\n```\n"
                        "Common issues: missing ##File header, incorrect fence markers, or malformed SEARCH/REPLACE delimiters."
                    ),
                )

            file_content_mapping: Dict[str, str] = {}
            failed_blocks: List[Tuple[str, str, str]] = []
            errors: List[str] = []
            failed_details_by_file: Dict[str, Any] = {}

            # 按文件分组处理块
            file_blocks_map: Dict[str, List[Tuple[str, str]]] = {}
            for block in codes:
                file_path, head, update = block

                # 使用 paths 模块进行安全校验
                validation = validate_file_path(file_path, self._path_policy)
                if not validation.valid:
                    errors.append(
                        validation.error_message
                        or f"Access denied to file: {file_path}"
                    )
                    continue

                if file_path not in file_blocks_map:
                    file_blocks_map[file_path] = []
                file_blocks_map[file_path].append((head, update))

            # 对每个文件使用智能替换策略
            for file_path, blocks in file_blocks_map.items():
                abs_file_path = os.path.abspath(os.path.join(source_dir, file_path))

                if not os.path.exists(abs_file_path):
                    # New file - 对于新文件，直接使用所有更新块的内容
                    new_content = "\n".join(
                        [update for head, update in blocks if update]
                    )
                    file_content_mapping[file_path] = new_content
                else:
                    # 读取现有文件内容
                    existing_content = FileUtils.read_file(abs_file_path)

                    # 使用智能替换策略（与 normal 方法保持一致）
                    logger.info(f"Using intelligent replacement for file: {file_path}")
                    success, new_content, intelligent_errors = (
                        self._intelligent_replace(existing_content, blocks)
                    )

                    if success:
                        file_content_mapping[file_path] = new_content
                        logger.info(
                            f"Intelligent replacement succeeded for {len(blocks)} blocks in {file_path}"
                        )
                    else:
                        logger.warning(
                            f"Intelligent replacement failed for {file_path}: {intelligent_errors}"
                        )
                        # Build structured details for each failed file
                        try:
                            # reuse new failure report via internal method
                            _, _, _, failure_report = (
                                self._apply_replacements_with_fallback(
                                    existing_content, blocks, file_path
                                )
                            )
                            if failure_report is not None:
                                failed_details_by_file[file_path] = (
                                    report_to_content_dict(failure_report)
                                )
                        except Exception:
                            pass
                        errors.extend(
                            [f"{file_path}: {err}" for err in intelligent_errors]
                        )
                        failed_blocks.extend(
                            [(file_path, head, update) for head, update in blocks]
                        )

            if failed_blocks:
                total_blocks = sum(len(blocks) for blocks in file_blocks_map.values())
                failed_count = len(failed_blocks)
                message = (
                    f"Failed to apply {failed_count}/{total_blocks} blocks across multiple files. "
                    f"See content.failed_files for details."
                )
                content_details: Dict[str, Any] = {
                    "failed_files": failed_details_by_file,
                    "total_blocks": total_blocks,
                    "failed_blocks": failed_count,
                    "errors": errors,
                }
                return ToolResult(
                    success=False, message=message, content=content_details
                )

            # Apply changes to files
            changed_files = []
            for file_path, new_content in file_content_mapping.items():
                abs_file_path = os.path.abspath(os.path.join(source_dir, file_path))
                os.makedirs(os.path.dirname(abs_file_path), exist_ok=True)

                # Handle checkpoint manager if available
                if self.agent and self.agent.checkpoint_manager:
                    changes = {
                        file_path: CheckpointFileChange(
                            file_path=file_path,
                            content=new_content,
                            is_deletion=False,
                            is_new=not os.path.exists(abs_file_path),
                        )
                    }
                    change_group_id = self.args.event_file

                    conversation_id = (
                        self.agent.conversation_config.conversation_id
                        if self.agent
                        else None
                    )
                    logger.debug(
                        f"多文件对话检查点调试 - conversation_config存在: {self.agent.conversation_config is not None}, conversation_id: {conversation_id}"
                    )

                    if conversation_id:
                        first_message_id, last_message_id = (
                            self.agent.get_conversation_message_range()
                        )
                        logger.debug(
                            f"多文件获取消息范围 - first_message_id: {first_message_id}, last_message_id: {last_message_id}"
                        )

                        self.agent.checkpoint_manager.apply_changes_with_conversation(
                            changes=changes,
                            conversation_id=conversation_id,
                            first_message_id=first_message_id,
                            last_message_id=last_message_id,
                            change_group_id=change_group_id,
                            metadata={"event_file": self.args.event_file},
                        )
                        logger.debug(f"多文件已调用 apply_changes_with_conversation")
                    else:
                        logger.warning(
                            f"多文件conversation_id 为 None，跳过对话检查点保存"
                        )
                else:
                    with open(abs_file_path, "w", encoding="utf-8") as f:
                        f.write(new_content)

                changed_files.append(file_path)

                # Record file change for AgenticEdit
                if self.agent:
                    rel_path = os.path.relpath(abs_file_path, abs_project_dir)
                    self.agent.record_file_change(
                        rel_path, "modified", content=new_content
                    )

            # 计算统计信息（与 normal 方法保持一致）
            total_blocks = sum(len(blocks) for blocks in file_blocks_map.values())
            applied_blocks = total_blocks - len(failed_blocks)

            # 构建成功消息（与 normal 方法保持一致）
            if errors:
                success_message = f"Successfully applied {applied_blocks}/{total_blocks} blocks across {len(changed_files)} files: {', '.join(changed_files)}. Warnings: {'; '.join(errors)}"
            else:
                success_message = f"Successfully applied {applied_blocks}/{total_blocks} blocks across {len(changed_files)} files: {', '.join(changed_files)}"

            # Run linter check if enabled for multiple files
            result = ToolResult(success=True, message=success_message)
            if (
                self.linter_config
                and self.linter_config.enabled
                and self.linter_config.check_after_modification
            ):
                # Collect all modified files for linting
                files_to_lint = []
                for file_path in changed_files:
                    if self.should_lint(file_path):
                        abs_path = os.path.abspath(os.path.join(source_dir, file_path))
                        files_to_lint.append(abs_path)

                if files_to_lint:
                    logger.info(
                        f"Running linter check on {len(files_to_lint)} modified files"
                    )
                    lint_report = self.lint_files(files_to_lint)
                    if lint_report:
                        result = self.handle_lint_results(result, lint_report)

            return result

        except Exception as e:
            logger.error(f"Error in multiple file replacement: {str(e)}")
            return ToolResult(
                success=False,
                message=f"Error processing multiple file replacement: {str(e)}",
            )

    def replace_in_file_normal(
        self,
        file_path: str,
        diff_content: str,
        source_dir: str,
        abs_project_dir: str,
        abs_file_path: str,
    ) -> ToolResult:
        """Replace content in file directly without using shadow manager.

        Adds structured failure details to result.content when no blocks applied.
        """
        try:
            # Read original content
            if not os.path.exists(abs_file_path):
                return ToolResult(
                    success=False,
                    message=get_message_with_format(
                        "replace_in_file.file_not_found", file_path=file_path
                    ),
                )
            if not os.path.isfile(abs_file_path):
                return ToolResult(
                    success=False,
                    message=get_message_with_format(
                        "replace_in_file.not_a_file", file_path=file_path
                    ),
                )

            with open(abs_file_path, "r", encoding="utf-8", errors="replace") as f:
                original_content = f.read()

            parsed_blocks = self.parse_search_replace_blocks(diff_content)
            if not parsed_blocks:
                return ToolResult(
                    success=False,
                    message=get_message_with_format("replace_in_file.no_valid_blocks"),
                )

            current_content = original_content
            applied_count = 0
            errors = []

            # 使用智能替换
            logger.info("Using intelligent replacement with multiple strategies")
            success, new_content, intelligent_errors = self._intelligent_replace(
                current_content, parsed_blocks
            )

            if success:
                current_content = new_content
                applied_count = len(parsed_blocks)
                logger.info(
                    f"Intelligent replacement succeeded for all {applied_count} blocks"
                )
            else:
                logger.warning(f"Intelligent replacement failed: {intelligent_errors}")
                errors.extend(intelligent_errors)

            if applied_count == 0 and errors:
                # Provide structured report
                _, _, _, failure_report = self._apply_replacements_with_fallback(
                    original_content, parsed_blocks, file_path
                )
                content_details = (
                    report_to_content_dict(failure_report) if failure_report else None
                )
                return ToolResult(
                    success=False,
                    message=get_message_with_format(
                        "replace_in_file.apply_failed", errors="\n\n".join(errors)
                    ),
                    content=content_details,
                )

            # Write the modified content back to file
            if self.agent and self.agent.checkpoint_manager:
                changes = {
                    file_path: CheckpointFileChange(
                        file_path=file_path,
                        content=current_content,
                        is_deletion=False,
                        is_new=False,
                    )
                }
                change_group_id = self.args.event_file

                conversation_id = (
                    self.agent.conversation_config.conversation_id
                    if self.agent
                    else None
                )
                logger.debug(
                    f"对话检查点调试 - conversation_config存在: {self.agent.conversation_config is not None}, conversation_id: {conversation_id}"
                )

                if conversation_id:
                    first_message_id, last_message_id = (
                        self.agent.get_conversation_message_range()
                    )
                    logger.debug(
                        f"获取消息范围 - first_message_id: {first_message_id}, last_message_id: {last_message_id}"
                    )

                    self.agent.checkpoint_manager.apply_changes_with_conversation(
                        changes=changes,
                        conversation_id=conversation_id,
                        first_message_id=first_message_id,
                        last_message_id=last_message_id,
                        change_group_id=change_group_id,
                        metadata={"event_file": self.args.event_file},
                    )
                    logger.debug(f"已调用 apply_changes_with_conversation")
                else:
                    logger.warning(f"conversation_id 为 None，跳过对话检查点保存")
            else:
                with open(abs_file_path, "w", encoding="utf-8") as f:
                    f.write(current_content)

            logger.info(
                f"Successfully applied {applied_count}/{len(parsed_blocks)} changes to file: {file_path}"
            )

            # 构建成功消息
            if errors:
                final_message = get_message_with_format(
                    "replace_in_file.apply_success_with_warnings",
                    applied=applied_count,
                    total=len(parsed_blocks),
                    file_path=file_path,
                    errors="\n".join(errors),
                )
            else:
                final_message = get_message_with_format(
                    "replace_in_file.apply_success",
                    applied=applied_count,
                    total=len(parsed_blocks),
                    file_path=file_path,
                )

            # 变更跟踪，回调AgenticEdit
            if self.agent:
                rel_path = os.path.relpath(abs_file_path, abs_project_dir)
                self.agent.record_file_change(
                    rel_path, "modified", diff=diff_content, content=current_content
                )

            # Run linter check if enabled
            result = ToolResult(success=True, message=final_message)
            if (
                self.should_lint(file_path)
                and self.linter_config
                and self.linter_config.check_after_modification
            ):
                logger.info(f"Running linter check on modified file: {file_path}")
                lint_report = self.lint_files([abs_file_path])
                if lint_report:
                    result = self.handle_lint_results(result, lint_report)

            return result
        except Exception as e:
            logger.error(
                f"Error writing replaced content to file '{file_path}': {str(e)}"
            )
            return ToolResult(
                success=False,
                message=get_message_with_format(
                    "replace_in_file.write_error", error=str(e)
                ),
            )

    def resolve(self) -> ToolResult:
        """Resolve the replace in file tool by calling the appropriate implementation.

        使用 paths 模块进行统一的路径安全校验。
        """
        file_path = self.tool.path
        diff_content = self.tool.diff.strip()
        source_dir = self.args.source_dir or "."
        abs_project_dir = os.path.abspath(source_dir)

        # 更新路径策略（source_dir 可能在运行时变化）
        self._path_policy = PathPolicy.from_args(self.args, source_dir)

        # Plan mode 和 workspace 安全校验
        if file_path == "*":
            # 多文件模式：解析所有文件路径并验证
            codes = self.get_edits(diff_content)
            if self._path_policy.plan_mode:
                # codes 现在是 (path, search, replace) 元组列表
                file_paths = [code[0] for code in codes]
                all_valid, results = validate_multiple_file_paths(
                    file_paths, self._path_policy
                )
                if not all_valid:
                    # 返回第一个失败的错误
                    for i, result in enumerate(results):
                        if not result.valid:
                            return ToolResult(
                                success=False,
                                message=result.error_message
                                or f"Path validation failed for '{file_paths[i]}'",
                            )

            logger.info("Multiple file replacement mode detected")
            return self.replace_in_multiple_files(
                diff_content, source_dir, abs_project_dir
            )

        # 单文件模式：验证路径
        validation = validate_file_path(file_path, self._path_policy)
        if not validation.valid:
            return ToolResult(
                success=False,
                message=validation.error_message
                or get_message_with_format(
                    "replace_in_file.access_denied", file_path=file_path
                ),
            )

        abs_file_path = (
            str(validation.abs_path)
            if validation.abs_path
            else os.path.abspath(os.path.join(source_dir, file_path))
        )

        return self.replace_in_file_normal(
            file_path, diff_content, source_dir, abs_project_dir, abs_file_path
        )
