# Familiar - Self-Hosted AI Companion Platform
# Copyright (c) 2026 George Scott Foley
# ORCID: 0009-0006-4957-0540
# Email: Georgescottfoley@proton.me
# Licensed under the MIT License - see LICENSE file for details

"""
Smart Search / Unified Query Skill

Cross-skill search: one query searches all data stores simultaneously.
Recent activity feed. Quick entity lookup. AI-routed queries.

Dependencies: None (reads JSON data stores directly)
"""

import json
import logging
from datetime import datetime, timedelta
from pathlib import Path


def _get_data_dir():
    """Get data directory (tenant-scoped in Reflection, default in standalone)."""
    try:
        from familiar.core import paths

        return paths.DATA_DIR
    except ImportError:
        return Path.home() / ".familiar" / "data"


logger = logging.getLogger(__name__)

DATA_DIR = _get_data_dir()


def _load_store(filename):
    path = DATA_DIR / filename
    if path.exists():
        try:
            with open(path, encoding="utf-8") as f:
                return json.load(f)
        except (json.JSONDecodeError, IOError) as e:
            logger.debug("JSON parse error suppressed: %s", e)
    return {}


def _text_search(obj, terms):
    """Recursively search a dict/list for query terms. Returns match score."""
    if isinstance(obj, str):
        text = obj.lower()
        return sum(3 if t in text else 0 for t in terms)
    elif isinstance(obj, dict):
        return sum(_text_search(v, terms) for v in obj.values())
    elif isinstance(obj, list):
        return sum(_text_search(item, terms) for item in obj[:20])
    return 0


def _extract_timestamp(record):
    """Extract the most recent timestamp from a record."""
    for field in ["updated_at", "created_at", "date", "start", "timestamp", "last_gift"]:
        val = record.get(field, "")
        if val and isinstance(val, str) and len(val) >= 10:
            return val
    return ""


def _format_result(record, store_name, record_type):
    """Format a search result for display."""
    name = (
        record.get("name")
        or record.get("title")
        or record.get("subject")
        or record.get("description")
        or record.get("message")
        or str(record.get("id", ""))[:20]
    )
    if isinstance(name, str) and len(name) > 60:
        name = name[:57] + "..."
    ts = _extract_timestamp(record)[:10]
    rid = record.get("id", "")
    return {
        "name": name,
        "store": store_name,
        "type": record_type,
        "id": rid,
        "date": ts,
        "record": record,
    }


# === Data Store Registry ===

STORES = {
    "contacts": {"file": "contacts.json", "key": "contacts", "label": "Contact"},
    "donors": {"file": "nonprofit.json", "key": "donors", "label": "Donor"},
    "grants": {"file": "nonprofit.json", "key": "grants", "label": "Grant"},
    "tasks": {"file": "tasks.json", "key": None, "label": "Task"},  # tasks.json is a list
    "transactions": {"file": "bookkeeping.json", "key": "transactions", "label": "Transaction"},
    "meetings": {"file": "meetings.json", "key": "meetings", "label": "Meeting"},
    "knowledge": {"file": "knowledge_base.json", "key": "entries", "label": "Knowledge"},
    "notifications": {"file": "notifications.json", "key": "history", "label": "Notification"},
    "workflows": {"file": "workflows.json", "key": "runs", "label": "Workflow Run"},
}


def _get_all_records():
    """Load all records from all stores."""
    all_records = []
    for store_name, info in STORES.items():
        data = _load_store(info["file"])
        if info["key"]:
            records = data.get(info["key"], [])
        else:
            records = data if isinstance(data, list) else []
        for r in records:
            if isinstance(r, dict):
                all_records.append((store_name, info["label"], r))
    return all_records


# === Tool Handlers ===


