#!/usr/bin/env python3
"""
Madpod agent hook: activity-logger
Sends tool activity events via the Madpod WebSocket channel.
Also writes a local JSONL backup to /mnt/agent/{AGENT_ID}/logs/activity.jsonl

Usage: set as madpod-agent hook for PreToolUse, PostToolUse, OnError events.
"""

from __future__ import annotations

import json
import logging
import os
import sys
from datetime import datetime, timezone
from pathlib import Path
from typing import Any

logging.basicConfig(level=logging.WARNING)
logger = logging.getLogger("activity-logger")

AGENT_ID = os.environ.get("MADPOD_AGENT_ID", "unknown")
LOG_DIR = Path(f"/mnt/agent/{AGENT_ID}/logs")


def _ensure_log_dir() -> None:
    try:
        LOG_DIR.mkdir(parents=True, exist_ok=True)
    except Exception:
        pass


def _write_local_log(event: dict) -> None:
    """Write event to local JSONL backup file."""
    try:
        _ensure_log_dir()
        log_file = LOG_DIR / "activity.jsonl"
        with open(log_file, "a") as f:
            f.write(json.dumps(event) + "\n")
    except Exception as e:
        logger.debug(f"Failed to write local log: {e}")


def _send_via_channel(event: dict) -> None:
    """
    Send event via the Madpod channel socket.
    The agent process exposes a local unix socket for hook communication.
    """
    channel_socket = os.environ.get("MADPOD_CHANNEL_SOCKET", f"/tmp/madpod-{AGENT_ID}.sock")
    try:
        import socket as sock_module
        with sock_module.socket(sock_module.AF_UNIX, sock_module.SOCK_STREAM) as s:
            s.settimeout(2.0)
            s.connect(channel_socket)
            s.sendall(json.dumps(event).encode() + b"\n")
    except Exception as e:
        logger.debug(f"Failed to send via channel socket: {e}")
        # Not fatal — local log is the backup


def process_hook_event(raw_input: str) -> None:
    """Process a hook event from madpod_agent."""
    try:
        data = json.loads(raw_input)
    except json.JSONDecodeError as e:
        logger.debug(f"Failed to parse hook input: {e}")
        return

    hook_type = data.get("hook_event_name", "unknown")
    tool_name = data.get("tool_name", "")
    tool_input = data.get("tool_input", {})
    tool_result = data.get("tool_response", {})
    error = data.get("error", None)

    event: dict[str, Any] = {
        "type": "activity",
        "agent_id": AGENT_ID,
        "timestamp": datetime.now(timezone.utc).isoformat(),
        "hook_type": hook_type,
        "tool_name": tool_name,
    }

    if hook_type == "PreToolUse":
        event["action"] = "tool_start"
        event["tool_input"] = _sanitize_tool_payload(tool_input)

    elif hook_type == "PostToolUse":
        sanitized_input = _sanitize_tool_payload(tool_input)
        sanitized_result = _sanitize_tool_payload(tool_result)
        event["tool_input"] = sanitized_input
        event["tool_result"] = sanitized_result
        event["success"] = not bool(error)

        if error:
            event["action"] = "tool_error"
            event["error"] = str(error)[:500]
        else:
            semantic = _classify_semantic_activity(tool_name, tool_input, tool_result)
            if semantic:
                event["action"] = semantic["action"]
                event["summary"] = semantic["summary"]
                event["chat_visible"] = True
                semantic_hint = semantic.get("semantic_hint")
                if semantic_hint:
                    sanitized_input["semantic_hint"] = semantic_hint
                    event["tool_input"] = sanitized_input
            else:
                event["action"] = "tool_complete"
                semantic_hint = _extract_semantic_hint(tool_name, tool_input)
                if semantic_hint:
                    sanitized_input["semantic_hint"] = semantic_hint
                    event["tool_input"] = sanitized_input

    elif hook_type == "OnError":
        event["action"] = "tool_error"
        event["error"] = str(error)[:500] if error else "unknown error"

    _write_local_log(event)
    _send_via_channel(event)
    print(json.dumps({"continue": True}))


def _clip(value: str | None, limit: int = 120) -> str:
    text = (value or "").strip()
    if len(text) <= limit:
        return text
    return text[: limit - 1].rstrip() + "..."


def _sanitize_tool_payload(value: Any) -> dict[str, Any]:
    """Remove sensitive fields from tool input/output before logging."""
    if not isinstance(value, dict):
        return {}
    sanitized = dict(value)
    for key in ("password", "token", "secret", "key", "api_key", "authorization"):
        if key in sanitized:
            sanitized[key] = "[REDACTED]"
    for key, val in list(sanitized.items()):
        if isinstance(val, str) and len(val) > 500:
            sanitized[key] = val[:500] + "...[truncated]"
    return sanitized


