"""
Terminal Broker Search — Cross-channel message search with filtering.

Search across all broker channels by keyword, sender, platform, date range,
or message type.

Usage (CLI):
  python -m brynq.cli search --query "backfill" --platform gemini --limit 10
  python -m brynq.cli search --sender sigma --since 2026-03-15
  python -m brynq.cli search --msg-type alert --channel gen21

Usage (Python):
  from brynq.search import search, format_results
  results = search(query="backfill", platform="gemini", limit=10)
  print(format_results(results))
"""

import json
from datetime import datetime
from pathlib import Path
from typing import Optional

from .server import CHANNELS_DIR


def search(
    query: Optional[str] = None,
    channel: Optional[str] = None,
    sender: Optional[str] = None,
    platform: Optional[str] = None,
    msg_type: Optional[str] = None,
    since: Optional[str] = None,
    until: Optional[str] = None,
    limit: int = 20,
) -> list[dict]:
    """Search across broker channels with flexible filtering.

    Args:
        query: Substring search in message text (case-insensitive).
        channel: Filter to a specific channel name. None searches all channels.
        sender: Filter by session_name (case-insensitive substring match).
        platform: Filter by platform tag (claude, gemini, script, etc.;
            case-insensitive exact match).
        msg_type: Filter by message type (info, alert, request, etc.;
            case-insensitive exact match).
        since: ISO8601 datetime string. Only messages after this time.
        until: ISO8601 datetime string. Only messages before this time.
        limit: Maximum number of results to return. Defaults to 20.

    Returns:
        List of message dicts matching all filters, sorted newest first.
        Each dict has keys: id, timestamp, session_id, session_name,
        platform, channel, msg_type, message.
    """
    # Parse date bounds once
    since_dt = _parse_datetime(since) if since else None
    until_dt = _parse_datetime(until) if until else None

    # Determine which channel files to scan
    if channel:
        channel_file = CHANNELS_DIR / f"{channel}.jsonl"
        files = [channel_file] if channel_file.exists() else []
    else:
        files = sorted(CHANNELS_DIR.glob("*.jsonl"))

    # Collect all matching messages
    matches: list[dict] = []
    query_lower = query.lower() if query else None
    sender_lower = sender.lower() if sender else None
    platform_lower = platform.lower() if platform else None
    msg_type_lower = msg_type.lower() if msg_type else None

    for filepath in files:
        channel_name = filepath.stem
        for entry in _read_all_entries(filepath):
            # Inject channel from filename if missing in entry
            if "channel" not in entry:
                entry["channel"] = channel_name

            if not _matches_filters(
                entry,
                query_lower=query_lower,
                sender_lower=sender_lower,
                platform_lower=platform_lower,
                msg_type_lower=msg_type_lower,
                since_dt=since_dt,
                until_dt=until_dt,
            ):
                continue

            matches.append(entry)

    # Sort by timestamp descending (newest first)
    matches.sort(key=lambda m: m.get("timestamp", ""), reverse=True)

    # Apply limit
    return matches[:limit]


def format_results(results: list[dict]) -> str:
    """Format search results for terminal display.

    Each result is formatted as:
        [timestamp] sender@platform #channel (type): message

    Args:
        results: List of message dicts from search().

    Returns:
        Formatted string for terminal output. Returns a 'no results'
        message if the list is empty.
    """
    if not results:
        return "No messages matched the search criteria."

    lines = [f"Search results ({len(results)} match(es)):", ""]
    for m in results:
        ts = m.get("timestamp", "")[:19]
        name = m.get("session_name", "unknown")
        plat = m.get("platform", "?")
        ch = m.get("channel", "?")
        typ = m.get("msg_type", "info")
        msg = m.get("message", "")
        lines.append(f"  [{ts}] {name}@{plat} #{ch} ({typ}): {msg}")

    return "\n".join(lines)


# ── Internal helpers ──────────────────────────────────────────────────────


