# 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

"""
Proactive Notifications Skill

Daily briefings, smart reminders, deadline alerts.
Works with the scheduler to send proactive messages.
"""

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

logger = logging.getLogger(__name__)

# Storage paths - use centralized paths


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.paths import DATA_DIR, ensure_dir
except ImportError:
    DATA_DIR = _get_data_dir()

    def ensure_dir(path):
        path.mkdir(parents=True, exist_ok=True)
        return path


PROACTIVE_FILE = DATA_DIR / "proactive.json"

ensure_dir(DATA_DIR)


def _load_config() -> dict:
    """Load proactive config."""
    if PROACTIVE_FILE.exists():
        try:
            return json.loads(PROACTIVE_FILE.read_text())
        except (json.JSONDecodeError, IOError) as e:
            logger.warning(f"Failed to load proactive config: {e}")
    return {
        "briefing_time": "08:00",
        "briefing_enabled": False,
        "deadline_alert_days": 3,
        "followup_enabled": True,
        "quiet_hours_start": "21:00",
        "quiet_hours_end": "08:00",
    }


def _save_config(config: dict):
    """Save proactive config."""
    PROACTIVE_FILE.write_text(json.dumps(config, indent=2))


def generate_briefing(data: dict) -> str:
    """Generate a comprehensive daily briefing."""
    sections = []

    # Date header
    now = datetime.now()
    sections.append(f"📅 **{now.strftime('%A, %B %d, %Y')}**\n")

    # === Calendar ===
    try:
        from .calendar.skill import get_events

        calendar = get_events({"date": "today", "days": 1})
        if calendar and "No events" not in calendar:
            sections.append("**Today's Schedule:**")
            sections.append(calendar)
            sections.append("")
    except Exception as e:
        logger.debug(f"Calendar not available: {e}")

    # === Tasks ===
    try:
        # Import from sibling skill
        import sys

        tasks_path = Path(__file__).parent.parent / "tasks"
        sys.path.insert(0, str(tasks_path))
        from skill import get_daily_briefing as get_tasks

        tasks = get_tasks({})
        if tasks and "All clear" not in tasks:
            sections.append("**Tasks:**")
            sections.append(tasks)
            sections.append("")
    except Exception as e:
        logger.debug(f"Tasks not available: {e}")

    # === Email Summary ===
    try:
        import sys

        triage_path = Path(__file__).parent.parent / "triage"
        sys.path.insert(0, str(triage_path))
        from skill import get_category_summary

        email_summary = get_category_summary({"days_back": 1})
        if email_summary and "No unread" not in email_summary:
            sections.append("**Email:**")
            sections.append(email_summary)
            sections.append("")
    except Exception as e:
        logger.debug(f"Email triage not available: {e}")

    # === Grant Deadlines ===
    try:
        import sys

        nonprofit_path = Path(__file__).parent.parent / "nonprofit"
        sys.path.insert(0, str(nonprofit_path))
        from skill import grant_deadlines

        grants = grant_deadlines({"days": 14})
        if grants and "No grant deadlines" not in grants:
            sections.append("**Upcoming Grant Deadlines:**")
            sections.append(grants)
            sections.append("")
    except Exception as e:
        logger.debug(f"Nonprofit skill not available: {e}")

    # === Donors Needing Thanks ===
    try:
        from skill import donors_needing_thanks

        thanks = donors_needing_thanks({"days": 7})
        if thanks and "All recent donors" not in thanks:
            sections.append("**Donor Follow-ups:**")
            sections.append(thanks)
    except Exception as e:
        logger.debug(f"Donor tracking not available: {e}")

    if len(sections) <= 1:
        sections.append("✨ No urgent items today!")

    return "\n".join(sections)


