"""
Diff 解析模块

提供单文件和多文件 SEARCH/REPLACE 块的纯解析功能。
所有函数均为纯函数，无 IO 副作用，便于单元测试。
"""

from dataclasses import dataclass, field
from typing import List, Tuple, Optional
from loguru import logger


# ============================================================================
# 标准标记常量
# ============================================================================

SEARCH_MARKER: str = "<<<<<<< SEARCH"
DIVIDER_MARKER: str = "======="
REPLACE_MARKER: str = ">>>>>>> REPLACE"

DEFAULT_FENCE_OPEN: str = "```"
DEFAULT_FENCE_CLOSE: str = "```"


# ============================================================================
# 数据结构
# ============================================================================


@dataclass
class SearchReplaceBlock:
    """单个 SEARCH/REPLACE 块的结构化表示。

    Attributes:
        search: 要搜索的文本（原始换行保留）
        replace: 替换后的文本（原始换行保留）
    """

    search: str
    replace: str

    def as_tuple(self) -> Tuple[str, str]:
        """兼容旧接口，返回 (search, replace) 元组。"""
        return (self.search, self.replace)


@dataclass
class MultiFileEdit:
    """多文件编辑块的结构化表示。

    Attributes:
        path: 文件路径（相对路径或绝对路径）
        blocks: 该文件的所有 SEARCH/REPLACE 块
    """

    path: str
    blocks: List[SearchReplaceBlock] = field(default_factory=list)

    def as_tuples(self) -> List[Tuple[str, str]]:
        """兼容旧接口，返回 [(search, replace), ...] 列表。"""
        return [b.as_tuple() for b in self.blocks]


# ============================================================================
# 单文件解析
# ============================================================================


def parse_search_replace_blocks(
    diff_content: str,
    search_marker: str = SEARCH_MARKER,
    divider_marker: str = DIVIDER_MARKER,
    replace_marker: str = REPLACE_MARKER,
) -> List[SearchReplaceBlock]:
    """解析单文件 diff 内容为 SEARCH/REPLACE 块列表。

    支持标准格式：
        <<<<<<< SEARCH
        [search content]
        =======
        [replace content]
        >>>>>>> REPLACE

    Args:
        diff_content: 包含 SEARCH/REPLACE 块的 diff 文本
        search_marker: SEARCH 起始标记
        divider_marker: 分隔标记
        replace_marker: REPLACE 结束标记

    Returns:
        解析出的 SearchReplaceBlock 列表（保留原始换行）
    """
    blocks: List[SearchReplaceBlock] = []
    lines = diff_content.splitlines(keepends=True)
    i = 0
    n = len(lines)

    while i < n:
        line = lines[i]
        if line.strip() == search_marker:
            i += 1
            search_lines: List[str] = []
            # 收集 SEARCH 块内容
            while i < n and lines[i].strip() != divider_marker:
                search_lines.append(lines[i])
                i += 1
            if i >= n:
                logger.warning("Unterminated SEARCH block found in diff content.")
                break
            i += 1  # 跳过分隔符

            replace_lines: List[str] = []
            # 收集 REPLACE 块内容
            while i < n and lines[i].strip() != replace_marker:
                replace_lines.append(lines[i])
                i += 1
            if i >= n:
                logger.warning("Unterminated REPLACE block found in diff content.")
                break
            i += 1  # 跳过结束标记

            blocks.append(
                SearchReplaceBlock(
                    search="".join(search_lines),
                    replace="".join(replace_lines),
                )
            )
        else:
            i += 1

    if not blocks and diff_content.strip():
        logger.warning(
            f"Could not parse any SEARCH/REPLACE blocks from diff content (length={len(diff_content)})"
        )

    return blocks


def parse_search_replace_blocks_as_tuples(
    diff_content: str,
    search_marker: str = SEARCH_MARKER,
    divider_marker: str = DIVIDER_MARKER,
    replace_marker: str = REPLACE_MARKER,
) -> List[Tuple[str, str]]:
    """解析单文件 diff 并返回元组列表（兼容旧接口）。"""
    blocks = parse_search_replace_blocks(
        diff_content, search_marker, divider_marker, replace_marker
    )
    return [b.as_tuple() for b in blocks]


