# 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

"""
Google Calendar Skill

Manage Google Calendar via the API.

Setup:
1. Create OAuth credentials at https://console.cloud.google.com
2. Enable Calendar API
3. Download credentials.json to ~/.familiar/data/google_credentials.json
4. First run will prompt for authorization
"""

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

logger = logging.getLogger(__name__)


REQUEST_TIMEOUT = 30  # seconds for Google API calls

# 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
except ImportError:
    DATA_DIR = _get_data_dir()

CREDENTIALS_FILE = DATA_DIR / "google_credentials.json"
TOKEN_FILE = DATA_DIR / "google_token.json"

# Scopes needed
SCOPES = ["https://www.googleapis.com/auth/calendar"]


def _get_calendar_service():
    """Get authenticated Google Calendar service."""
    try:
        from google.auth.transport.requests import Request
        from google.oauth2.credentials import Credentials
        from google_auth_oauthlib.flow import InstalledAppFlow
        from googleapiclient.discovery import build
    except ImportError:
        raise RuntimeError(
            "Google API libraries not installed. Run:\n"
            "  pip install google-auth-oauthlib google-auth-httplib2 google-api-python-client"
        )

    if not CREDENTIALS_FILE.exists():
        raise RuntimeError(
            f"Google credentials not found at {CREDENTIALS_FILE}\n"
            "Download from Google Cloud Console (OAuth 2.0 Client ID)"
        )

    creds = None

    # Load existing token
    if TOKEN_FILE.exists():
        creds = Credentials.from_authorized_user_file(str(TOKEN_FILE), SCOPES)

    # Refresh or get new credentials
    if not creds or not creds.valid:
        if creds and creds.expired and creds.refresh_token:
            creds.refresh(Request())
        else:
            flow = InstalledAppFlow.from_client_secrets_file(str(CREDENTIALS_FILE), SCOPES)
            creds = flow.run_local_server(port=0)

        # Save token
        TOKEN_FILE.write_text(creds.to_json())

    import httplib2

    http = httplib2.Http(timeout=REQUEST_TIMEOUT)
    http = creds.authorize(http)
    return build("calendar", "v3", http=http)


def _parse_datetime(dt_str: str, default_time: str = "09:00") -> datetime:
    """Parse various datetime formats."""
    dt_str = dt_str.lower().strip()
    now = datetime.now()
    today = now.date()

    # Relative dates
    if dt_str == "today":
        return datetime.combine(today, datetime.strptime(default_time, "%H:%M").time())
    elif dt_str == "tomorrow":
        return datetime.combine(
            today + timedelta(days=1), datetime.strptime(default_time, "%H:%M").time()
        )
    elif dt_str.startswith("next "):
        day_name = dt_str[5:]
        days = ["monday", "tuesday", "wednesday", "thursday", "friday", "saturday", "sunday"]
        if day_name in days:
            target_day = days.index(day_name)
            current_day = today.weekday()
            days_ahead = target_day - current_day
            if days_ahead <= 0:
                days_ahead += 7
            return datetime.combine(
                today + timedelta(days=days_ahead), datetime.strptime(default_time, "%H:%M").time()
            )

    # Try various formats
    formats = [
        "%Y-%m-%d %H:%M",
        "%Y-%m-%d",
        "%m/%d/%Y %H:%M",
        "%m/%d/%Y",
        "%m/%d %H:%M",
        "%m/%d",
        "%B %d %H:%M",
        "%B %d",
        "%b %d %H:%M",
        "%b %d",
    ]

    for fmt in formats:
        try:
            parsed = datetime.strptime(dt_str, fmt)
            if parsed.year == 1900:
                parsed = parsed.replace(year=today.year)
            if parsed.hour == 0 and parsed.minute == 0 and ":" not in dt_str:
                parsed = parsed.replace(hour=int(default_time.split(":")[0]))
            return parsed
        except ValueError:
            continue

    raise ValueError(f"Could not parse datetime: {dt_str}")


def _format_event(event: dict) -> str:
    """Format an event for display."""
    start = event.get("start", {})
    end = event.get("end", {})

    # Handle all-day vs timed events
    if "dateTime" in start:
        start_dt = datetime.fromisoformat(start["dateTime"].replace("Z", "+00:00"))
        end_dt = datetime.fromisoformat(end["dateTime"].replace("Z", "+00:00"))
        time_str = f"{start_dt.strftime('%I:%M %p')} - {end_dt.strftime('%I:%M %p')}"
    else:
        time_str = "All day"

    summary = event.get("summary", "(No title)")
    location = event.get("location", "")

    result = f"• {time_str}: {summary}"
    if location:
        result += f"\n  📍 {location}"

    return result