def smart_search(data):
    """AI-routed search: determines which stores are most relevant and searches them."""
    query = data.get("query", "").strip()
    if not query:
        return "Please provide a search query."

    terms = [t.lower() for t in query.split() if len(t) > 1]

    # Route to relevant stores based on query keywords
    routing_hints = {
        "contacts": ["contact", "phone", "email", "address", "person", "people"],
        "donors": ["donor", "gift", "donation", "giving", "stewardship", "thank"],
        "grants": ["grant", "funder", "deadline", "proposal", "application", "loi"],
        "tasks": ["task", "todo", "action", "deadline", "overdue", "pending", "assign"],
        "transactions": ["income", "expense", "payment", "budget", "financial", "money", "receipt"],
        "meetings": ["meeting", "agenda", "minutes", "attendee", "board", "schedule"],
        "knowledge": ["note", "reference", "snippet", "knowledge", "saved", "bookmark"],
    }

    # Score each store by routing relevance
    store_scores = {}
    for store, hints in routing_hints.items():
        score = sum(1 for t in terms if any(t in h for h in hints))
        store_scores[store] = score

    # If no strong routing signal, search all
    max_score = max(store_scores.values()) if store_scores else 0
    if max_score == 0:
        target_stores = list(STORES.keys())
    else:
        # Search stores with any routing match, plus always include top matches
        target_stores = [s for s, sc in store_scores.items() if sc > 0]
        if len(target_stores) < 3:
            target_stores = list(STORES.keys())

    results = []
    for store_name in target_stores:
        info = STORES[store_name]
        data_store = _load_store(info["file"])
        if info["key"]:
            records = data_store.get(info["key"], [])
        else:
            records = data_store if isinstance(data_store, list) else []

        for r in records:
            if not isinstance(r, dict):
                continue
            score = _text_search(r, terms)
            if score > 0:
                result = _format_result(r, store_name, info["label"])
                result["score"] = score
                # Boost routed stores
                if store_scores.get(store_name, 0) > 0:
                    result["score"] += 5
                results.append(result)

    results.sort(key=lambda x: -x["score"])
    limit = min(data.get("limit", 15), 50)
    results = results[:limit]

    if not results:
        return f"No results found for: {query}"

    lines = [f"🔍 Smart Search: {query} ({len(results)} results)\n"]
    for r in results:
        lines.append(f"  [{r['type']}] {r['name']}")
        detail_parts = []
        if r["date"]:
            detail_parts.append(r["date"])
        if r["id"]:
            detail_parts.append(f"ID:{r['id']}")
        if detail_parts:
            lines.append(f"    {' | '.join(detail_parts)}")

    return "\n".join(lines)


def search_everything(data):
    """Brute-force full-text search across all data stores simultaneously."""
    query = data.get("query", "").strip()
    if not query:
        return "Please provide a search query."

    terms = [t.lower() for t in query.split() if len(t) > 1]
    limit = min(data.get("limit", 20), 100)

    all_records = _get_all_records()
    results = []

    for store_name, label, record in all_records:
        score = _text_search(record, terms)
        if score > 0:
            result = _format_result(record, store_name, label)
            result["score"] = score
            results.append(result)

    results.sort(key=lambda x: -x["score"])
    results = results[:limit]

    if not results:
        return f"No results found across any data store for: {query}"

    # Group by store
    by_store = {}
    for r in results:
        by_store.setdefault(r["store"], []).append(r)

    lines = [
        f"🔍 Search Everything: {query} ({len(results)} results across {len(by_store)} stores)\n"
    ]
    for store, store_results in by_store.items():
        lines.append(f"  📁 {store.upper()} ({len(store_results)}):")
        for r in store_results:
            date_str = f" ({r['date']})" if r["date"] else ""
            lines.append(f"    • {r['name']}{date_str}")

    return "\n".join(lines)


def recent_activity(data):
    """Activity feed: recent changes across all skills."""
    hours = data.get("hours", 24)
    limit = min(data.get("limit", 30), 200)
    cutoff = (datetime.now() - timedelta(hours=hours)).isoformat()

    all_records = _get_all_records()
    recent = []

    for store_name, label, record in all_records:
        ts = _extract_timestamp(record)
        if ts and ts >= cutoff[: len(ts)]:
            result = _format_result(record, store_name, label)
            result["timestamp"] = ts
            recent.append(result)

    recent.sort(key=lambda x: x.get("timestamp", ""), reverse=True)
    recent = recent[:limit]

    if not recent:
        return f"No activity in the last {hours} hours."

    by_store = {}
    for r in recent:
        by_store.setdefault(r["store"], []).append(r)

    lines = [f"📊 Recent Activity (last {hours}h, {len(recent)} items):\n"]
    for r in recent:
        ts = r.get("timestamp", "")[:16].replace("T", " ")
        emoji_map = {
            "Contact": "👤",
            "Donor": "💝",
            "Grant": "📋",
            "Task": "✅",
            "Transaction": "💰",
            "Meeting": "📅",
            "Knowledge": "📚",
            "Notification": "🔔",
            "Workflow Run": "🔄",
        }
        emoji = emoji_map.get(r["type"], "•")
        lines.append(f"  {emoji} {ts} [{r['type']}] {r['name']}")

    lines.append(f"\n  Summary: {', '.join(f'{s}({len(rs)})' for s, rs in by_store.items())}")

    return "\n".join(lines)


