# 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

"""
Meeting Management Skill

End-to-end meeting lifecycle: schedule, agenda, minutes, follow-ups.
Data stored in ~/.familiar/data/meetings.json

Dependencies:
  - icalendar (optional, for .ics invite generation)
  - Integrates with calendar, contacts, email, documents, tasks skills
"""

import json
import logging
import os
import smtplib
from datetime import datetime, timedelta
from email import encoders
from email.mime.base import MIMEBase
from email.mime.multipart import MIMEMultipart
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__)


SMTP_TIMEOUT = 20  # seconds

try:
    from icalendar import Calendar
    from icalendar import Event as ICalEvent

    ICAL_AVAILABLE = True
except ImportError:
    ICAL_AVAILABLE = False


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


DATA_FILE = _get_data_file()  # Default for backward compat
DOCS_DIR = Path.home() / ".familiar" / "documents"

_DEFAULT_DATA = {"meetings": [], "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 _resolve_attendees(names_or_emails):
    """Resolve attendee names to contact records for email addresses."""
    contacts_file = Path.home() / ".familiar" / "data" / "contacts.json"
    contacts = []
    if contacts_file.exists():
        try:
            with open(contacts_file, encoding="utf-8") as f:
                contacts = json.load(f).get("contacts", [])
        except (json.JSONDecodeError, IOError) as e:
            logger.debug("JSON parse error suppressed: %s", e)
    resolved = []
    for entry in names_or_emails:
        entry = entry.strip()
        if not entry:
            continue
        # If it looks like an email, use directly
        if "@" in entry:
            resolved.append({"name": entry.split("@")[0], "email": entry})
            continue
        # Try to find in contacts
        found = False
        for c in contacts:
            if (
                c.get("name", "").lower() == entry.lower()
                or c.get("email", "").lower() == entry.lower()
            ):
                resolved.append({"name": c["name"], "email": c.get("email", "")})
                found = True
                break
        if not found:
            resolved.append({"name": entry, "email": ""})
    return resolved


def _generate_ics(meeting):
    """Generate an .ics calendar invite."""
    if not ICAL_AVAILABLE:
        return None
    cal = Calendar()
    cal.add("prodid", "-//Familiar//Meeting//EN")
    cal.add("version", "2.0")
    event = ICalEvent()
    event.add("summary", meeting.get("title", "Meeting"))
    try:
        start = datetime.fromisoformat(meeting["start"])
    except (ValueError, KeyError):
        start = datetime.now() + timedelta(hours=1)
    duration = meeting.get("duration_minutes", 60)
    event.add("dtstart", start)
    event.add("dtend", start + timedelta(minutes=duration))
    if meeting.get("location"):
        event.add("location", meeting["location"])
    if meeting.get("description"):
        event.add("description", meeting["description"])
    for att in meeting.get("attendees", []):
        if att.get("email"):
            event.add("attendee", f"mailto:{att['email']}")
    event.add("uid", f"{meeting['id']}@familiar")
    cal.add_component(event)
    return cal.to_ical()


def _send_invite_email(meeting, ics_data=None):
    """Send meeting invite via email."""
    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"))
    if not addr or not password:
        return 0, "Email not configured"
    sent = 0
    for att in meeting.get("attendees", []):
        to_addr = att.get("email")
        if not to_addr:
            continue
        msg = MIMEMultipart()
        msg["Subject"] = f"Meeting Invite: {meeting.get('title', 'Meeting')}"
        msg["From"] = addr
        msg["To"] = to_addr
        body = (
            f"You are invited to: {meeting.get('title', 'Meeting')}\n"
            f"Date: {meeting.get('start', 'TBD')}\n"
            f"Duration: {meeting.get('duration_minutes', 60)} minutes\n"
        )
        if meeting.get("location"):
            body += f"Location: {meeting['location']}\n"
        if meeting.get("description"):
            body += f"\n{meeting['description']}\n"
        if meeting.get("agenda_items"):
            body += "\nAgenda:\n"
            for i, item in enumerate(meeting["agenda_items"], 1):
                topic = item if isinstance(item, str) else item.get("topic", "")
                body += f"  {i}. {topic}\n"
        msg.attach(MIMEText(body))
        if ics_data:
            attachment = MIMEBase("text", "calendar", method="REQUEST")
            attachment.set_payload(ics_data)
            encoders.encode_base64(attachment)
            attachment.add_header("Content-Disposition", "attachment", filename="invite.ics")
            msg.attach(attachment)
        try:
            with smtplib.SMTP(smtp_server, smtp_port, timeout=15) as server:
                server.starttls()
                server.login(addr, password)
                server.send_message(msg)
            sent += 1
        except Exception as e:
            logger.warning(f"Failed to send invite to {to_addr}: {e}")
    return sent, f"{sent} invite(s) sent"


# === Tool Handlers ===


def schedule_meeting(data):
    """Create calendar event with attendees, send invites via email."""
    title = data.get("title", "Meeting").strip()
    start = data.get("start", "").strip()
    duration = data.get("duration_minutes", 60)
    location = data.get("location", "").strip()
    description = data.get("description", "").strip()
    attendee_list = data.get("attendees", [])

    if not start:
        return "Please provide a start date/time (e.g. '2025-03-15T14:00')."

    if not data.get("_confirmed"):
        from familiar.core.confirmations import meeting_preview, needs_confirmation

        preview_att = _resolve_attendees(attendee_list)
        return needs_confirmation(
            "schedule_meeting",
            data,
            meeting_preview(title, start, preview_att, data.get("send_invites", True)),
            risk="medium",
        )

    attendees = _resolve_attendees(attendee_list)
    db = _load_data()

    meeting = {
        "id": _gen_id(),
        "title": title,
        "start": start,
        "duration_minutes": duration,
        "location": location,
        "description": description,
        "attendees": attendees,
        "agenda_items": [],
        "minutes": None,
        "action_items": [],
        "status": "scheduled",
        "created_at": datetime.now().isoformat(),
    }

    db["meetings"].append(meeting)
    _save_data(db)

    # Generate and send invites
    result_parts = [f"✅ Meeting scheduled: {title} [{meeting['id']}]"]
    result_parts.append(f"   {start} ({duration} min)")
    if attendees:
        names = ", ".join(a["name"] for a in attendees)
        result_parts.append(f"   Attendees: {names}")

    if data.get("send_invites", True) and any(a.get("email") for a in attendees):
        ics = _generate_ics(meeting)
        sent, msg = _send_invite_email(meeting, ics)
        result_parts.append(f"   Invites: {msg}")

    return "\n".join(result_parts)


def create_agenda(data):
    """Generate meeting agenda document from topics, prior action items."""
    meeting_id = data.get("meeting_id", "").strip()
    topics = data.get("topics", [])
    include_action_items = data.get("include_prior_actions", True)

    db = _load_data()
    meeting = None
    if meeting_id:
        for m in db["meetings"]:
            if m["id"] == meeting_id:
                meeting = m
                break

    if not topics and not meeting:
        return "Please provide topics or a meeting_id."

    title = meeting["title"] if meeting else data.get("title", "Meeting Agenda")
    date_str = meeting["start"][:10] if meeting else datetime.now().strftime("%Y-%m-%d")

    if meeting:
        meeting["agenda_items"] = topics or meeting.get("agenda_items", [])
        _save_data(db)

    # Build agenda text
    lines = [f"AGENDA: {title}", f"Date: {date_str}", ""]

    # Prior action items from recent meetings
    if include_action_items:
        prior_actions = []
        for m in db.get("meetings", []):
            if m["id"] != (meeting_id or "") and m.get("action_items"):
                for ai in m["action_items"]:
                    if ai.get("status") != "done":
                        prior_actions.append(ai)
        if prior_actions:
            lines.append("PRIOR ACTION ITEMS:")
            for ai in prior_actions[-10:]:
                owner = ai.get("owner", "TBD")
                lines.append(f"  □ {ai.get('description', '')} ({owner})")
            lines.append("")

    lines.append("AGENDA:")
    agenda_items = topics or (meeting.get("agenda_items", []) if meeting else [])
    for i, item in enumerate(agenda_items, 1):
        if isinstance(item, dict):
            topic = item.get("topic", "")
            duration = item.get("duration", "")
            presenter = item.get("presenter", "")
            detail = f" ({duration})" if duration else ""
            detail += f" — {presenter}" if presenter else ""
            lines.append(f"  {i}. {topic}{detail}")
        else:
            lines.append(f"  {i}. {item}")

    lines.extend(["", "NOTES:", "", "", "ACTION ITEMS:", ""])

    agenda_text = "\n".join(lines)

    # Try to save as document
    DOCS_DIR.mkdir(parents=True, exist_ok=True)
    ts = datetime.now().strftime("%Y%m%d_%H%M%S")
    filepath = DOCS_DIR / f"agenda_{ts}.md"
    filepath.write_text(agenda_text, encoding="utf-8")

    return f"✅ Agenda created: {filepath}\n\n{agenda_text}"


def record_minutes(data):
    """Take meeting minutes (manual entry or from transcription)."""
    meeting_id = data.get("meeting_id", "").strip()
    content = data.get("content", "").strip()
    attendees_present = data.get("attendees_present", [])

    if not content:
        return "Please provide minutes content."

    db = _load_data()
    meeting = None
    if meeting_id:
        for m in db["meetings"]:
            if m["id"] == meeting_id:
                meeting = m
                break

    if meeting:
        meeting["minutes"] = {
            "content": content,
            "attendees_present": attendees_present
            or [a["name"] for a in meeting.get("attendees", [])],
            "recorded_at": datetime.now().isoformat(),
        }
        meeting["status"] = "completed"
        _save_data(db)
    else:
        # Create standalone minutes
        meeting = {
            "id": _gen_id(),
            "title": data.get("title", "Meeting"),
            "start": data.get("date", datetime.now().isoformat()),
            "minutes": {
                "content": content,
                "attendees_present": attendees_present,
                "recorded_at": datetime.now().isoformat(),
            },
            "action_items": [],
            "status": "completed",
            "created_at": datetime.now().isoformat(),
        }
        db["meetings"].append(meeting)
        _save_data(db)

    # Save as document
    DOCS_DIR.mkdir(parents=True, exist_ok=True)
    ts = datetime.now().strftime("%Y%m%d_%H%M%S")
    filepath = DOCS_DIR / f"minutes_{ts}.md"

    doc_lines = [
        f"# Meeting Minutes: {meeting.get('title', 'Meeting')}",
        f"**Date:** {meeting.get('start', '')[:10]}",
        f"**Present:** {', '.join(attendees_present or ['Not recorded'])}",
        "",
        "---",
        "",
        content,
    ]
    filepath.write_text("\n".join(doc_lines), encoding="utf-8")

    return f"✅ Minutes recorded for {meeting.get('title', 'meeting')} [{meeting['id']}]\n   Saved: {filepath}"


def distribute_minutes(data):
    """Send minutes to attendees with action items highlighted."""
    meeting_id = data.get("meeting_id", "").strip()
    if not meeting_id:
        return "Please provide a meeting_id."

    db = _load_data()
    meeting = None
    for m in db["meetings"]:
        if m["id"] == meeting_id:
            meeting = m
            break
    if not meeting:
        return f"Meeting {meeting_id} not found."
    if not meeting.get("minutes"):
        return "No minutes recorded for this meeting. Use record_minutes first."

    # Build email body
    body_lines = [
        f"Meeting Minutes: {meeting.get('title', 'Meeting')}",
        f"Date: {meeting.get('start', '')[:10]}",
        f"Present: {', '.join(meeting['minutes'].get('attendees_present', []))}",
        "",
        "---",
        "",
        meeting["minutes"]["content"],
    ]

    if meeting.get("action_items"):
        body_lines.extend(["", "ACTION ITEMS:", ""])
        for ai in meeting["action_items"]:
            owner = ai.get("owner", "TBD")
            due = ai.get("due_date", "")
            due_str = f" (due: {due})" if due else ""
            body_lines.append(f"  → {ai.get('description', '')} — {owner}{due_str}")

    body = "\n".join(body_lines)

    # Send via email
    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"))

    sent = 0
    recipients = meeting.get("attendees", [])
    if not recipients or not addr or not password:
        return f"✅ Minutes prepared but email not configured or no attendees with email.\n\n{body}"

    for att in recipients:
        to_addr = att.get("email")
        if not to_addr:
            continue
        msg = MIMEText(body)
        msg["Subject"] = f"Minutes: {meeting.get('title', 'Meeting')}"
        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)
            sent += 1
        except Exception as e:
            logger.warning(f"Failed to send minutes to {to_addr}: {e}")

    return f"✅ Minutes distributed to {sent} attendee(s) for: {meeting.get('title', 'Meeting')}"


