"""行为模式匹配器 — 从工具序列推断决策

5 种核心模式，纯 L0 规则，不依赖任何 LLM：
1. 调查-决策: Read×N → Edit 1 file
2. 错误-修复: test_failed → Read/Search → Edit → test_passed
3. 依赖引入: Bash(pip install) → Edit(import)
4. 方案切换: Edit A → revert/undo → Edit B
5. 迭代修改: Edit same file ×3+
"""

from __future__ import annotations

import json
import re
from pathlib import Path


def detect_patterns(events: list[dict], collection: str, session_id: str | None) -> list[dict]:
    """扫描事件序列，返回推断出的 decision/root_cause facts

    每个返回的 fact dict 可直接传给 db_cog.insert_fact()。
    """
    if len(events) < 2:
        return []

    facts: list[dict] = []
    seen: set[str] = set()  # 去重

    facts.extend(_detect_investigate_decide(events, collection, session_id, seen))
    facts.extend(_detect_error_fix(events, collection, session_id, seen))
    facts.extend(_detect_dependency_intro(events, collection, session_id, seen))
    facts.extend(_detect_approach_switch(events, collection, session_id, seen))
    facts.extend(_detect_iterative_refine(events, collection, session_id, seen))

    return facts


def _detect_investigate_decide(
    events: list[dict], collection: str, session_id: str | None, seen: set[str],
) -> list[dict]:
    """模式 1: Read×N → Edit 1 file (调查后决策)

    检测窗口内连续 Read 后跟一个 Edit 的模式。
    """
    facts = []
    i = 0
    while i < len(events):
        # 找连续 Read 序列
        read_start = i
        read_files: list[str] = []
        while i < len(events) and events[i].get("type") == "file_read":
            for f in events[i].get("files", []):
                if f not in read_files:
                    read_files.append(f)
            i += 1

        if len(read_files) >= 2 and i < len(events) and events[i].get("type") == "file_modified":
            edit_file = events[i].get("files", [""])[0] if events[i].get("files") else ""
            if edit_file:
                assertion = (
                    f"Investigated {len(read_files)} files, then modified {Path(edit_file).name}"
                )
                if assertion not in seen:
                    seen.add(assertion)
                    facts.append(_make_pattern_fact(
                        assertion=assertion,
                        category="decision",
                        collection=collection,
                        session_id=session_id,
                        related_files=read_files[:5] + [edit_file],
                        tool_sequence="Read×" + str(len(read_files)) + "→Edit",
                    ))
            i += 1
        elif read_files:
            i = read_start + 1  # 回退，避免跳过
        else:
            i += 1

    return facts


def _detect_error_fix(
    events: list[dict], collection: str, session_id: str | None, seen: set[str],
) -> list[dict]:
    """模式 2: test_failed → Read/Search → Edit → test_passed (错误-修复)"""
    facts = []

    for i in range(len(events)):
        if events[i].get("type") != "test_failed":
            continue

        # 往后找：Read/Search → Edit → test_passed
        modified_files: list[str] = []
        found_pass = False
        j = i + 1

        while j < len(events) and j - i <= 10:
            ev = events[j]
            if ev.get("type") == "file_modified":
                for f in ev.get("files", []):
                    if f not in modified_files:
                        modified_files.append(f)
            elif ev.get("type") == "test_passed":
                found_pass = True
                break
            elif ev.get("type") == "test_failed":
                break  # 另一个测试失败，重新开始
            j += 1

        if found_pass and modified_files:
            files_str = ", ".join(Path(f).name for f in modified_files[:3])
            error_detail = events[i].get("detail", "")[:60]
            assertion = f"Fixed test failure by modifying {files_str}"
            if assertion not in seen:
                seen.add(assertion)
                facts.append(_make_pattern_fact(
                    assertion=assertion,
                    category="root_cause",
                    collection=collection,
                    session_id=session_id,
                    related_files=modified_files[:5],
                    tool_sequence="Bash(fail)→Edit→Bash(pass)",
                    error_detail=error_detail,
                ))

    return facts


