# 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

"""
Notification Routing Skill

Push alerts via Telegram, email, ntfy.sh, or PWA push.
Configuration and history stored in ~/.familiar/data/notifications.json

Channels:
  - telegram: Uses existing bot token from config
  - email: Sends via SMTP (existing email skill config)
  - ntfy: Self-hosted ntfy.sh instance (HIPAA-safe)
  - log: Local log only (always available, default fallback)

Dependencies: None (uses urllib for HTTP, existing SMTP for email)
"""

import json
import logging
import os
import smtplib
import urllib.parse
import urllib.request
from datetime import datetime
from email.mime.text import MIMEText
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"


try:
    from familiar.core.utils import atomic_write_json, generate_id, safe_load_json
except ImportError:
    atomic_write_json = None
    safe_load_json = None
    generate_id = None

logger = logging.getLogger(__name__)


def _get_data_file():
    """Get data file path (re-evaluates for tenant scoping)."""
    return _get_data_dir() / "notifications.json"


DATA_FILE = _get_data_file()  # Default for backward compat

_DEFAULT_DATA = {
    "history": [],
    "rules": [],
    "subscriptions": [],
    "config": {
        "default_channel": "log",
        "quiet_hours_start": "22:00",
        "quiet_hours_end": "07:00",
        "ntfy_url": "https://ntfy.sh",
        "ntfy_topic": "familiar",
    },
    "version": 1,
}


def _load_data():
    if safe_load_json:
        return safe_load_json(
            _get_data_file(), default=lambda: json.loads(json.dumps(_DEFAULT_DATA))
        )
    if _get_data_file().exists():
        try:
            with open(_get_data_file(), encoding="utf-8") as f:
                return json.load(f)
        except (json.JSONDecodeError, IOError) as e:
            logger.debug("JSON parse error suppressed: %s", e)
    return json.loads(json.dumps(_DEFAULT_DATA))


def _save_data(data):
    if atomic_write_json:
        atomic_write_json(_get_data_file(), data)
    else:
        _get_data_file().parent.mkdir(parents=True, exist_ok=True)
        with open(_get_data_file(), "w", encoding="utf-8") as f:
            json.dump(data, f, indent=2)


def _gen_id():
    if generate_id:
        return generate_id()
    import secrets

    return secrets.token_hex(4)


def _is_quiet_hours(config):
    """Check if current time falls within quiet hours."""
    now = datetime.now().strftime("%H:%M")
    start = config.get("quiet_hours_start", "22:00")
    end = config.get("quiet_hours_end", "07:00")
    if start <= end:
        return start <= now <= end
    else:  # Wraps midnight
        return now >= start or now <= end


def _send_telegram(message, title=""):
    """Send via Telegram bot."""
    token = os.environ.get("TELEGRAM_BOT_TOKEN", "")
    chat_id = os.environ.get("TELEGRAM_CHAT_ID", "")
    if not token or not chat_id:
        return False, "Telegram not configured (need TELEGRAM_BOT_TOKEN and TELEGRAM_CHAT_ID)"
    text = f"*{title}*\n{message}" if title else message
    payload = json.dumps({"chat_id": chat_id, "text": text, "parse_mode": "Markdown"}).encode()
    req = urllib.request.Request(
        f"https://api.telegram.org/bot{token}/sendMessage",
        data=payload,
        headers={"Content-Type": "application/json"},
    )
    try:
        urllib.request.urlopen(req, timeout=10)
        return True, "sent"
    except Exception as e:
        return False, str(e)


def _send_email_notification(message, title="Familiar Alert", recipient=None):
    """Send via SMTP — uses primary account from registry if available."""
    try:
        from familiar.skills.email.accounts import get_account

        acct = get_account()  # primary
        if acct:
            addr = acct["address"]
            password = acct["password"]
            smtp_server = acct["smtp_server"]
            smtp_port = acct["smtp_port"]
        else:
            raise ImportError
    except (ImportError, TypeError):
        addr = os.environ.get("EMAIL_ADDRESS", "")
        password = os.environ.get("EMAIL_PASSWORD", "")
        smtp_server = os.environ.get("EMAIL_SMTP_SERVER", "smtp.gmail.com")
        smtp_port = int(os.environ.get("EMAIL_SMTP_PORT", "587"))
    to_addr = recipient or addr
    if not addr or not password:
        return False, "Email not configured (need EMAIL_ADDRESS and EMAIL_PASSWORD)"
    msg = MIMEText(message)
    msg["Subject"] = title
    msg["From"] = addr
    msg["To"] = to_addr
    try:
        with smtplib.SMTP(smtp_server, smtp_port, timeout=15) as server:
            server.starttls()
            server.login(addr, password)
            server.send_message(msg)
        return True, "sent"
    except Exception as e:
        return False, str(e)