def track_action_items(data):
    """Extract action items from minutes and create tasks assigned to contacts."""
    meeting_id = data.get("meeting_id", "").strip()
    items = data.get("items", [])

    db = _load_data()
    meeting = None
    if meeting_id:
        for m in db["meetings"]:
            if m["id"] == meeting_id:
                meeting = m
                break

    if not items and not meeting:
        return "Please provide items or a meeting_id."

    if not items and meeting and meeting.get("action_items"):
        items = meeting["action_items"]

    if not items:
        return "No action items provided or found in meeting record."

    # Store on meeting
    parsed_items = []
    for item in items:
        if isinstance(item, str):
            parsed = {"description": item, "owner": "TBD", "due_date": "", "status": "open"}
        else:
            parsed = {
                "description": item.get("description", ""),
                "owner": item.get("owner", "TBD"),
                "due_date": item.get("due_date", ""),
                "status": item.get("status", "open"),
            }
        parsed["id"] = _gen_id()
        parsed_items.append(parsed)

    if meeting:
        meeting["action_items"] = parsed_items
        _save_data(db)

    # Try to create tasks via tasks skill
    tasks_created = 0
    try:
        tasks_file = Path.home() / ".familiar" / "data" / "tasks.json"
        if tasks_file.exists():
            with open(tasks_file, encoding="utf-8") as f:
                tasks = json.load(f)
        else:
            tasks = []

        for ai in parsed_items:
            import secrets

            task = {
                "id": secrets.token_hex(4),
                "title": ai["description"],
                "status": "pending",
                "priority": "normal",
                "due_date": ai.get("due_date", ""),
                "category": "meeting_action",
                "assignee": ai.get("owner", ""),
                "meeting_id": meeting_id or "",
                "created_at": datetime.now().isoformat(),
            }
            tasks.append(task)
            tasks_created += 1

        tasks_file.parent.mkdir(parents=True, exist_ok=True)
        if atomic_write_json:
            atomic_write_json(tasks_file, tasks)
        else:
            with open(tasks_file, "w", encoding="utf-8") as f:
                json.dump(tasks, f, indent=2)
    except Exception as e:
        logger.warning(f"Could not create tasks: {e}")

    lines = [f"✅ {len(parsed_items)} action item(s) tracked:"]
    for ai in parsed_items:
        owner = ai.get("owner", "TBD")
        due = f" (due: {ai['due_date']})" if ai.get("due_date") else ""
        lines.append(f"  → {ai['description']} — {owner}{due}")
    if tasks_created:
        lines.append(f"\n  📋 {tasks_created} task(s) auto-created in task list")

    return "\n".join(lines)


