"""Context backup search tools for recovering information from compacted context."""

import json
import pathlib
from typing import Annotated, Any

from arcade_tdk import ToolContext, tool

from cadecoder.core.logging import log

BACKUP_DIR = pathlib.Path.home() / ".cadecoder" / "context_backups"


def _list_backup_files(limit: int = 10) -> list[pathlib.Path]:
    """List backup files sorted by modification time (most recent first)."""
    if not BACKUP_DIR.exists():
        return []
    return sorted(
        BACKUP_DIR.glob("context_backup_*.json"),
        key=lambda p: p.stat().st_mtime,
        reverse=True,
    )[:limit]


def _load_backup_file(path: pathlib.Path) -> dict[str, Any] | None:
    """Safely load a single backup JSON file."""
    try:
        with open(path, encoding="utf-8") as f:
            return json.load(f)
    except (json.JSONDecodeError, OSError) as e:
        log.warning(f"Failed to load backup {path.name}: {e}")
        return None


def _load_backup_metadata(path: pathlib.Path) -> dict[str, Any] | None:
    """Load only metadata from a backup file (skip full message content).

    Reads the file but extracts just the summary fields plus
    user message previews for orientation.
    """
    data = _load_backup_file(path)
    if data is None:
        return None

    messages = data.get("messages", [])

    # Extract user message previews for orientation
    user_previews = []
    for msg in messages:
        if msg.get("role") == "user":
            content = msg.get("content", "")
            if isinstance(content, str) and content.strip():
                preview = content.strip().replace("\n", " ")[:120]
                user_previews.append(preview)

    # Extract tool names used
    tools_used = set()
    for msg in messages:
        if msg.get("tool_calls"):
            for tc in msg["tool_calls"]:
                name = tc.get("function", {}).get("name", "")
                if name:
                    tools_used.add(name)

    return {
        "file": path.name,
        "timestamp": data.get("timestamp", "unknown"),
        "thread_id": data.get("thread_id", ""),
        "topic": data.get("topic", ""),
        "token_count": data.get("token_count", 0),
        "message_count": len(messages),
        "user_messages": user_previews[:5],
        "tools_used": sorted(tools_used)[:10],
    }


def _extract_text(message: dict[str, Any]) -> str:
    """Extract searchable text from a message dict.

    Handles both string content and list-of-blocks content (Anthropic format).
    """
    content = message.get("content", "")
    if isinstance(content, str):
        return content
    if isinstance(content, list):
        parts = []
        for block in content:
            if isinstance(block, str):
                parts.append(block)
            elif isinstance(block, dict):
                text = block.get("text", "")
                if text:
                    parts.append(text)
                if block.get("type") == "tool_use":
                    input_data = block.get("input", {})
                    if isinstance(input_data, dict):
                        parts.append(json.dumps(input_data))
                    elif isinstance(input_data, str):
                        parts.append(input_data)
        return "\n".join(parts)
    return str(content) if content else ""


def _search_messages(
    messages: list[dict[str, Any]],
    query: str,
    case_sensitive: bool,
    context_messages: int,
) -> list[dict[str, Any]]:
    """Search messages for query string, returning matches with surrounding context."""
    search_query = query if case_sensitive else query.lower()
    matches = []

    for i, msg in enumerate(messages):
        text = _extract_text(msg)
        compare_text = text if case_sensitive else text.lower()

        if search_query in compare_text:
            start = max(0, i - context_messages)
            end = min(len(messages), i + context_messages + 1)

            context_msgs = []
            for j in range(start, end):
                ctx_msg = messages[j]
                context_msgs.append(
                    {
                        "index": j,
                        "role": ctx_msg.get("role", "unknown"),
                        "content_preview": _extract_text(ctx_msg)[:500],
                        "is_match": j == i,
                    }
                )

            tool_calls_info = []
            if msg.get("tool_calls"):
                for tc in msg["tool_calls"]:
                    func = tc.get("function", {})
                    tool_calls_info.append(
                        {
                            "name": func.get("name", ""),
                            "arguments_preview": func.get("arguments", "")[:200],
                        }
                    )

            matches.append(
                {
                    "message_index": i,
                    "role": msg.get("role", "unknown"),
                    "content_preview": text[:500],
                    "tool_calls": tool_calls_info or None,
                    "context": context_msgs,
                }
            )

    return matches


