"""
失败报告模块

提供替换失败时的相似度分析和结构化报告生成。
message 只做摘要，详细信息稳定放入 result.content。
"""

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

from autocoder.common.text_similarity import TextSimilarity
from .parsing import find_line_numbers


# ============================================================================
# Pydantic 模型（结构化失败报告）
# ============================================================================


class ReplacementFailureBlockAnalysis(pydantic.BaseModel):
    """单个 SEARCH 块的结构化失败分析。

    Attributes:
        search_preview: 用户提供的搜索文本
        similarity: 与文件内容的最佳相似度 (0-1)
        start_line: 最佳匹配的起始行号 (1-indexed，-1 表示未找到)
        end_line: 最佳匹配的结束行号 (1-indexed，-1 表示未找到)
        best_window_preview: 文件中最佳匹配的实际内容
        hints: 修复建议列表
    """

    search_preview: str
    similarity: float
    start_line: int
    end_line: int
    best_window_preview: str
    hints: List[str] = []


class ReplacementFailureReport(pydantic.BaseModel):
    """替换失败的结构化报告。

    Attributes:
        reason: 失败原因标识
        file_path: 目标文件路径
        used_strategy: 最后使用的策略
        tried_strategies: 尝试过的所有策略
        suggestions: 通用修复建议
        blocks: 每个 SEARCH 块的详细分析
    """

    reason: str = "replacement_failed"
    file_path: Optional[str] = None
    used_strategy: Optional[str] = None
    tried_strategies: List[str] = []
    suggestions: List[str] = []
    blocks: List[ReplacementFailureBlockAnalysis] = []


# ============================================================================
# 相似度分析
# ============================================================================


def analyze_search_block_similarity(
    content: str, search_text: str
) -> ReplacementFailureBlockAnalysis:
    """分析 SEARCH 块与文件内容的相似度。

    使用 TextSimilarity 查找最佳匹配窗口，返回完整内容以便 LLM 直接复制使用。

    Args:
        content: 完整文件内容
        search_text: 用户提供的搜索文本

    Returns:
        ReplacementFailureBlockAnalysis 对象
    """
    try:
        similarity_finder = TextSimilarity(search_text, content)
        similarity, best_window = similarity_finder.get_best_matching_window()
        start_line, end_line = find_line_numbers(content, best_window)

        return ReplacementFailureBlockAnalysis(
            search_preview=search_text,
            similarity=float(similarity),
            start_line=start_line,
            end_line=end_line,
            best_window_preview=best_window,
            hints=[],
        )
    except Exception as e:
        logger.warning(f"Error analyzing search block similarity: {e}")
        return ReplacementFailureBlockAnalysis(
            search_preview=search_text,
            similarity=0.0,
            start_line=-1,
            end_line=-1,
            best_window_preview=f"<analysis error: {str(e)}>",
            hints=[],
        )


def generate_common_suggestions(content: str) -> List[str]:
    """生成通用的修复建议。

    Args:
        content: 文件内容

    Returns:
        建议列表
    """
    suggestions: List[str] = []

    # 检测换行符类型
    has_crlf = "\r\n" in content
    has_lf = "\n" in content and "\r\n" not in content
    if has_crlf or has_lf:
        line_ending = "CRLF (\\r\\n)" if has_crlf else "LF (\\n)"
        suggestions.append(
            f"• File uses {line_ending} — ensure SEARCH blocks match exact line endings"
        )

    # 检测尾随空白
    content_lines = content.splitlines()
    has_trailing_spaces = any(
        line.endswith(" ") or line.endswith("\t") for line in content_lines
    )
    if has_trailing_spaces:
        suggestions.append(
            "• File contains trailing whitespace — include exact spacing in SEARCH blocks"
        )

    suggestions.append(
        "• Use read_file tool to examine exact content around the target location"
    )
    suggestions.append("• Consider searching for a smaller, more unique fragment first")

    return suggestions


# ============================================================================
# 报告构建
# ============================================================================


def build_failure_report(
    content: str,
    search_blocks: List[Tuple[str, str]],
    used_strategy: Optional[str] = None,
    tried_strategies: Optional[List[str]] = None,
    file_path: Optional[str] = None,
) -> ReplacementFailureReport:
    """构建替换失败报告。

    Args:
        content: 原始文件内容
        search_blocks: (search, replace) 元组列表
        used_strategy: 最后使用的策略
        tried_strategies: 尝试过的策略列表
        file_path: 目标文件路径

    Returns:
        ReplacementFailureReport 对象
    """
    blocks_analysis: List[ReplacementFailureBlockAnalysis] = []
    for search_text, _ in search_blocks:
        analysis = analyze_search_block_similarity(content, search_text)
        blocks_analysis.append(analysis)

    return ReplacementFailureReport(
        file_path=file_path,
        used_strategy=used_strategy,
        tried_strategies=tried_strategies or [],
        suggestions=generate_common_suggestions(content),
        blocks=blocks_analysis,
    )