def _send_ntfy(message, title="", config=None):
    """Send via ntfy.sh (self-hosted or public)."""
    cfg = config or {}
    url = cfg.get("ntfy_url", "https://ntfy.sh").rstrip("/")
    topic = cfg.get("ntfy_topic", "familiar")
    headers = {"Title": title} if title else {}
    req = urllib.request.Request(
        f"{url}/{topic}", data=message.encode(), headers={**headers, "Content-Type": "text/plain"}
    )
    try:
        urllib.request.urlopen(req, timeout=10)
        return True, "sent"
    except Exception as e:
        return False, str(e)


# === Tool Handlers ===


def send_notification(data):
    """Send alert via configured channel."""
    message = data.get("message", "").strip()
    if not message:
        return "Please provide a notification message."
    title = data.get("title", "Familiar Alert")
    channel = data.get("channel", "").strip().lower()
    priority = data.get("priority", "normal")
    db = _load_data()
    cfg = db.get("config", {})

    if not channel:
        channel = cfg.get("default_channel", "log")

    # Check quiet hours (skip for urgent)
    if priority != "urgent" and _is_quiet_hours(cfg):
        # Queue instead of sending
        entry = {
            "id": _gen_id(),
            "title": title,
            "message": message,
            "channel": channel,
            "priority": priority,
            "status": "queued_quiet_hours",
            "created_at": datetime.now().isoformat(),
        }
        db["history"].append(entry)
        _save_data(db)
        return f"🔇 Notification queued (quiet hours). Will deliver after {cfg.get('quiet_hours_end', '07:00')}. [{entry['id']}]"

    # Send
    success, detail = False, "unknown channel"
    if channel == "telegram":
        success, detail = _send_telegram(message, title)
    elif channel == "email":
        recipient = data.get("recipient")
        success, detail = _send_email_notification(message, title, recipient)
    elif channel == "ntfy":
        success, detail = _send_ntfy(message, title, cfg)
    elif channel == "log":
        logger.info(f"NOTIFICATION [{priority}] {title}: {message}")
        success, detail = True, "logged"
    elif channel == "all":
        results = []
        for ch in ["telegram", "ntfy", "log"]:
            if ch == "telegram":
                s, d = _send_telegram(message, title)
            elif ch == "ntfy":
                s, d = _send_ntfy(message, title, cfg)
            else:
                logger.info(f"NOTIFICATION [{priority}] {title}: {message}")
                s, d = True, "logged"
            results.append(f"{ch}: {'✅' if s else '❌'} {d}")
        success = any("✅" in r for r in results)
        detail = "; ".join(results)

    entry = {
        "id": _gen_id(),
        "title": title,
        "message": message,
        "channel": channel,
        "priority": priority,
        "status": "delivered" if success else "failed",
        "detail": detail,
        "created_at": datetime.now().isoformat(),
    }
    db["history"].append(entry)
    # Keep last 500 entries
    if len(db["history"]) > 500:
        db["history"] = db["history"][-500:]
    _save_data(db)

    emoji = "✅" if success else "❌"
    return f"{emoji} Notification via {channel}: {detail} [{entry['id']}]"