@tool(
    name="SearchRecentContext",
    desc=(
        "Search or list compacted context backups. "
        "Use query='' to list available backups with topics and timestamps. "
        "Use query='search term' to search backup content."
    ),
)
def search_recent_context_tool(
    context: ToolContext,
    query: Annotated[
        str,
        "Text to search for, or empty string to list available backups with their topics.",
    ] = "",
    max_backups: Annotated[
        int,
        "Maximum number of recent backup files to search (most recent first).",
    ] = 5,
    case_sensitive: Annotated[bool, "Whether search should be case sensitive."] = False,
    role_filter: Annotated[
        str,
        "Filter matches by message role: 'user', 'assistant', or 'tool'.",
    ] = "",
    max_results: Annotated[int, "Maximum number of matching messages to return."] = 20,
) -> Annotated[dict[str, Any], "Search results or backup listing."]:
    """Search or list compacted context backup files.

    When called with an empty query, lists all available backups with their
    topics, timestamps, thread IDs, and user message previews -- useful for
    understanding what information is available before searching.

    When called with a query string, searches backup message content for matches
    and returns them with surrounding context.
    """
    backup_files = _list_backup_files(limit=max_backups)

    if not backup_files:
        return {
            "results": [],
            "summary": {
                "total_matches": 0,
                "backups_searched": 0,
                "message": "No context backups found. Context has not been compacted yet.",
            },
        }

    # List mode: return metadata about available backups
    if not query or not query.strip():
        backup_index = []
        for bp in backup_files:
            meta = _load_backup_metadata(bp)
            if meta:
                backup_index.append(meta)

        return {
            "backups": backup_index,
            "summary": {
                "total_backups": len(backup_index),
                "message": (
                    f"{len(backup_index)} context backups available. "
                    "Use a search query to find specific content."
                ),
            },
        }

    # Search mode
    all_results = []
    backups_searched = 0

    for backup_path in backup_files:
        backup_data = _load_backup_file(backup_path)
        if backup_data is None:
            continue

        backups_searched += 1
        messages = backup_data.get("messages", [])

        matches = _search_messages(messages, query, case_sensitive, context_messages=2)

        if role_filter:
            matches = [m for m in matches if m["role"] == role_filter]

        if matches:
            all_results.append(
                {
                    "backup_file": backup_path.name,
                    "backup_timestamp": backup_data.get("timestamp", "unknown"),
                    "thread_id": backup_data.get("thread_id", ""),
                    "topic": backup_data.get("topic", ""),
                    "total_messages_in_backup": len(messages),
                    "token_count_at_compaction": backup_data.get("token_count", 0),
                    "matches": matches,
                }
            )

    total_matches = sum(len(r["matches"]) for r in all_results)

    # Trim to max_results
    if total_matches > max_results:
        remaining = max_results
        for result in all_results:
            if remaining <= 0:
                result["matches"] = []
            elif len(result["matches"]) > remaining:
                result["matches"] = result["matches"][:remaining]
                remaining = 0
            else:
                remaining -= len(result["matches"])
        all_results = [r for r in all_results if r["matches"]]
        total_matches = max_results

    return {
        "results": all_results,
        "summary": {
            "total_matches": total_matches,
            "backups_searched": backups_searched,
            "backups_available": len(backup_files),
            "query": query,
            "message": (
                f"Found {total_matches} matching messages across {backups_searched} backup files."
                if total_matches > 0
                else "No matches found in context backups."
            ),
        },
    }