def set_briefing_time(data: dict) -> str:
    """Set the daily briefing time."""
    time_str = data.get("time", "08:00")
    enabled = data.get("enabled", True)

    # Validate time format
    try:
        datetime.strptime(time_str, "%H:%M")
    except ValueError:
        return "Invalid time format. Use HH:MM (e.g., 08:00, 14:30)"

    config = _load_config()
    config["briefing_time"] = time_str
    config["briefing_enabled"] = enabled
    _save_config(config)

    if enabled:
        return f"✅ Daily briefing set for {time_str}"
    else:
        return "✅ Daily briefing disabled"


def set_deadline_alerts(data: dict) -> str:
    """Configure deadline alert settings."""
    days = data.get("days_before", 3)

    config = _load_config()
    config["deadline_alert_days"] = days
    _save_config(config)

    return f"✅ Will alert {days} days before deadlines"


def set_quiet_hours(data: dict) -> str:
    """Set quiet hours (no notifications)."""
    start = data.get("start", "21:00")
    end = data.get("end", "08:00")

    try:
        datetime.strptime(start, "%H:%M")
        datetime.strptime(end, "%H:%M")
    except ValueError:
        return "Invalid time format. Use HH:MM"

    config = _load_config()
    config["quiet_hours_start"] = start
    config["quiet_hours_end"] = end
    _save_config(config)

    return f"✅ Quiet hours: {start} - {end}"


def check_deadlines(data: dict) -> str:
    """Check for approaching deadlines and generate alerts."""
    config = _load_config()
    days = config.get("deadline_alert_days", 3)

    alerts = []
    today = datetime.now().date()
    cutoff = today + timedelta(days=days)

    # Check tasks
    try:
        tasks_file = DATA_DIR / "tasks.json"
        if tasks_file.exists():
            tasks = json.loads(tasks_file.read_text())
            for task in tasks:
                if task.get("completed"):
                    continue
                if task.get("due_date"):
                    try:
                        due = datetime.fromisoformat(task["due_date"]).date()
                        if due <= cutoff:
                            days_left = (due - today).days
                            if days_left < 0:
                                alerts.append(f"🔴 OVERDUE: {task['title']}")
                            elif days_left == 0:
                                alerts.append(f"🟡 DUE TODAY: {task['title']}")
                            else:
                                alerts.append(f"⚠️ Due in {days_left}d: {task['title']}")
                    except (ValueError, KeyError) as e:
                        logger.debug(f"Invalid task date: {e}")
    except (json.JSONDecodeError, IOError) as e:
        logger.debug(f"Could not load tasks: {e}")

    # Check grants
    try:
        nonprofit_file = DATA_DIR / "nonprofit.json"
        if nonprofit_file.exists():
            data = json.loads(nonprofit_file.read_text())
            for grant in data.get("grants", []):
                for date_field in ["deadline", "report_due"]:
                    if grant.get(date_field):
                        try:
                            due = datetime.fromisoformat(grant[date_field]).date()
                            if due <= cutoff:
                                days_left = (due - today).days
                                field_name = "Application" if date_field == "deadline" else "Report"
                                if days_left < 0:
                                    alerts.append(f"🔴 OVERDUE: {grant['name']} {field_name}")
                                elif days_left <= 3:
                                    alerts.append(
                                        f"⚠️ {field_name} due in {days_left}d: {grant['name']}"
                                    )
                        except (ValueError, KeyError) as e:
                            logger.debug(f"Invalid grant date: {e}")
    except (json.JSONDecodeError, IOError) as e:
        logger.debug(f"Could not load nonprofit data: {e}")

    if not alerts:
        return f"✅ No deadlines in the next {days} days"

    return "⏰ Deadline Alerts:\n\n" + "\n".join(alerts)


def get_proactive_status(data: dict) -> str:
    """Get current proactive settings."""
    config = _load_config()

    lines = ["📋 Proactive Settings:\n"]

    briefing = "Enabled" if config.get("briefing_enabled") else "Disabled"
    lines.append(f"  Morning Briefing: {briefing} at {config.get('briefing_time', '08:00')}")
    lines.append(f"  Deadline Alerts: {config.get('deadline_alert_days', 3)} days before")
    lines.append(
        f"  Quiet Hours: {config.get('quiet_hours_start', '21:00')} - {config.get('quiet_hours_end', '08:00')}"
    )

    return "\n".join(lines)