def get_events(data: dict) -> str:
    """Get calendar events."""
    try:
        service = _get_calendar_service()
    except Exception as e:
        logger.error(f"Calendar operation failed: {e}")
        return "❌ Calendar operation failed. Check OAuth credentials."

    date_str = data.get("date", "today")
    days = data.get("days", 1)

    try:
        if date_str == "today":
            start_date = datetime.now().replace(hour=0, minute=0, second=0, microsecond=0)
        elif date_str == "tomorrow":
            start_date = (datetime.now() + timedelta(days=1)).replace(
                hour=0, minute=0, second=0, microsecond=0
            )
        elif date_str == "this week":
            start_date = datetime.now().replace(hour=0, minute=0, second=0, microsecond=0)
            days = 7
        else:
            start_date = _parse_datetime(date_str, "00:00")
    except (ValueError, TypeError):
        start_date = datetime.now().replace(hour=0, minute=0, second=0, microsecond=0)

    end_date = start_date + timedelta(days=days)

    try:
        events_result = (
            service.events()
            .list(
                calendarId="primary",
                timeMin=start_date.isoformat() + "Z",
                timeMax=end_date.isoformat() + "Z",
                singleEvents=True,
                orderBy="startTime",
                maxResults=20,
            )
            .execute()
        )

        events = events_result.get("items", [])

        if not events:
            return f"📅 No events for {date_str}"

        lines = [f"📅 Calendar for {start_date.strftime('%A, %B %d')}:"]
        if days > 1:
            lines[0] = (
                f"📅 Calendar ({start_date.strftime('%b %d')} - {end_date.strftime('%b %d')}):"
            )

        current_date = None
        for event in events:
            start = event.get("start", {})
            if "dateTime" in start:
                event_date = datetime.fromisoformat(start["dateTime"].replace("Z", "+00:00")).date()
            else:
                event_date = datetime.fromisoformat(start["date"]).date()

            if days > 1 and event_date != current_date:
                current_date = event_date
                lines.append(f"\n{event_date.strftime('%A, %b %d')}:")

            lines.append(_format_event(event))

        return "\n".join(lines)

    except Exception as e:
        logger.error(f"Calendar operation failed: {e}")
        return "❌ Calendar operation failed."


def create_event(data: dict) -> str:
    """Create a calendar event."""
    try:
        service = _get_calendar_service()
    except Exception as e:
        logger.error(f"Calendar operation failed: {e}")
        return "❌ Calendar operation failed. Check OAuth credentials."

    title = data.get("title", "").strip()
    if not title:
        return "Please provide an event title"

    start_str = data.get("start", "")
    duration_minutes = data.get("duration", 60)
    location = data.get("location", "")
    description = data.get("description", "")
    attendees = data.get("attendees", [])

    try:
        start_dt = _parse_datetime(start_str)
    except ValueError as e:
        logger.error(f"Calendar operation failed: {e}")
        return "❌ Calendar operation failed. Check OAuth credentials."

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

        att_display = [
            a if isinstance(a, str) else a.get("email", str(a)) for a in (attendees or [])
        ]
        return needs_confirmation(
            "create_event",
            data,
            calendar_create_preview(title, start_str, duration_minutes, att_display),
            risk="medium",
        )

    end_dt = start_dt + timedelta(minutes=duration_minutes)

    # Timezone: CALENDAR_TIMEZONE env var, then config, then UTC
    _tz = os.environ.get("CALENDAR_TIMEZONE", "")
    if not _tz:
        try:
            from familiar.core.config import load_config

            _cfg = load_config()
            _tz = (
                getattr(_cfg, "timezone", "")
                or getattr(getattr(_cfg, "agent", None), "timezone", "")
                or ""
            )
        except Exception:
            pass
    if not _tz:
        _tz = "UTC"

    event = {
        "summary": title,
        "start": {
            "dateTime": start_dt.isoformat(),
            "timeZone": _tz,
        },
        "end": {
            "dateTime": end_dt.isoformat(),
            "timeZone": _tz,
        },
    }

    if location:
        event["location"] = location
    if description:
        event["description"] = description
    if attendees:
        event["attendees"] = [{"email": email} for email in attendees]

    try:
        created = service.events().insert(calendarId="primary", body=event).execute()
        event_link = created.get("htmlLink", "")

        return (
            f"✅ Created: {title}\n"
            f"   📅 {start_dt.strftime('%A, %B %d at %I:%M %p')}\n"
            f"   ⏱️ {duration_minutes} minutes"
            + (f"\n   📍 {location}" if location else "")
            + (f"\n   🔗 {event_link}" if event_link else "")
        )
    except Exception as e:
        logger.error(f"Calendar operation failed: {e}")
        return "❌ Calendar operation failed."


