"""
cortex/hippo/tools/grep.py — hippo_grep tool implementation.

Full port of tools/lcm-grep-tool.ts from lossless-claw.
Issues: #806, #1117
"""

from __future__ import annotations

from datetime import datetime
from typing import Any, Optional

from cortex.hippo.compaction import format_timestamp
from cortex.hippo.retrieval import GrepInput, GrepResult, RetrievalEngine
from cortex.hippo.tools.common import json_result
from cortex.hippo.tools.conversation_scope import (
    LcmConversationScope,
    parse_iso_timestamp_param,
)

MAX_RESULT_CHARS = 40_000  # ~10k tokens

HIPPO_GREP_TOOL = {
    "name": "hippo_grep",
    "description": (
        "Search compacted conversation history using regex, full-text, or semantic search. "
        "Searches across messages and/or summaries stored by Hippo LCM. "
        "Use this to find specific content that may have been compacted away from active context. "
        "Returns matching snippets with their summary/message IDs for follow-up with hippo_expand."
    ),
    "input_schema": {
        "type": "object",
        "properties": {
            "pattern": {
                "type": "string",
                "description": "Search pattern (regex, keywords, or natural language for semantic).",
            },
            "mode": {
                "type": "string",
                "enum": ["regex", "full_text", "semantic"],
                "default": "regex",
                "description": (
                    "Search mode: 'regex' for pattern matching (LIKE fallback), "
                    "'full_text' for FTS5 BM25 ranked search, "
                    "'semantic' for vector similarity search."
                ),
            },
            "scope": {
                "type": "string",
                "enum": ["messages", "summaries", "both"],
                "default": "both",
                "description": "What to search: messages, summaries, or both.",
            },
            "conversationId": {
                "type": "integer",
                "description": "Restrict search to a specific conversation.",
            },
            "allConversations": {
                "type": "boolean",
                "default": False,
                "description": "Search across all conversations (requires owner_id).",
            },
            "since": {
                "type": "string",
                "format": "date-time",
                "description": "Only results created at or after this ISO timestamp.",
            },
            "before": {
                "type": "string",
                "format": "date-time",
                "description": "Only results created before this ISO timestamp.",
            },
            "limit": {
                "type": "integer",
                "default": 50,
                "maximum": 200,
                "minimum": 1,
                "description": "Maximum number of results to return.",
            },
        },
        "required": ["pattern"],
    },
}


def truncate_snippet(content: str, max_len: int = 200) -> str:
    """Truncate content to a single-line snippet."""
    single_line = content.replace("\n", " ").strip()
    if len(single_line) <= max_len:
        return single_line
    return single_line[: max_len - 3] + "..."


async def execute_grep(
    retrieval: RetrievalEngine,
    params: dict[str, Any],
    conversation_scope: LcmConversationScope,
    timezone: str = "UTC",
) -> dict[str, Any]:
    """Execute the grep tool logic and return a tool result dict.

    This is the core implementation, separated from tool registration
    for testability.
    """
    pattern = str(params.get("pattern", "")).strip()
    mode = params.get("mode", "regex")
    scope = params.get("scope", "both")
    raw_limit = params.get("limit", 50)
    limit = int(raw_limit) if isinstance(raw_limit, (int, float)) else 50

    # H-4: Pattern length limit (#1181)
    if len(pattern) > 500:
        return json_result({"error": "Pattern too long (max 500 characters)."})

    # Parse time filters
    since: Optional[datetime] = None
    before: Optional[datetime] = None
    try:
        since = parse_iso_timestamp_param(params, "since")
        before = parse_iso_timestamp_param(params, "before")
    except ValueError as exc:
        return json_result({"error": str(exc)})

    if since and before and since >= before:
        return json_result({"error": "`since` must be earlier than `before`."})

    # Validate conversation scope
    if not conversation_scope.all_conversations and conversation_scope.conversation_id is None:
        return json_result({
            "error": (
                "No LCM conversation found for this session. "
                "Provide conversationId or set allConversations=true."
            ),
        })

    # Execute grep
    result: GrepResult = await retrieval.grep(
        GrepInput(
            pattern=pattern,
            mode=mode,
            scope=scope,
            conversation_id=conversation_scope.conversation_id,
            limit=limit,
            since=since,
            before=before,
        )
    )

    # Format output as markdown
    lines: list[str] = []
    lines.append("## LCM Grep Results")
    lines.append(f"**Pattern:** `{pattern}`")
    lines.append(f"**Mode:** {mode} | **Scope:** {scope}")

    if conversation_scope.all_conversations:
        lines.append("**Conversation scope:** all conversations")
    elif conversation_scope.conversation_id is not None:
        lines.append(f"**Conversation scope:** {conversation_scope.conversation_id}")

    if since or before:
        since_str = format_timestamp(since.isoformat(), timezone) if since else "since -∞"
        before_str = format_timestamp(before.isoformat(), timezone) if before else "before +∞"
        lines.append(f"**Time filter:** {since_str} | {before_str}")

    lines.append(f"**Total matches:** {result.total_matches}")
    lines.append("")

    current_chars = sum(len(line) for line in lines) + len(lines)  # +newlines

    if result.messages:
        lines.append("### Messages")
        lines.append("")
        for msg in result.messages:
            snippet = truncate_snippet(msg.content)
            ts = format_timestamp(msg.created_at, timezone)
            line = f"- [msg#{msg.message_id}] ({msg.role}, {ts}): {snippet}"
            if current_chars + len(line) > MAX_RESULT_CHARS:
                lines.append("*(truncated — more results available)*")
                break
            lines.append(line)
            current_chars += len(line)
        lines.append("")

    if result.summaries:
        lines.append("### Summaries")
        lines.append("")
        for summary in result.summaries:
            snippet = truncate_snippet(summary.content)
            ts = format_timestamp(summary.created_at, timezone)
            line = f"- [{summary.summary_id}] ({summary.kind}, {ts}): {snippet}"
            if current_chars + len(line) > MAX_RESULT_CHARS:
                lines.append("*(truncated — more results available)*")
                break
            lines.append(line)
            current_chars += len(line)
        lines.append("")

    if result.total_matches == 0:
        lines.append("No matches found.")

    return {
        "content": [{"type": "text", "text": "\n".join(lines)}],
        "details": {
            "messageCount": len(result.messages),
            "summaryCount": len(result.summaries),
            "totalMatches": result.total_matches,
        },
    }