# ============================================================================
# 多文件解析
# ============================================================================


@dataclass
class _RawEditBlock:
    """内部用：解析多文件格式时的原始块。"""

    path: str
    content: str  # 包含 SEARCH/REPLACE 标记的原始内容


def _parse_multi_file_raw_blocks(
    text: str,
    fence_open: str = DEFAULT_FENCE_OPEN,
    fence_close: str = DEFAULT_FENCE_CLOSE,
    search_marker: str = SEARCH_MARKER,
) -> List[_RawEditBlock]:
    """解析多文件格式的原始块（支持 two_line_mode 和 one_line_mode）。

    two_line_mode:
        ```python
        ##File: path/to/file.py
        <<<<<<< SEARCH
        ...
        ```

    one_line_mode:
        ```python:path/to/file.py
        <<<<<<< SEARCH
        ...
        ```

    Args:
        text: 包含多文件编辑块的完整文本
        fence_open: 代码块开始标记（默认 ```）
        fence_close: 代码块结束标记（默认 ```）
        search_marker: SEARCH 标记

    Returns:
        解析出的原始编辑块列表
    """
    lines = text.split("\n")
    lines_len = len(lines)
    result: List[_RawEditBlock] = []

    start_marker_count = 0
    block_lines: List[str] = []
    current_mode = "two_line_mode"
    current_path: Optional[str] = None

    def guard(index: int) -> bool:
        return index + 1 < lines_len

    def is_start_marker(line: str, index: int) -> bool:
        nonlocal current_mode, current_path

        # one_line_mode: ```lang:path/to/file
        if (
            line.startswith(fence_open)
            and guard(index)
            and ":" in line
            and lines[index + 1].startswith(search_marker)
        ):
            current_mode = "one_line_mode"
            current_path = line.split(":", 1)[1].strip()
            return True

        # two_line_mode: ``` 后跟 ##File:
        if (
            line.startswith(fence_open)
            and guard(index)
            and lines[index + 1].startswith("##File:")
        ):
            current_mode = "two_line_mode"
            current_path = None
            return True

        return False

    def is_end_marker(line: str, index: int) -> bool:
        return (
            line.startswith(fence_close)
            and index > 0
            and REPLACE_MARKER in lines[index - 1]
        )

    for index, line in enumerate(lines):
        if is_start_marker(line, index) and start_marker_count == 0:
            start_marker_count += 1
        elif is_end_marker(line, index) and start_marker_count == 1:
            start_marker_count -= 1
            if block_lines:
                if current_mode == "two_line_mode":
                    # 第一行是 ##File: path
                    path = block_lines[0].split(":", 1)[1].strip()
                    content = "\n".join(block_lines[1:])
                else:
                    path = current_path or ""
                    content = "\n".join(block_lines)
                block_lines = []
                if path:
                    result.append(_RawEditBlock(path=path, content=content))
        elif start_marker_count > 0:
            block_lines.append(line)

    return result


def _parse_block_content(
    content: str,
    search_marker: str = SEARCH_MARKER,
    divider_marker: str = DIVIDER_MARKER,
    replace_marker: str = REPLACE_MARKER,
    strip_trailing_newline: bool = True,
) -> SearchReplaceBlock:
    """解析单个编辑块的内容为 SearchReplaceBlock。

    Args:
        content: 包含 SEARCH/REPLACE 标记的块内容
        search_marker: SEARCH 标记
        divider_marker: 分隔标记
        replace_marker: REPLACE 标记
        strip_trailing_newline: 是否去除末尾换行（多文件模式默认 True）

    Returns:
        解析后的 SearchReplaceBlock
    """
    heads: List[str] = []
    updates: List[str] = []
    in_head = False
    in_updated = False

    lines = content.splitlines(keepends=True)
    for line in lines:
        stripped = line.strip()
        if stripped == search_marker:
            in_head = True
            continue
        if stripped == divider_marker:
            in_head = False
            in_updated = True
            continue
        if stripped == replace_marker:
            in_head = False
            in_updated = False
            continue
        if in_head:
            heads.append(line)
        if in_updated:
            updates.append(line)

    head_content = "".join(heads)
    update_content = "".join(updates)

    # 多文件模式：去除末尾换行以避免重复
    if strip_trailing_newline:
        if head_content.endswith("\n"):
            head_content = head_content[:-1]
        if update_content.endswith("\n"):
            update_content = update_content[:-1]

    return SearchReplaceBlock(search=head_content, replace=update_content)


