"""iCalendar VEVENT operations for calendar management.

Uses ``icalendar`` for parsing/serialization and ``recurring-ical-events``
for RRULE expansion. One .ics file per event, filename derived from UID.
"""

from __future__ import annotations

import logging
import uuid
from datetime import date, datetime, timedelta, timezone
from pathlib import Path
from typing import Any

import icalendar
import recurring_ical_events
from icarus.pim.common import (
    _find_ics_file,
    _parse_attendees,
    _serialize_attendee,
    _to_datetime,
)

logger = logging.getLogger(__name__)


def _format_dt(dt: datetime | date | None) -> str:
    """Format a datetime for human-readable display."""
    if dt is None:
        return ""
    if isinstance(dt, datetime):
        return dt.strftime("%A %b %d, %H:%M")
    return dt.strftime("%A %b %d")


def _event_to_dict(component: icalendar.cal.Component, path: Path | None = None) -> dict[str, Any]:
    """Convert a VEVENT component to a dictionary."""
    uid = str(component.get("uid", ""))
    summary = str(component.get("summary", ""))
    description = str(component.get("description", ""))
    location = str(component.get("location", ""))

    dtstart = _to_datetime(component.get("dtstart"))
    dtend = _to_datetime(component.get("dtend"))

    # Attendees — normalize to list (single ATTENDEE returns a vCalAddress, not a list)
    attendees, _ = _parse_attendees(component.get("attendee"))

    # Check for recurrence
    rrule = component.get("rrule")
    recurrence_text = ""
    if rrule:
        freq = rrule.get("freq", [""])[0] if isinstance(rrule, dict) else ""
        if hasattr(rrule, "to_ical"):
            recurrence_text = rrule.to_ical().decode("utf-8", errors="replace")
        elif freq:
            recurrence_text = str(freq).lower()

    return {
        "uid": uid,
        "summary": summary,
        "description": description,
        "location": location,
        "dtstart": dtstart,
        "dtend": dtend,
        "attendees": attendees,
        "recurrence": recurrence_text,
        "_path": path,
    }


def parse_event(path: Path) -> dict[str, Any]:
    """Parse a .ics file containing a single VEVENT.

    Returns:
        Event dictionary with uid, summary, dtstart, dtend, etc.
    """
    data = path.read_bytes()
    cal = icalendar.Calendar.from_ical(data)
    for component in cal.walk():
        if component.name == "VEVENT":
            return _event_to_dict(component, path)
    return {"uid": "", "summary": path.stem, "_path": path}


def list_events(
    directory: Path,
    start: date,
    end: date,
) -> list[dict[str, Any]]:
    """List events in a date range (inclusive), expanding recurrence rules.

    Uses ``recurring-ical-events`` so the agent never sees RRULE directly —
    each recurrence instance appears as a separate event dict.

    Args:
        directory: Path to the calendar directory.
        start: Start date (inclusive).
        end: End date (inclusive).

    Returns:
        List of event dicts sorted by start time.
    """
    if not directory.exists():
        return []

    # recurring-ical-events uses exclusive end, so add 1 day
    end_exclusive = end + timedelta(days=1)
    events: list[dict[str, Any]] = []

    for ics_path in sorted(directory.glob("*.ics")):
        try:
            data = ics_path.read_bytes()
            cal = icalendar.Calendar.from_ical(data)
        except Exception as e:  # noqa: BLE001
            logger.warning("Failed to parse %s: %s", ics_path, e)
            continue

        # Expand recurring events into concrete instances
        try:
            expanded = recurring_ical_events.of(cal).between(start, end_exclusive)
        except Exception:  # noqa: BLE001
            # Fallback: extract events without recurrence expansion
            expanded = [c for c in cal.walk() if c.name == "VEVENT"]

        for component in expanded:
            ev = _event_to_dict(component, ics_path)
            if ev["dtstart"] is not None:
                events.append(ev)

    # Sort by start time
    events.sort(key=lambda e: e["dtstart"] or datetime.min.replace(tzinfo=timezone.utc))
    return events


def _build_calendar(data: dict[str, Any], uid: str) -> icalendar.Calendar:
    """Build an iCalendar object from event data."""
    cal = icalendar.Calendar()
    cal.add("prodid", "-//ICARUS//PIM//EN")
    cal.add("version", "2.0")

    event = icalendar.Event()
    event.add("uid", uid)
    event.add("summary", data.get("summary", ""))

    # Start time (required)
    dtstart = data.get("dtstart")
    if isinstance(dtstart, str):
        dtstart = datetime.fromisoformat(dtstart)
    if dtstart:
        event.add("dtstart", dtstart)

    # End time (optional, defaults to start + 1h)
    dtend = data.get("dtend")
    if isinstance(dtend, str):
        dtend = datetime.fromisoformat(dtend)
    if dtend:
        event.add("dtend", dtend)
    elif dtstart:
        event.add("dtend", dtstart + timedelta(hours=1))

    # Optional fields
    if data.get("description"):
        event.add("description", data["description"])
    if data.get("location"):
        event.add("location", data["location"])
    if data.get("rrule"):
        event.add("rrule", data["rrule"])

    # Attendees
    for att in data.get("attendees", []):
        _serialize_attendee(event, att)

    # Timestamp
    event.add("dtstamp", datetime.now(tz=timezone.utc))

    cal.add_component(event)
    return cal


def create_event(directory: Path, data: dict[str, Any]) -> Path:
    """Create a new calendar event .ics file.

    Returns:
        Path to the created file.
    """
    directory.mkdir(parents=True, exist_ok=True)
    uid = data.get("uid") or str(uuid.uuid4())
    cal = _build_calendar(data, uid)

    # Filename from UID
    safe_uid = uid.replace("/", "_").replace("\\", "_")
    path = directory / f"{safe_uid}.ics"
    path.write_bytes(cal.to_ical())
    return path


def update_event(directory: Path, uid: str, updates: dict[str, Any]) -> Path:
    """Update an existing event by UID.

    Returns:
        Path to the updated file.

    Raises:
        FileNotFoundError: If no event with the given UID exists.
    """
    # Find the file by UID
    target = _find_event_file(directory, uid)
    if target is None:
        msg = f"Event not found: {uid}"
        raise FileNotFoundError(msg)

    # Parse existing, merge updates
    existing = parse_event(target)
    for key, value in updates.items():
        if key.startswith("_"):
            continue
        existing[key] = value

    cal = _build_calendar(existing, uid)
    target.write_bytes(cal.to_ical())
    return target


def delete_event(directory: Path, uid: str) -> bool:
    """Delete an event by UID.

    Returns:
        True if deleted, False if not found.
    """
    target = _find_event_file(directory, uid)
    if target is None:
        return False
    target.unlink()
    return True


def _find_event_file(directory: Path, uid: str) -> Path | None:
    """Find the .ics file containing an event with the given UID."""
    return _find_ics_file(directory, uid, "VEVENT")