# ============================================================================
# 消息格式化
# ============================================================================


def format_failure_summary(report: ReplacementFailureReport) -> str:
    """格式化失败摘要消息。

    只包含关键信息，详细内容放入 result.content。

    Args:
        report: 失败报告

    Returns:
        摘要消息字符串
    """
    total_blocks = len(report.blocks)

    # 计算最佳相似度
    best_similarity = 0.0
    best_location = ""
    for blk in report.blocks:
        if blk.similarity > best_similarity:
            best_similarity = blk.similarity
            if blk.start_line != -1:
                best_location = f" at lines {blk.start_line}-{blk.end_line}"

    # 构建摘要
    if report.file_path:
        summary = f"SEARCH block mismatch in '{report.file_path}'"
    else:
        summary = "SEARCH block mismatch"

    summary += f" ({total_blocks} block(s) failed)"

    if best_similarity > 0:
        summary += f". Best match: {best_similarity:.0%}{best_location}"

    if report.tried_strategies:
        summary += f". Tried strategies: {', '.join(report.tried_strategies)}"

    summary += ". See result.content for detailed analysis."

    return summary


def format_failure_message_detailed(report: ReplacementFailureReport) -> str:
    """格式化详细的失败消息（用于 LLM 自我修正）。

    包含完整内容以便 LLM 直接复制使用。

    Args:
        report: 失败报告

    Returns:
        详细消息字符串
    """
    total_blocks = len(report.blocks)
    if report.file_path:
        base_error = f"SEARCH block mismatch in '{report.file_path}' ({total_blocks} block(s) failed)."
    else:
        base_error = f"SEARCH block mismatch ({total_blocks} block(s) failed)."

    analysis_parts: List[str] = []
    for idx, blk in enumerate(report.blocks, 1):
        # Header with key metrics
        if blk.start_line != -1:
            header = f"❌ Block {idx} (similarity: {blk.similarity:.0%}, lines {blk.start_line}-{blk.end_line}):"
        else:
            header = f"❌ Block {idx} (similarity: {blk.similarity:.0%}):"

        parts = [header]

        # Show what user searched for
        parts.append("YOUR SEARCH:")
        parts.append(f"```\n{blk.search_preview}\n```")

        # Show actual content and actionable hint based on similarity
        if blk.similarity > 0.5 and blk.start_line != -1:
            parts.append("ACTUAL CONTENT (copy this as your SEARCH block):")
            parts.append(f"```\n{blk.best_window_preview}\n```")

            if blk.similarity > 0.8:
                parts.append(
                    "✅ High similarity — use ACTUAL CONTENT above as your SEARCH block."
                )
            else:
                parts.append(
                    "⚠️ Moderate similarity — check whitespace/indentation, then use ACTUAL CONTENT."
                )
        else:
            # Low similarity - need to re-read file
            if report.file_path:
                parts.append(
                    f"⚠️ No match found. Re-read the file: read_file path='{report.file_path}'"
                )
            else:
                parts.append(
                    "⚠️ No match found. Use read_file to verify the target content."
                )

        analysis_parts.append("\n".join(parts))

    detailed_analysis = "\n\n".join(analysis_parts)

    # 添加通用建议
    if report.suggestions:
        detailed_analysis += "\n\nSuggestions:\n" + "\n".join(report.suggestions)

    return f"{base_error}\n\n{detailed_analysis}"


def report_to_content_dict(report: ReplacementFailureReport) -> Dict[str, Any]:
    """将失败报告转换为 result.content 字典格式。

    Args:
        report: 失败报告

    Returns:
        适合放入 ToolResult.content 的字典
    """
    return report.model_dump()


# ============================================================================
# 多文件失败报告
# ============================================================================


def build_multi_file_failure_content(
    failed_files: Dict[str, ReplacementFailureReport],
    total_blocks: int,
    failed_blocks: int,
    errors: List[str],
) -> Dict[str, Any]:
    """构建多文件失败时的 result.content 内容。

    Args:
        failed_files: 每个失败文件的报告
        total_blocks: 总块数
        failed_blocks: 失败块数
        errors: 错误信息列表

    Returns:
        result.content 字典
    """
    return {
        "failed_files": {
            path: report.model_dump() for path, report in failed_files.items()
        },
        "total_blocks": total_blocks,
        "failed_blocks": failed_blocks,
        "errors": errors,
    }