def quick_lookup(data):
    """Fast entity lookup by name or ID across all skills."""
    query = data.get("query", "").strip()
    if not query:
        return "Please provide a name or ID to look up."

    q_lower = query.lower()

    all_records = _get_all_records()
    matches = []

    for store_name, label, record in all_records:
        # Exact or near-exact match on key fields
        match_score = 0
        for field in ["name", "title", "id", "email", "ein", "vendor", "source"]:
            val = str(record.get(field, "")).lower()
            if val == q_lower:
                match_score = 100  # Exact match
            elif q_lower in val and len(q_lower) >= 3:
                match_score = max(match_score, 50)

        if match_score > 0:
            matches.append((match_score, store_name, label, record))

    matches.sort(key=lambda x: -x[0])
    matches = matches[:10]

    if not matches:
        return f"No entity found matching: {query}"

    lines = [f"🔎 Quick Lookup: {query}\n"]
    for score, store, label, record in matches:
        lines.append(f"  [{label}] {record.get('name') or record.get('title', 'Untitled')}")

        # Show key details based on type
        details = []
        for field in [
            "email",
            "phone",
            "organization",
            "status",
            "amount",
            "total_given",
            "date",
            "priority",
            "fund",
            "category",
        ]:
            val = record.get(field)
            if val and str(val).strip():
                details.append(f"{field}: {val}")
        if details:
            lines.append(f"    {' | '.join(details[:4])}")

        if record.get("id"):
            lines.append(f"    ID: {record['id']} (in {store})")

    return "\n".join(lines)


# === Tool Definitions ===

TOOLS = [
    {
        "name": "smart_search",
        "description": (
            "AI-routed search across all data stores. "
            "Analyzes the query to determine which stores (contacts, donors, grants, tasks, etc.) are most relevant, then searches them with boosted ranking. "
            "Use this for natural-language questions when you are unsure which store to target. "
            "Returns results sorted by relevance score."
        ),
        "input_schema": {
            "type": "object",
            "properties": {
                "query": {"type": "string", "description": "Search query (natural language)"},
                "limit": {
                    "type": "integer",
                    "default": 15,
                    "description": "Maximum number of results to return",
                },
            },
            "required": ["query"],
        },
        "handler": smart_search,
        "category": "smart_search",
    },
    {
        "name": "search_everything",
        "description": (
            "Full-text search across ALL data stores simultaneously including contacts, donors, grants, tasks, transactions, meetings, knowledge, and notifications. "
            "Use this when you need exhaustive results rather than AI-routed relevance. "
            "Results are grouped by store and sorted by match score."
        ),
        "input_schema": {
            "type": "object",
            "properties": {
                "query": {"type": "string", "description": "Search terms"},
                "limit": {
                    "type": "integer",
                    "default": 20,
                    "description": "Maximum number of results to return",
                },
            },
            "required": ["query"],
        },
        "handler": search_everything,
        "category": "smart_search",
    },
    {
        "name": "recent_activity",
        "description": (
            "Activity feed showing recent changes across all data stores within the last N hours. "
            "Scans contacts, donors, grants, tasks, transactions, meetings, knowledge, and notifications for recent timestamps. "
            "Use this to see what changed today or catch up after being away. "
            "Results are sorted newest-first with a per-store summary."
        ),
        "input_schema": {
            "type": "object",
            "properties": {
                "hours": {"type": "integer", "default": 24, "description": "Look back N hours"},
                "limit": {
                    "type": "integer",
                    "default": 30,
                    "description": "Maximum number of results to return",
                },
            },
        },
        "handler": recent_activity,
        "category": "smart_search",
    },
    {
        "name": "quick_lookup",
        "description": (
            "Fast entity lookup by name, email, or ID across all data stores. "
            "Performs exact and substring matching on key fields like name, title, id, and email. "
            "Use this when you know the entity you want rather than searching broadly. "
            "Returns up to 10 matches with key details and record IDs."
        ),
        "input_schema": {
            "type": "object",
            "properties": {
                "query": {"type": "string", "description": "Name, email, or ID to look up"},
            },
            "required": ["query"],
        },
        "handler": quick_lookup,
        "category": "smart_search",
    },
]