def _extract_domain_label(url: str) -> str:
    try:
        from urllib.parse import urlparse

        parsed = urlparse(url)
        domain = parsed.hostname or ""
        path = parsed.path or ""
        if domain.startswith("www."):
            domain = domain[4:]
        label = f"{domain}{path}" if domain else url
    except Exception:
        label = url
    return _clip(label, 120)


def _browser_target(tool_input: dict[str, Any]) -> str | None:
    for candidate in (
        tool_input.get("label"),
        tool_input.get("text"),
        tool_input.get("ref"),
        tool_input.get("selector"),
        tool_input.get("element"),
    ):
        if isinstance(candidate, str) and candidate.strip():
            return _clip(candidate, 90)
    return None


def _collect_text_fragments(tool_name: str, tool_input: dict[str, Any], tool_result: dict[str, Any]) -> str:
    parts = [tool_name.lower()]
    for source in (tool_input, tool_result):
        for value in source.values():
            if isinstance(value, str):
                parts.append(value.lower())
            elif isinstance(value, list):
                parts.extend(str(item).lower() for item in value[:5])
    return " ".join(parts)


def _extract_lead_summary(
    tool_name: str,
    tool_input: dict[str, Any],
    tool_result: dict[str, Any],
) -> str | None:
    context_blob = _collect_text_fragments(tool_name, tool_input, tool_result)
    lead_keywords = ("lead", "prospect", "contact", "buyer", "founder", "recruiter", "candidate", "outreach")
    has_lead_context = any(keyword in context_blob for keyword in lead_keywords)
    has_contact_fields = any(
        key in tool_result
        for key in ("email", "linkedin_url", "linkedin", "profile_url")
    )
    name = str(
        tool_result.get("name")
        or tool_result.get("full_name")
        or tool_result.get("lead_name")
        or ""
    ).strip()
    company = str(
        tool_result.get("company")
        or tool_result.get("company_name")
        or tool_result.get("organization")
        or ""
    ).strip()
    title = str(tool_result.get("title") or tool_result.get("role") or "").strip()
    has_identity_fields = bool(name and (company or title))
    if not has_lead_context and not has_contact_fields and not has_identity_fields:
        return None
    if not any([name, company, title]):
        return None

    if name and company and title:
        return f"Found lead {_clip(name, 80)}, {_clip(title, 80)} at {_clip(company, 80)}."
    if name and company:
        return f"Found lead {_clip(name, 80)} at {_clip(company, 80)}."
    if name:
        return f"Found lead {_clip(name, 80)}."
    if company:
        return f"Found a lead at {_clip(company, 80)}."
    return None


def _extract_draft_summary(
    tool_name: str,
    tool_input: dict[str, Any],
    tool_result: dict[str, Any],
) -> str | None:
    context_blob = _collect_text_fragments(tool_name, tool_input, tool_result)
    draft_keywords = ("draft", "message", "email", "outreach", "reply", "subject", "body", "dm", "connection note")
    if not any(keyword in context_blob for keyword in draft_keywords):
        return None

    body_candidates = [
        tool_input.get("text"),
        tool_input.get("content"),
        tool_input.get("body"),
        tool_result.get("draft"),
        tool_result.get("content"),
        tool_result.get("body"),
    ]
    has_body = any(isinstance(value, str) and len(value.strip()) >= 40 for value in body_candidates)
    target = str(
        tool_input.get("recipient")
        or tool_input.get("lead_name")
        or tool_result.get("recipient")
        or tool_result.get("lead_name")
        or ""
    ).strip()
    if not has_body and not target:
        return None
    if target:
        return f"Drafted a message for {_clip(target, 80)}."
    return "Drafted a message."


def _extract_data_collection_summary(
    tool_name: str,
    tool_input: dict[str, Any],
    tool_result: dict[str, Any],
) -> str | None:
    """Detect when agent collects or saves structured data."""
    name_lower = tool_name.lower()
    context_blob = _collect_text_fragments(tool_name, tool_input, tool_result)
    data_keywords = ("scrape", "extract", "collect", "download", "export", "csv", "json", "spreadsheet", "dataset", "table", "row", "record")
    if not any(keyword in context_blob for keyword in data_keywords):
        return None
    # Check for file write or structured result
    filename = str(tool_input.get("filename") or tool_input.get("path") or tool_result.get("filename") or "").strip()
    count = tool_result.get("count") or tool_result.get("total") or tool_result.get("rows")
    if filename:
        label = _clip(filename, 80)
        if count:
            return f"Collected {count} records into {label}."
        return f"Saved data to {label}."
    if count:
        return f"Collected {count} records."
    if name_lower in ("write_file", "edit_file") and any(keyword in context_blob for keyword in data_keywords):
        return "Saved collected data to a file."
    return None