# Function to be called by scheduler
def run_scheduled_briefing(task):
    """Called by scheduler to send morning briefing."""
    briefing = generate_briefing({})

    # This would be sent via the channel specified in the task
    # For now, just log it
    logger.info(f"Scheduled briefing:\n{briefing}")

    return briefing


def heartbeat_check(data: dict) -> str:
    """
    Heartbeat check-in — Article 2 + SILENCE_GUIDANCE.

    Runs every 4-6 hours during non-quiet-hours. Checks for actionable
    items only. If nothing to report, produces NOTHING — silence is service.

    Convergence Pipeline Week 1, Item 1B.
    """
    config = _load_config()

    # Respect quiet hours
    now = datetime.now()
    quiet_start = config.get("quiet_hours_start", "22:00")
    quiet_end = config.get("quiet_hours_end", "07:00")
    try:
        qs = datetime.strptime(quiet_start, "%H:%M").replace(
            year=now.year, month=now.month, day=now.day
        )
        qe = datetime.strptime(quiet_end, "%H:%M").replace(
            year=now.year, month=now.month, day=now.day
        )
        if qs > qe:  # Overnight quiet hours (e.g. 22:00 - 07:00)
            if now >= qs or now <= qe:
                logger.debug("Heartbeat: quiet hours — skipping")
                return ""
        elif qs <= now <= qe:
            logger.debug("Heartbeat: quiet hours — skipping")
            return ""
    except ValueError:
        pass

    # Check for actionable content
    items = []

    # Overdue tasks
    try:
        tasks_dir = Path(os.path.expanduser("~/.familiar/data/tasks"))
        if tasks_dir.exists():
            import json

            tasks_file = tasks_dir / "tasks.json"
            if tasks_file.exists():
                tasks = json.loads(tasks_file.read_text())
                overdue = [
                    t
                    for t in tasks
                    if t.get("due")
                    and t.get("status") != "done"
                    and datetime.fromisoformat(t["due"]) < now
                ]
                if overdue:
                    items.append(
                        f"📋 {len(overdue)} overdue task{'s' if len(overdue) != 1 else ''}"
                    )
    except Exception as e:
        logger.debug(f"Heartbeat task check error: {e}")

    # Approaching deadlines (next 24h)
    deadline_info = check_deadlines({})
    if "approaching" in deadline_info.lower() or "due" in deadline_info.lower():
        # Only include if there's actually something, not "no deadlines"
        if "no " not in deadline_info.lower()[:20]:
            items.append("⏰ Deadlines approaching")

    # Gate: only speak when there's something worth saying
    if not items:
        logger.debug("Heartbeat: nothing actionable — staying silent")
        return ""

    # Build concise nudge
    lines = ["Quick check-in:\n"]
    for item in items:
        lines.append(f"  {item}")
    lines.append("\nWant me to help with any of these?")

    result = "\n".join(lines)
    logger.info(f"Heartbeat check-in: {len(items)} actionable items")
    return result