def set_notification_rules(data):
    """Configure rules: what events trigger notifications, which channel, quiet hours."""
    db = _load_data()
    action = data.get("action", "add").lower()

    if action == "set_quiet_hours":
        start = data.get("quiet_hours_start", "22:00")
        end = data.get("quiet_hours_end", "07:00")
        db["config"]["quiet_hours_start"] = start
        db["config"]["quiet_hours_end"] = end
        _save_data(db)
        return f"✅ Quiet hours set: {start} to {end}"

    if action == "set_default_channel":
        channel = data.get("channel", "log")
        db["config"]["default_channel"] = channel
        _save_data(db)
        return f"✅ Default notification channel: {channel}"

    if action == "set_ntfy":
        db["config"]["ntfy_url"] = data.get("ntfy_url", "https://ntfy.sh")
        db["config"]["ntfy_topic"] = data.get("ntfy_topic", "familiar")
        _save_data(db)
        return f"✅ ntfy configured: {db['config']['ntfy_url']}/{db['config']['ntfy_topic']}"

    if action == "add":
        rule = {
            "id": _gen_id(),
            "event": data.get(
                "event", ""
            ),  # e.g. "deadline_approaching", "urgent_email", "donor_gift"
            "channel": data.get("channel", "log"),
            "priority": data.get("priority", "normal"),
            "condition": data.get("condition", ""),  # e.g. "amount > 1000", "days_until < 7"
            "enabled": True,
            "created_at": datetime.now().isoformat(),
        }
        db["rules"].append(rule)
        _save_data(db)
        return f"✅ Rule added: {rule['event']} → {rule['channel']} [{rule['id']}]"

    if action == "remove":
        rule_id = data.get("rule_id", "")
        db["rules"] = [r for r in db["rules"] if r["id"] != rule_id]
        _save_data(db)
        return f"✅ Rule removed: {rule_id}"

    if action == "list":
        rules = db.get("rules", [])
        cfg = db.get("config", {})
        lines = ["🔔 Notification Configuration:\n"]
        lines.append(f"  Default channel: {cfg.get('default_channel', 'log')}")
        lines.append(
            f"  Quiet hours: {cfg.get('quiet_hours_start', '22:00')} – {cfg.get('quiet_hours_end', '07:00')}"
        )
        lines.append(f"  ntfy: {cfg.get('ntfy_url', 'N/A')}/{cfg.get('ntfy_topic', 'N/A')}")
        if rules:
            lines.append(f"\n  Rules ({len(rules)}):")
            for r in rules:
                status = "✅" if r.get("enabled") else "⏸️"
                lines.append(
                    f"    {status} [{r['id']}] {r['event']} → {r['channel']} ({r.get('priority', 'normal')})"
                )
        else:
            lines.append("\n  No notification rules configured.")
        return "\n".join(lines)

    return "Unknown action. Use: add, remove, list, set_quiet_hours, set_default_channel, set_ntfy"


def notification_history(data):
    """View sent notifications and delivery status."""
    db = _load_data()
    history = db.get("history", [])
    limit = min(data.get("limit", 20), 100)
    status_filter = data.get("status", "").lower()
    channel_filter = data.get("channel", "").lower()

    if status_filter:
        history = [h for h in history if status_filter in h.get("status", "")]
    if channel_filter:
        history = [h for h in history if channel_filter in h.get("channel", "")]

    history = history[-limit:]
    history.reverse()

    if not history:
        return "No notification history found."

    lines = [f"🔔 Notification History ({len(history)} shown):\n"]
    for h in history:
        emoji = (
            "✅"
            if h.get("status") == "delivered"
            else "❌"
            if h.get("status") == "failed"
            else "⏳"
        )
        dt = h.get("created_at", "")[:16].replace("T", " ")
        lines.append(
            f"  {emoji} {dt} [{h.get('channel', '')}] {h.get('title', '')}: {h.get('message', '')[:50]}"
        )

    delivered = sum(1 for h in db.get("history", []) if h.get("status") == "delivered")
    failed = sum(1 for h in db.get("history", []) if h.get("status") == "failed")
    lines.append(
        f"\n  Totals: {delivered} delivered, {failed} failed, {len(db.get('history', []))} total"
    )

    return "\n".join(lines)


def subscribe_topic(data):
    """Subscribe to a notification topic (grants, donors, tasks, email)."""
    db = _load_data()
    topic = data.get("topic", "").strip().lower()
    channel = data.get("channel", db.get("config", {}).get("default_channel", "log"))
    action = data.get("action", "subscribe").lower()

    if not topic:
        # List subscriptions
        subs = db.get("subscriptions", [])
        if not subs:
            return "No topic subscriptions. Available topics: grants, donors, tasks, email, calendar, all"
        lines = ["📬 Subscriptions:\n"]
        for s in subs:
            lines.append(
                f"  • {s['topic']} → {s['channel']} (since {s.get('created_at', '')[:10]})"
            )
        return "\n".join(lines)

    if action == "unsubscribe":
        db["subscriptions"] = [s for s in db.get("subscriptions", []) if s["topic"] != topic]
        _save_data(db)
        return f"✅ Unsubscribed from: {topic}"

    # Check for duplicate
    for s in db.get("subscriptions", []):
        if s["topic"] == topic:
            s["channel"] = channel
            _save_data(db)
            return f"✅ Updated subscription: {topic} → {channel}"

    db.setdefault("subscriptions", []).append(
        {
            "topic": topic,
            "channel": channel,
            "created_at": datetime.now().isoformat(),
        }
    )
    _save_data(db)
    return f"✅ Subscribed to {topic} → {channel}"