def check_availability(data: dict) -> str:
    """Check if a time slot is available."""
    try:
        service = _get_calendar_service()
    except Exception as e:
        logger.error(f"Calendar operation failed: {e}")
        return "❌ Calendar operation failed. Check OAuth credentials."

    date_str = data.get("date", "today")
    time_str = data.get("time", "")
    duration = data.get("duration", 60)

    try:
        if time_str:
            check_dt = _parse_datetime(f"{date_str} {time_str}")
        else:
            check_dt = _parse_datetime(date_str, "09:00")
    except ValueError as e:
        logger.error(f"Calendar operation failed: {e}")
        return "❌ Calendar operation failed. Check OAuth credentials."

    check_end = check_dt + timedelta(minutes=duration)

    try:
        # Get events in that window
        events_result = (
            service.events()
            .list(
                calendarId="primary",
                timeMin=check_dt.isoformat() + "Z",
                timeMax=check_end.isoformat() + "Z",
                singleEvents=True,
                orderBy="startTime",
            )
            .execute()
        )

        events = events_result.get("items", [])

        if not events:
            return f"✅ {check_dt.strftime('%A %I:%M %p')} is available ({duration} min slot)"
        else:
            conflicts = [e.get("summary", "Busy") for e in events]
            return f"❌ {check_dt.strftime('%A %I:%M %p')} has conflicts:\n" + "\n".join(
                f"  • {c}" for c in conflicts
            )
    except Exception as e:
        return f"❌ Error: {e}"


def find_free_time(data: dict) -> str:
    """Find available time slots on a given day."""
    try:
        service = _get_calendar_service()
    except Exception as e:
        logger.error(f"Calendar operation failed: {e}")
        return "❌ Calendar operation failed. Check OAuth credentials."

    date_str = data.get("date", "today")
    duration = data.get("duration", 60)
    start_hour = data.get("start_hour", 9)
    end_hour = data.get("end_hour", 17)

    try:
        day = _parse_datetime(date_str, "00:00").date()
    except (ValueError, TypeError):
        day = datetime.now().date()

    day_start = datetime.combine(day, datetime.min.time().replace(hour=start_hour))
    day_end = datetime.combine(day, datetime.min.time().replace(hour=end_hour))

    try:
        events_result = (
            service.events()
            .list(
                calendarId="primary",
                timeMin=day_start.isoformat() + "Z",
                timeMax=day_end.isoformat() + "Z",
                singleEvents=True,
                orderBy="startTime",
            )
            .execute()
        )

        events = events_result.get("items", [])

        # Build list of busy times
        busy = []
        for event in events:
            start = event.get("start", {})
            end = event.get("end", {})
            if "dateTime" in start:
                busy.append(
                    (
                        datetime.fromisoformat(start["dateTime"].replace("Z", "+00:00")).replace(
                            tzinfo=None
                        ),
                        datetime.fromisoformat(end["dateTime"].replace("Z", "+00:00")).replace(
                            tzinfo=None
                        ),
                    )
                )

        # Find free slots
        free_slots = []
        current = day_start

        for busy_start, busy_end in sorted(busy):
            if current + timedelta(minutes=duration) <= busy_start:
                free_slots.append((current, busy_start))
            current = max(current, busy_end)

        if current + timedelta(minutes=duration) <= day_end:
            free_slots.append((current, day_end))

        if not free_slots:
            return f"❌ No {duration}-minute slots available on {day.strftime('%A, %B %d')}"

        lines = [f"✅ Available times on {day.strftime('%A, %B %d')}:"]
        for slot_start, slot_end in free_slots[:5]:
            lines.append(f"  • {slot_start.strftime('%I:%M %p')} - {slot_end.strftime('%I:%M %p')}")

        return "\n".join(lines)

    except Exception as e:
        return f"❌ Error: {e}"