# === Tool Definitions ===

TOOLS = [
    {
        "name": "schedule_meeting",
        "description": (
            "Create a calendar event with attendees resolved from contacts and send "
            "email invites with .ics attachments. Use this when the user wants to book "
            "a meeting, appointment, or recurring event. Requires EMAIL_ADDRESS and "
            "EMAIL_PASSWORD environment variables for invite delivery."
        ),
        "requires_confirmation": True,
        "input_schema": {
            "type": "object",
            "properties": {
                "title": {"type": "string", "description": "Meeting title"},
                "start": {
                    "type": "string",
                    "description": "Start date/time (ISO format, e.g. 2025-03-15T14:00)",
                },
                "duration_minutes": {
                    "type": "integer",
                    "default": 60,
                    "description": "Meeting duration in minutes",
                },
                "location": {"type": "string", "description": "Location or video link"},
                "description": {"type": "string", "description": "Meeting description or notes"},
                "attendees": {
                    "type": "array",
                    "items": {"type": "string", "description": "Attendee name or email address"},
                    "description": "List of attendee names or emails (resolved from contacts)",
                },
                "send_invites": {"type": "boolean", "default": True},
            },
            "required": ["title", "start"],
        },
        "input_examples": [
            {
                "title": "Weekly standup",
                "start": "next Monday 10:00",
                "duration": 30,
                "attendees": ["alice@example.com", "bob@example.com"],
                "send_invites": True,
            },
        ],
        "handler": schedule_meeting,
        "category": "meetings",
    },
    {
        "name": "create_agenda",
        "description": (
            "Generate a formatted meeting agenda document from topics and prior action "
            "items. Use this before a meeting to prepare an agenda with numbered topics, "
            "presenter assignments, and time allocations. The agenda is saved as a "
            "Markdown file in ~/.familiar/documents/."
        ),
        "input_schema": {
            "type": "object",
            "properties": {
                "meeting_id": {"type": "string", "description": "Meeting ID to attach agenda to"},
                "title": {"type": "string", "description": "Agenda title (if no meeting_id)"},
                "topics": {
                    "type": "array",
                    "items": {},
                    "description": "Agenda items: strings or objects with topic/duration/presenter",
                },
                "include_prior_actions": {"type": "boolean", "default": True},
            },
        },
        "handler": create_agenda,
        "category": "meetings",
    },
    {
        "name": "record_minutes",
        "description": (
            "Record meeting minutes from manual entry or voice transcription and "
            "attach them to a meeting record. Use this during or after a meeting to "
            "capture discussion notes and decisions. Minutes are saved as a Markdown "
            "file and the meeting status is updated to completed."
        ),
        "input_schema": {
            "type": "object",
            "properties": {
                "meeting_id": {"type": "string", "description": "Meeting ID"},
                "title": {"type": "string", "description": "Meeting title (if no meeting_id)"},
                "content": {"type": "string", "description": "Minutes text content"},
                "attendees_present": {
                    "type": "array",
                    "items": {"type": "string", "description": "Name of attendee who was present"},
                    "description": "List of attendee names who were present at the meeting",
                },
                "date": {"type": "string", "description": "Meeting date (if no meeting_id)"},
            },
            "required": ["content"],
        },
        "handler": record_minutes,
        "category": "meetings",
    },
    {
        "name": "distribute_minutes",
        "description": (
            "Send recorded meeting minutes to all attendees via email with action "
            "items highlighted. Use this after recording minutes to distribute them "
            "to participants. Requires EMAIL_ADDRESS and EMAIL_PASSWORD environment "
            "variables and that minutes have already been recorded for the meeting."
        ),
        "input_schema": {
            "type": "object",
            "properties": {
                "meeting_id": {"type": "string", "description": "Meeting ID"},
            },
            "required": ["meeting_id"],
        },
        "handler": distribute_minutes,
        "category": "meetings",
    },
    {
        "name": "track_action_items",
        "description": (
            "Extract action items from meeting minutes and create tasks assigned to "
            "contacts. Use this after a meeting to convert discussed action items into "
            "tracked tasks with owners and due dates. Tasks are automatically created "
            "in the task list for follow-up."
        ),
        "input_schema": {
            "type": "object",
            "properties": {
                "meeting_id": {
                    "type": "string",
                    "description": "Meeting ID to pull/store action items",
                },
                "items": {
                    "type": "array",
                    "description": "Action items: strings or objects with description/owner/due_date",
                    "items": {},
                },
            },
        },
        "handler": track_action_items,
        "category": "meetings",
    },
]