def _read_all_entries(path: Path) -> list[dict]:
    """Read all JSON entries from a JSONL file, skipping malformed lines."""
    if not path.exists():
        return []
    entries = []
    try:
        text = path.read_text("utf-8").strip()
    except OSError:
        return []
    if not text:
        return []
    for line in text.splitlines():
        line = line.strip()
        if not line:
            continue
        try:
            entries.append(json.loads(line))
        except json.JSONDecodeError:
            continue
    return entries


def _parse_datetime(dt_str: str) -> Optional[datetime]:
    """Parse an ISO8601 datetime string, returning None on failure.

    If the parsed datetime is timezone-naive, it is assumed to be UTC.
    """
    try:
        from datetime import timezone

        dt = datetime.fromisoformat(dt_str)
        if dt.tzinfo is None:
            dt = dt.replace(tzinfo=timezone.utc)
        return dt
    except (ValueError, TypeError):
        return None


def _matches_filters(
    entry: dict,
    query_lower: Optional[str],
    sender_lower: Optional[str],
    platform_lower: Optional[str],
    msg_type_lower: Optional[str],
    since_dt: Optional[datetime],
    until_dt: Optional[datetime],
) -> bool:
    """Check whether a message entry passes all active filters."""
    # Query: substring in message text
    if query_lower is not None:
        msg_text = entry.get("message", "")
        if query_lower not in msg_text.lower():
            return False

    # Sender: substring in session_name
    if sender_lower is not None:
        session_name = entry.get("session_name", "")
        if sender_lower not in session_name.lower():
            return False

    # Platform: exact match (case-insensitive)
    if platform_lower is not None:
        entry_platform = entry.get("platform", "")
        if entry_platform.lower() != platform_lower:
            return False

    # Message type: exact match (case-insensitive)
    if msg_type_lower is not None:
        entry_type = entry.get("msg_type", "")
        if entry_type.lower() != msg_type_lower:
            return False

    # Date range: since
    if since_dt is not None:
        entry_ts = _parse_datetime(entry.get("timestamp", ""))
        if entry_ts is None or entry_ts < since_dt:
            return False

    # Date range: until
    if until_dt is not None:
        entry_ts = _parse_datetime(entry.get("timestamp", ""))
        if entry_ts is None or entry_ts > until_dt:
            return False

    return True


# ── CLI integration ───────────────────────────────────────────────────────


def add_search_subparser(subparsers) -> None:
    """Add the 'search' subcommand to an argparse subparsers object.

    Args:
        subparsers: The subparsers object from argparse.ArgumentParser.add_subparsers().
    """
    p = subparsers.add_parser("search", help="Search messages across channels")
    p.add_argument(
        "--query", "-q",
        default=None,
        help="Substring search in message text (case-insensitive)",
    )
    p.add_argument(
        "--channel", "-c",
        default=None,
        help="Filter to a specific channel (default: all)",
    )
    p.add_argument(
        "--sender", "-s",
        default=None,
        help="Filter by session name (substring match)",
    )
    p.add_argument(
        "--platform", "-p",
        default=None,
        help="Filter by platform (claude, gemini, script, etc.)",
    )
    p.add_argument(
        "--msg-type", "-t",
        default=None,
        help="Filter by message type (info, alert, request, etc.)",
    )
    p.add_argument(
        "--since",
        default=None,
        help="Only messages after this ISO8601 datetime",
    )
    p.add_argument(
        "--until",
        default=None,
        help="Only messages before this ISO8601 datetime",
    )
    p.add_argument(
        "--limit", "-n",
        type=int,
        default=20,
        help="Max results to return (default: 20)",
    )


def cmd_search(args) -> None:
    """CLI handler for the search subcommand.

    Args:
        args: Parsed argparse namespace with search filter fields.
    """
    results = search(
        query=args.query,
        channel=args.channel,
        sender=args.sender,
        platform=args.platform,
        msg_type=getattr(args, "msg_type", None),
        since=args.since,
        until=getattr(args, "until", None),
        limit=args.limit,
    )
    print(format_results(results))