def delete_event(data: dict) -> str:
    """Delete/cancel an event."""
    try:
        service = _get_calendar_service()
    except Exception as e:
        logger.error(f"Calendar operation failed: {e}")
        return "I can't access your calendar yet. Run /connect google auth calendar to connect it."

    search = data.get("search", "")
    date_str = data.get("date", "today")

    if not search:
        return "Please provide event title to search for"

    try:
        day = _parse_datetime(date_str, "00:00")
    except (ValueError, TypeError):
        day = datetime.now()

    day_start = day.replace(hour=0, minute=0, second=0)
    day_end = day_start + timedelta(days=1)

    try:
        events_result = (
            service.events()
            .list(
                calendarId="primary",
                timeMin=day_start.isoformat() + "Z",
                timeMax=day_end.isoformat() + "Z",
                singleEvents=True,
                orderBy="startTime",
                q=search,
            )
            .execute()
        )

        events = events_result.get("items", [])

        if not events:
            return f"No events found matching '{search}' on {day.strftime('%B %d')}"

        if len(events) > 1 and not data.get("_confirmed"):
            lines = [f"Found {len(events)} matching events:"]
            for e in events:
                lines.append(
                    f"  • {e.get('summary')} — {e.get('start', {}).get('dateTime', '')[:16]}"
                )
            lines.append("\nPlease be more specific so I can confirm the right one.")
            return "\n".join(lines)

        event = events[0]
        event_title = event.get("summary", search)
        event_start = event.get("start", {}).get("dateTime", "")[:16]

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

            # Store resolved event ID so confirmed call skips re-search
            confirmed_input = dict(data)
            confirmed_input["_event_id"] = event["id"]
            return needs_confirmation(
                "delete_event",
                confirmed_input,
                calendar_delete_preview(event_title, event_start),
                risk="high",
            )

        event_id = data.get("_event_id") or event["id"]
        service.events().delete(calendarId="primary", eventId=event_id).execute()
        return f"✅ Deleted: {event_title}"

    except Exception as e:
        return f"❌ Error: {e}"


# Tool definitions
TOOLS = [
    {
        "name": "calendar_today",
        "description": "Get today's calendar events, or events for a specific date/range",
        "input_schema": {
            "type": "object",
            "properties": {
                "date": {
                    "type": "string",
                    "description": "Date to check (today, tomorrow, this week, next Monday, 2024-02-15)",
                    "default": "today",
                },
                "days": {"type": "integer", "description": "Number of days to show", "default": 1},
            },
        },
        "handler": get_events,
        "category": "calendar",
    },
    {
        "name": "calendar_create",
        "description": "Create a new calendar event",
        "input_schema": {
            "type": "object",
            "properties": {
                "title": {"type": "string", "description": "Event title"},
                "start": {
                    "type": "string",
                    "description": "Start date/time (tomorrow 2pm, next Tuesday 10:00, 2024-02-15 14:30)",
                },
                "duration": {
                    "type": "integer",
                    "description": "Duration in minutes",
                    "default": 60,
                },
                "location": {"type": "string"},
                "description": {"type": "string"},
                "attendees": {
                    "type": "array",
                    "items": {"type": "string"},
                    "description": "List of attendee email addresses",
                },
            },
            "required": ["title", "start"],
        },
        "handler": create_event,
        "category": "calendar",
    },
    {
        "name": "calendar_check",
        "description": "Check if a specific time is available",
        "input_schema": {
            "type": "object",
            "properties": {
                "date": {"type": "string", "description": "Date to check"},
                "time": {"type": "string", "description": "Time to check (2pm, 14:30)"},
                "duration": {
                    "type": "integer",
                    "description": "Duration needed in minutes",
                    "default": 60,
                },
            },
            "required": ["date"],
        },
        "handler": check_availability,
        "category": "calendar",
    },
    {
        "name": "calendar_free",
        "description": "Find available time slots on a given day",
        "input_schema": {
            "type": "object",
            "properties": {
                "date": {"type": "string", "default": "today"},
                "duration": {"type": "integer", "description": "Minutes needed", "default": 60},
                "start_hour": {
                    "type": "integer",
                    "description": "Earliest hour (24h)",
                    "default": 9,
                },
                "end_hour": {"type": "integer", "description": "Latest hour (24h)", "default": 17},
            },
        },
        "handler": find_free_time,
        "category": "calendar",
    },
    {
        "name": "calendar_delete",
        "description": "Delete/cancel a calendar event",
        "input_schema": {
            "type": "object",
            "properties": {
                "search": {"type": "string", "description": "Event title to search for"},
                "date": {"type": "string", "default": "today"},
                "confirm": {"type": "boolean", "default": False},
            },
            "required": ["search"],
        },
        "handler": delete_event,
        "category": "calendar",
    },
]