def parse_multi_file_edits(
    text: str,
    fence_open: str = DEFAULT_FENCE_OPEN,
    fence_close: str = DEFAULT_FENCE_CLOSE,
    search_marker: str = SEARCH_MARKER,
    divider_marker: str = DIVIDER_MARKER,
    replace_marker: str = REPLACE_MARKER,
) -> List[MultiFileEdit]:
    """解析多文件编辑格式，返回按文件分组的编辑列表。

    支持两种格式：
    1. two_line_mode:
        ```python
        ##File: path/to/file.py
        <<<<<<< SEARCH
        old content
        =======
        new content
        >>>>>>> REPLACE
        ```

    2. one_line_mode:
        ```python:path/to/file.py
        <<<<<<< SEARCH
        old content
        =======
        new content
        >>>>>>> REPLACE
        ```

    Args:
        text: 包含多文件编辑块的完整文本
        fence_open: 代码块开始标记
        fence_close: 代码块结束标记
        search_marker: SEARCH 标记
        divider_marker: 分隔标记
        replace_marker: REPLACE 标记

    Returns:
        按文件分组的 MultiFileEdit 列表
    """
    raw_blocks = _parse_multi_file_raw_blocks(
        text, fence_open, fence_close, search_marker
    )

    # 按路径分组
    path_to_blocks: dict[str, List[SearchReplaceBlock]] = {}
    for raw in raw_blocks:
        block = _parse_block_content(
            raw.content, search_marker, divider_marker, replace_marker
        )
        if raw.path not in path_to_blocks:
            path_to_blocks[raw.path] = []
        path_to_blocks[raw.path].append(block)

    return [
        MultiFileEdit(path=path, blocks=blocks)
        for path, blocks in path_to_blocks.items()
    ]


def parse_multi_file_edits_as_tuples(
    text: str,
    fence_open: str = DEFAULT_FENCE_OPEN,
    fence_close: str = DEFAULT_FENCE_CLOSE,
    search_marker: str = SEARCH_MARKER,
    divider_marker: str = DIVIDER_MARKER,
    replace_marker: str = REPLACE_MARKER,
) -> List[Tuple[str, str, str]]:
    """解析多文件编辑格式，返回 (path, search, replace) 元组列表（兼容旧接口）。

    注意：此函数返回扁平列表，每个块一个元组。
    """
    raw_blocks = _parse_multi_file_raw_blocks(
        text, fence_open, fence_close, search_marker
    )

    result: List[Tuple[str, str, str]] = []
    for raw in raw_blocks:
        block = _parse_block_content(
            raw.content, search_marker, divider_marker, replace_marker
        )
        result.append((raw.path, block.search, block.replace))

    return result


# ============================================================================
# 辅助函数
# ============================================================================


def find_line_numbers(content: str, text_block: str) -> Tuple[int, int]:
    """查找文本块在内容中的行号范围。

    Args:
        content: 完整文件内容
        text_block: 要查找的文本块

    Returns:
        (start_line, end_line) 行号（1-indexed），未找到返回 (-1, -1)
    """
    block_start_idx = content.find(text_block)
    if block_start_idx == -1:
        return (-1, -1)

    block_lines = text_block.splitlines()
    lines_before = content[:block_start_idx].count("\n")
    start_line = lines_before + 1
    lines_in_block = len(block_lines)
    end_line = start_line + lines_in_block - 1

    return (start_line, end_line)
