# 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

"""
Pending Events Store — calendar events detected in emails awaiting user review.

Best-practice pattern from Lindy AI / DonorDock: never auto-create calendar
events silently. Instead, batch detected events and present them for
confirmation in the next proactive check-in.

Stored at: ~/.familiar/data/pending_events.json
"""

import json
import logging
import os
import secrets
import threading
from datetime import datetime, timezone
from pathlib import Path
from typing import Optional

logger = logging.getLogger(__name__)

_lock = threading.Lock()


def _data_dir() -> Path:
    try:
        from familiar.core.paths import DATA_DIR

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


def _store_path() -> Path:
    return _data_dir() / "pending_events.json"


class PendingEventStore:
    """Thread-safe store for calendar events detected in emails.

    Events flow: monitor detects → store queues → heartbeat presents →
    user confirms or dismisses → calendar created.
    """

    def __init__(self):
        self._path = _store_path()
        self._data: dict = {"events": []}
        self.load()

    def load(self):
        """Load pending events from disk."""
        with _lock:
            if self._path.exists():
                try:
                    self._data = json.loads(self._path.read_text())
                except (json.JSONDecodeError, OSError) as e:
                    logger.warning("Failed to load pending events: %s", e)
                    self._data = {"events": []}
            else:
                self._data = {"events": []}

    def save(self):
        """Write pending events to disk atomically."""
        with _lock:
            self._path.parent.mkdir(parents=True, exist_ok=True)
            tmp = self._path.with_suffix(".tmp")
            tmp.write_text(json.dumps(self._data, indent=2))
            tmp.replace(self._path)
            try:
                os.chmod(self._path, 0o600)
            except OSError:
                pass

    def add_event(
        self,
        email_id: str,
        email_subject: str,
        email_from: str,
        title: str,
        start: str,
        duration: int = 60,
        location: str = "",
        has_rsvp: bool = False,
        conflict: Optional[str] = None,
        source_category: str = "",
    ) -> Optional[str]:
        """Add a pending event. Returns event ID, or None if duplicate."""
        # Deduplicate by email_id
        for ev in self._data["events"]:
            if ev["email_id"] == email_id:
                return None

        event_id = f"pe_{secrets.token_hex(6)}"
        now = datetime.now(timezone.utc).isoformat()

        self._data["events"].append(
            {
                "id": event_id,
                "email_id": email_id,
                "email_subject": email_subject,
                "email_from": email_from,
                "title": title,
                "start": start,
                "duration": duration,
                "location": location,
                "has_rsvp": has_rsvp,
                "conflict": conflict,
                "status": "pending",
                "detected_at": now,
                "reviewed_at": None,
                "source_category": source_category,
            }
        )
        self.save()
        return event_id

    def get_pending(self) -> list[dict]:
        """Return all pending events, sorted by start date ascending."""
        self.load()
        pending = [e for e in self._data["events"] if e["status"] == "pending"]
        pending.sort(key=lambda e: e.get("start", ""))
        return pending

    def get_event(self, event_id: str) -> Optional[dict]:
        """Get a single event by ID."""
        self.load()
        for e in self._data["events"]:
            if e["id"] == event_id:
                return e
        return None

    def get_by_index(self, index: int) -> Optional[dict]:
        """Get a pending event by 1-based index."""
        pending = self.get_pending()
        if 1 <= index <= len(pending):
            return pending[index - 1]
        return None

    def mark_confirmed(self, event_id: str) -> bool:
        """Mark event as confirmed (calendar event was created)."""
        for e in self._data["events"]:
            if e["id"] == event_id:
                e["status"] = "confirmed"
                e["reviewed_at"] = datetime.now(timezone.utc).isoformat()
                self.save()
                return True
        return False

    def mark_dismissed(self, event_id: str) -> bool:
        """Mark event as dismissed (user doesn't want it on calendar)."""
        for e in self._data["events"]:
            if e["id"] == event_id:
                e["status"] = "dismissed"
                e["reviewed_at"] = datetime.now(timezone.utc).isoformat()
                self.save()
                return True
        return False

    def cleanup_stale(self, max_age_days: int = 7):
        """Remove events older than max_age_days that are not pending."""
        now = datetime.now(timezone.utc)
        keep = []
        for e in self._data["events"]:
            detected = e.get("detected_at", "")
            if not detected:
                continue
            try:
                dt = datetime.fromisoformat(detected.replace("Z", "+00:00"))
                age = (now - dt).days
                # Keep pending events regardless of age, discard old reviewed ones
                if e["status"] == "pending" or age <= max_age_days:
                    keep.append(e)
            except ValueError:
                keep.append(e)  # Keep if we can't parse date
        if len(keep) != len(self._data["events"]):
            self._data["events"] = keep
            self.save()

    def count_pending(self) -> int:
        """Quick count of pending events without full load."""
        self.load()
        return sum(1 for e in self._data["events"] if e["status"] == "pending")


def get_pending_event_store() -> PendingEventStore:
    """Get the pending event store (creates new instance each call for thread safety)."""
    return PendingEventStore()