def generate_eod_summary(data: dict) -> str:
    """
    End-of-day summary — Article 2.

    "Here's what we got done today. Tomorrow: [upcoming items]."
    Uses audit trail and task completion to reconstruct the day.

    Convergence Pipeline Week 1, Item 1B.
    """
    now = datetime.now()
    today = now.strftime("%A, %B %d")

    lines = [f"📊 End of Day — {today}\n"]

    # Completed tasks today
    try:
        tasks_dir = Path(os.path.expanduser("~/.familiar/data/tasks"))
        if tasks_dir.exists():
            import json

            tasks_file = tasks_dir / "tasks.json"
            if tasks_file.exists():
                tasks = json.loads(tasks_file.read_text())
                completed = [
                    t
                    for t in tasks
                    if t.get("completed_at")
                    and datetime.fromisoformat(t["completed_at"]).date() == now.date()
                ]
                if completed:
                    lines.append(
                        f"✅ Completed: {len(completed)} task{'s' if len(completed) != 1 else ''}"
                    )
                    for t in completed[:5]:
                        lines.append(f"   • {t.get('title', 'Untitled')}")
    except Exception as e:
        logger.debug(f"EOD task summary error: {e}")

    # Interaction count today (constitution tracker integration pending)

    # Tomorrow preview
    lines.append("")
    tomorrow = (now + timedelta(days=1)).strftime("%A")
    lines.append(f"📅 Tomorrow ({tomorrow}):")

    # Calendar events tomorrow
    try:
        cal_dir = Path(os.path.expanduser("~/.familiar/data/calendar"))
        if cal_dir.exists():
            import json

            events_file = cal_dir / "events.json"
            if events_file.exists():
                events = json.loads(events_file.read_text())
                tomorrow_date = (now + timedelta(days=1)).date()
                tomorrow_events = [
                    e
                    for e in events
                    if datetime.fromisoformat(e.get("start", "")).date() == tomorrow_date
                ]
                if tomorrow_events:
                    for e in tomorrow_events[:5]:
                        time_str = datetime.fromisoformat(e["start"]).strftime("%I:%M %p")
                        lines.append(f"   {time_str} — {e.get('title', 'Event')}")
                else:
                    lines.append("   No events scheduled")
    except Exception as e:
        lines.append("   Calendar not connected")
        logger.debug(f"EOD calendar error: {e}")

    # Pending tasks
    try:
        tasks_dir = Path(os.path.expanduser("~/.familiar/data/tasks"))
        if tasks_dir.exists():
            import json

            tasks_file = tasks_dir / "tasks.json"
            if tasks_file.exists():
                tasks = json.loads(tasks_file.read_text())
                pending = [t for t in tasks if t.get("status") != "done"]
                if pending:
                    lines.append(
                        f"\n📋 {len(pending)} task{'s' if len(pending) != 1 else ''} still open"
                    )
    except Exception:
        pass

    lines.append("\nHave a good evening. 🌙")
    return "\n".join(lines)


def run_heartbeat(task):
    """Called by scheduler for heartbeat check-ins."""
    result = heartbeat_check({})
    if result:
        logger.info(f"Heartbeat:\n{result}")
    return result


def run_eod_summary(task):
    """Called by scheduler for end-of-day summary."""
    return generate_eod_summary({})


def _weekly_capability_digest(data: dict) -> str:
    """
    Weekly capability tip — Article 4 (Hot Dog Principle).

    Surfaces one unused skill the user hasn't tried yet.
    Convergence Pipeline Week 2, Item 2A.
    """
    try:
        from familiar.core.progressive import generate_weekly_digest

        # We need the skill_loader — try to get it from the agent
        # If not available, generate a simpler message
        result = generate_weekly_digest(None)
        if result:
            return result
        return "All your available skills are active and well-used!"
    except ImportError:
        return "Weekly digest not available — progressive module not loaded."


def run_weekly_digest(task):
    """Called by scheduler for weekly capability digest."""
    return _weekly_capability_digest({})