def _extract_content_creation_summary(
    tool_name: str,
    tool_input: dict[str, Any],
    tool_result: dict[str, Any],
) -> str | None:
    """Detect when agent creates content (articles, posts, documents)."""
    context_blob = _collect_text_fragments(tool_name, tool_input, tool_result)
    content_keywords = ("article", "blog", "post", "write", "publish", "document", "report", "memo", "essay", "script", "copy", "newsletter")
    if not any(keyword in context_blob for keyword in content_keywords):
        return None
    body_candidates = [
        tool_input.get("text"),
        tool_input.get("content"),
        tool_input.get("body"),
        tool_result.get("content"),
        tool_result.get("body"),
        tool_result.get("text"),
    ]
    has_body = any(isinstance(value, str) and len(value.strip()) >= 100 for value in body_candidates)
    if not has_body:
        return None
    title = str(
        tool_input.get("title")
        or tool_result.get("title")
        or tool_input.get("subject")
        or ""
    ).strip()
    if title:
        return f"Created content: {_clip(title, 80)}."
    return "Created written content."


def _classify_semantic_activity(
    tool_name: str,
    tool_input: Any,
    tool_result: Any,
) -> dict[str, str] | None:
    if not isinstance(tool_input, dict):
        tool_input = {}
    if not isinstance(tool_result, dict):
        tool_result = {}

    lead_summary = _extract_lead_summary(tool_name, tool_input, tool_result)
    if lead_summary:
        return {
            "action": "lead_found",
            "summary": lead_summary,
            "semantic_hint": lead_summary,
        }

    draft_summary = _extract_draft_summary(tool_name, tool_input, tool_result)
    if draft_summary:
        return {
            "action": "message_drafted",
            "summary": draft_summary,
            "semantic_hint": draft_summary,
        }

    data_summary = _extract_data_collection_summary(tool_name, tool_input, tool_result)
    if data_summary:
        return {
            "action": "data_collected",
            "summary": data_summary,
            "semantic_hint": data_summary,
        }

    content_summary = _extract_content_creation_summary(tool_name, tool_input, tool_result)
    if content_summary:
        return {
            "action": "content_created",
            "summary": content_summary,
            "semantic_hint": content_summary,
        }

    name_lower = tool_name.lower()

    if name_lower in ("browser_navigate", "navigate"):
        url = str(tool_input.get("url") or tool_input.get("uri") or "").strip()
        if url:
            label = _extract_domain_label(url)
            return {
                "action": "browser_visit",
                "summary": f"Visited {label}.",
                "semantic_hint": label,
            }

    if name_lower in ("browser_click", "click"):
        target = _browser_target(tool_input)
        return {
            "action": "browser_click",
            "summary": f"Clicked {target or 'an element'}.",
            "semantic_hint": f"Clicked: {target}" if target else "Clicked an element",
        }

    if name_lower in ("browser_type", "type"):
        label = _browser_target(tool_input) or "a field"
        return {
            "action": "browser_type",
            "summary": f"Typed into {label}.",
            "semantic_hint": f"Typed into {label}",
        }

    if name_lower in ("web_search",):
        query = str(tool_input.get("query") or tool_input.get("search_query") or "").strip()
        if query:
            clipped = _clip(query, 120)
            return {
                "action": "web_search",
                "summary": f"Searched the web for {clipped}.",
                "semantic_hint": f"Searched: {clipped}",
            }
        return {
            "action": "web_search",
            "summary": "Ran a web search.",
        }

    return None


def _extract_semantic_hint(tool_name: str, tool_input: Any) -> str:
    """Extract a concise semantic description from browser tool calls."""
    if not isinstance(tool_input, dict):
        return ""

    name_lower = tool_name.lower()

    if name_lower in ("browser_navigate", "navigate"):
        url = str(tool_input.get("url") or tool_input.get("uri") or "")
        if url:
            return _extract_domain_label(url)

    if name_lower in ("browser_click", "click"):
        target = _browser_target(tool_input)
        if target:
            return f"Clicked: {target}"

    if name_lower in ("browser_type", "type"):
        label = str(tool_input.get("label") or tool_input.get("selector") or "")
        if "password" in label.lower():
            return f"Typed in: {_clip(label, 60)} [REDACTED]"
        target = _browser_target(tool_input)
        if target:
            return f"Typed into {target}"

    if name_lower in ("web_search",):
        query = str(tool_input.get("query") or tool_input.get("search_query") or "")
        if query:
            return f"Searched: {_clip(query, 100)}"

    return ""


if __name__ == "__main__":
    raw = sys.stdin.read()
    process_hook_event(raw)