def _detect_dependency_intro(
    events: list[dict], collection: str, session_id: str | None, seen: set[str],
) -> list[dict]:
    """模式 3: Bash(pip install X) → Edit(import X) (依赖引入)"""
    facts = []

    for i in range(len(events)):
        ev = events[i]
        if ev.get("type") != "bash":
            continue

        detail = ev.get("detail", "")
        pkg_match = re.search(r'pip install\s+(\S+)', detail)
        if not pkg_match:
            pkg_match = re.search(r'npm install\s+(\S+)', detail)
        if not pkg_match:
            pkg_match = re.search(r'cargo add\s+(\S+)', detail)
        if not pkg_match:
            continue

        pkg_name = pkg_match.group(1).strip().rstrip("'\"")

        # 往后找 Edit
        for j in range(i + 1, min(i + 5, len(events))):
            if events[j].get("type") == "file_modified":
                edit_file = events[j].get("files", [""])[0] if events[j].get("files") else ""
                assertion = f"Introduced {pkg_name} (used in {Path(edit_file).name})"
                if assertion not in seen:
                    seen.add(assertion)
                    facts.append(_make_pattern_fact(
                        assertion=assertion,
                        category="decision",
                        collection=collection,
                        session_id=session_id,
                        related_files=[edit_file] if edit_file else [],
                        tool_sequence=f"Bash(install {pkg_name})→Edit",
                    ))
                break

    return facts


def _detect_approach_switch(
    events: list[dict], collection: str, session_id: str | None, seen: set[str],
) -> list[dict]:
    """模式 4: Edit A → revert/undo → Edit B (方案切换)

    检测 git revert 或同一文件被编辑后又编辑另一个文件的模式。
    """
    facts = []

    for i in range(len(events)):
        ev = events[i]
        if ev.get("type") != "bash":
            continue

        detail = ev.get("detail", "")
        if "git revert" not in detail and "git checkout" not in detail:
            continue

        # 往前找最近的 Edit
        prev_file = None
        for j in range(i - 1, max(i - 5, -1), -1):
            if events[j].get("type") == "file_modified":
                prev_file = events[j].get("files", [""])[0] if events[j].get("files") else ""
                break

        # 往后找下一个 Edit
        next_file = None
        for j in range(i + 1, min(i + 5, len(events))):
            if events[j].get("type") == "file_modified":
                next_file = events[j].get("files", [""])[0] if events[j].get("files") else ""
                break

        if prev_file and next_file:
            assertion = (
                f"Switched approach: reverted changes, "
                f"then modified {Path(next_file).name}"
            )
            if assertion not in seen:
                seen.add(assertion)
                facts.append(_make_pattern_fact(
                    assertion=assertion,
                    category="decision",
                    collection=collection,
                    session_id=session_id,
                    related_files=[prev_file, next_file],
                    tool_sequence="Edit→revert→Edit",
                ))

    return facts


def _detect_iterative_refine(
    events: list[dict], collection: str, session_id: str | None, seen: set[str],
) -> list[dict]:
    """模式 5: Edit same file ×3+ (迭代修改)"""
    facts = []
    file_edit_counts: dict[str, int] = {}

    for ev in events:
        if ev.get("type") == "file_modified":
            for f in ev.get("files", []):
                file_edit_counts[f] = file_edit_counts.get(f, 0) + 1

    for file_path, count in file_edit_counts.items():
        if count >= 3:
            assertion = f"Iteratively refined {Path(file_path).name} ({count} edits)"
            if assertion not in seen:
                seen.add(assertion)
                facts.append(_make_pattern_fact(
                    assertion=assertion,
                    category="code",
                    collection=collection,
                    session_id=session_id,
                    related_files=[file_path],
                    tool_sequence=f"Edit×{count}",
                ))

    return facts


def _make_pattern_fact(
    assertion: str,
    category: str,
    collection: str,
    session_id: str | None,
    related_files: list[str] | None = None,
    tool_sequence: str = "",
    error_detail: str = "",
) -> dict:
    """构造带 context_json 的 pattern fact"""
    context: dict = {}
    if related_files:
        context["related_files"] = related_files[:5]
    if tool_sequence:
        context["tool_sequence"] = tool_sequence
    if error_detail:
        context["error_detail"] = error_detail
    context["source_pattern"] = True

    return {
        "assertion": assertion[:200],
        "category": category,
        "collection": collection,
        "session_id": session_id,
        "scope_files": json.dumps(related_files[:3] if related_files else []),
        "scope_modules": "[]",
        "scope_entities": "[]",
        "confidence": 0.8,
        "source": "pattern",
        "status": "active",
        "context_json": json.dumps(context, ensure_ascii=False),
    }