# Tool definitions
TOOLS = [
    {
        "name": "daily_briefing",
        "description": (
            "Generate a comprehensive daily briefing combining calendar events, tasks, email summary, grant deadlines, and donor follow-ups. "
            "Pulls data from calendar, tasks, triage, and nonprofit skills when available. "
            "Use this for a morning overview or to catch up on the day's agenda. "
            "Returns a formatted summary with all actionable items."
        ),
        "input_schema": {"type": "object", "properties": {}},
        "handler": generate_briefing,
        "category": "proactive",
    },
    {
        "name": "set_briefing_time",
        "description": (
            "Set the time for the automatic daily briefing and enable or disable it. "
            "The briefing is delivered by the scheduler at the configured time each day. "
            "Use HH:MM format (24-hour) for the time parameter. "
            "Set enabled to false to pause briefings without losing the configured time."
        ),
        "input_schema": {
            "type": "object",
            "properties": {
                "time": {
                    "type": "string",
                    "description": "Time in HH:MM format (e.g., 08:00)",
                    "default": "08:00",
                },
                "enabled": {
                    "type": "boolean",
                    "default": True,
                    "description": "Whether to enable the daily briefing",
                },
            },
        },
        "handler": set_briefing_time,
        "category": "proactive",
    },
    {
        "name": "set_deadline_alerts",
        "description": (
            "Configure how many days before a deadline to trigger alerts for tasks and grants. "
            "The alert window applies to both task due dates and grant application deadlines. "
            "Alerts are surfaced in the daily briefing and heartbeat check-ins."
        ),
        "input_schema": {
            "type": "object",
            "properties": {
                "days_before": {
                    "type": "integer",
                    "description": "Days before deadline to alert",
                    "default": 3,
                }
            },
        },
        "handler": set_deadline_alerts,
        "category": "proactive",
    },
    {
        "name": "set_quiet_hours",
        "description": (
            "Set quiet hours during which no proactive notifications, briefings, or heartbeat check-ins will be sent. "
            "Supports overnight ranges where start is later than end (e.g. 21:00 to 08:00). "
            "Use HH:MM format (24-hour) for both start and end times."
        ),
        "input_schema": {
            "type": "object",
            "properties": {
                "start": {
                    "type": "string",
                    "description": "Start of quiet hours (HH:MM)",
                    "default": "21:00",
                },
                "end": {
                    "type": "string",
                    "description": "End of quiet hours (HH:MM)",
                    "default": "08:00",
                },
            },
        },
        "handler": set_quiet_hours,
        "category": "proactive",
    },
    {
        "name": "check_deadlines",
        "description": (
            "Check for approaching deadlines across tasks and grants and generate prioritized alerts. "
            "Flags overdue items, items due today, and items due within the configured alert window. "
            "Use this for an on-demand deadline check outside of scheduled briefings."
        ),
        "input_schema": {"type": "object", "properties": {}},
        "handler": check_deadlines,
        "category": "proactive",
    },
    {
        "name": "proactive_status",
        "description": (
            "Get the current proactive notification settings including briefing time, deadline alert window, and quiet hours. "
            "Shows whether each feature is enabled or disabled. "
            "Use this to review settings before modifying them."
        ),
        "input_schema": {"type": "object", "properties": {}},
        "handler": get_proactive_status,
        "category": "proactive",
    },
    {
        "name": "heartbeat_check",
        "description": (
            "Periodic heartbeat check for overdue tasks, urgent items, and approaching deadlines. "
            "Respects quiet hours and only produces output when there is something actionable to report. "
            "Silence is intentional -- no news means no action needed. "
            "Designed to run every 4-6 hours via the scheduler."
        ),
        "input_schema": {"type": "object", "properties": {}},
        "handler": heartbeat_check,
        "category": "proactive",
    },
    {
        "name": "end_of_day_summary",
        "description": (
            "Generate an end-of-day summary showing tasks completed today, tomorrow's calendar events, and remaining open items. "
            "Reconstructs the day's accomplishments from task completion timestamps. "
            "Use this for a daily wrap-up or to prepare for the next day."
        ),
        "input_schema": {"type": "object", "properties": {}},
        "handler": generate_eod_summary,
        "category": "proactive",
    },
    {
        "name": "weekly_capability_digest",
        "description": (
            "Generate a weekly tip highlighting an unused skill or capability the user has not tried yet. "
            "Follows the Hot Dog Principle (Article 4): surface features naturally rather than overwhelming with options. "
            "Designed to run weekly via the scheduler to gradually introduce new capabilities."
        ),
        "input_schema": {"type": "object", "properties": {}},
        "handler": _weekly_capability_digest,
        "category": "proactive",
    },
]