# === Tool Definitions ===

TOOLS = [
    {
        "name": "send_notification",
        "description": (
            "Send a system alert or reminder to the owner via a configured channel (telegram, email, ntfy, log). "
            "NOT for messaging other people -- use send_message or send_email for that. "
            "Respects quiet hours by queuing non-urgent messages for later delivery. "
            "Use channel 'all' to broadcast across all configured channels simultaneously."
        ),
        "input_schema": {
            "type": "object",
            "properties": {
                "message": {"type": "string", "description": "Notification body"},
                "title": {
                    "type": "string",
                    "default": "Familiar Alert",
                    "description": "Notification title",
                },
                "channel": {
                    "type": "string",
                    "enum": ["telegram", "email", "ntfy", "log", "all"],
                    "description": "Delivery channel (default: configured default)",
                },
                "priority": {
                    "type": "string",
                    "enum": ["low", "normal", "urgent"],
                    "default": "normal",
                },
                "recipient": {
                    "type": "string",
                    "description": "Email recipient (email channel only)",
                },
            },
            "required": ["message"],
        },
        "handler": send_notification,
        "category": "notifications",
    },
    {
        "name": "set_notification_rules",
        "description": (
            "Configure notification rules, quiet hours, default channel, and ntfy settings. "
            "Use this to set up automated alerts for events like deadline_approaching or donor_gift. "
            "Actions include add/remove rules, set_quiet_hours, set_default_channel, and set_ntfy. "
            "Use action 'list' to view all current rules and configuration."
        ),
        "input_schema": {
            "type": "object",
            "properties": {
                "action": {
                    "type": "string",
                    "enum": [
                        "add",
                        "remove",
                        "list",
                        "set_quiet_hours",
                        "set_default_channel",
                        "set_ntfy",
                    ],
                    "default": "list",
                },
                "event": {
                    "type": "string",
                    "description": "Event type (for add): deadline_approaching, urgent_email, donor_gift, etc.",
                },
                "channel": {"type": "string", "description": "Notification channel"},
                "priority": {"type": "string", "enum": ["low", "normal", "urgent"]},
                "condition": {
                    "type": "string",
                    "description": "Trigger condition (e.g. 'amount > 1000')",
                },
                "rule_id": {"type": "string", "description": "Rule ID (for remove)"},
                "quiet_hours_start": {"type": "string", "description": "Quiet hours start (HH:MM)"},
                "quiet_hours_end": {"type": "string", "description": "Quiet hours end (HH:MM)"},
                "ntfy_url": {
                    "type": "string",
                    "description": "Base URL for ntfy server (e.g. https://ntfy.sh)",
                },
                "ntfy_topic": {
                    "type": "string",
                    "description": "ntfy topic name to publish notifications to",
                },
            },
        },
        "handler": set_notification_rules,
        "category": "notifications",
    },
    {
        "name": "notification_history",
        "description": (
            "View the history of sent notifications with delivery status. "
            "Use this to verify that alerts were delivered or diagnose failed deliveries. "
            "Supports filtering by status (delivered, failed, queued) and channel. "
            "Returns timestamps, channels, titles, message previews, and delivery totals."
        ),
        "input_schema": {
            "type": "object",
            "properties": {
                "limit": {"type": "integer", "default": 20, "description": "Max results"},
                "status": {"type": "string", "description": "Filter: delivered, failed, queued"},
                "channel": {"type": "string", "description": "Filter by channel"},
            },
        },
        "handler": notification_history,
        "category": "notifications",
    },
    {
        "name": "subscribe_topic",
        "description": (
            "Subscribe to notification topics (grants, donors, tasks, email, calendar, all) or manage subscriptions. "
            "Use this to set up ongoing alerts for specific event categories. "
            "Omit the topic to list all current subscriptions. "
            "Use action 'unsubscribe' to stop receiving notifications for a topic."
        ),
        "input_schema": {
            "type": "object",
            "properties": {
                "topic": {
                    "type": "string",
                    "description": "Topic name (omit to list all subscriptions)",
                },
                "channel": {"type": "string", "description": "Delivery channel for this topic"},
                "action": {
                    "type": "string",
                    "enum": ["subscribe", "unsubscribe"],
                    "default": "subscribe",
                },
            },
        },
        "handler": subscribe_topic,
        "category": "notifications",
    },
]
