"""CalDAV tools — calendars via CalDAV (RFC 4791)."""

import json
from datetime import datetime, timedelta, timezone

import defusedxml.ElementTree as ET
import icalendar
import requests
from mcp.server.fastmcp import FastMCP

from pyfastmail_mcp.dav_client import CALDAV_BASE, DAVClient
from pyfastmail_mcp.exceptions import FastmailError

_DAV_NS = "DAV:"
_CAL_NS = "urn:ietf:params:xml:ns:caldav"
_CS_NS = "http://calendarserver.org/ns/"
_APPLE_NS = "http://apple.com/ns/ical/"

_PROPFIND_CALENDARS = """<?xml version="1.0" encoding="UTF-8"?>
<D:propfind xmlns:D="DAV:" xmlns:C="urn:ietf:params:xml:ns:caldav"
            xmlns:A="http://apple.com/ns/ical/">
  <D:prop>
    <D:displayname/>
    <D:resourcetype/>
    <C:calendar-description/>
    <A:calendar-color/>
  </D:prop>
</D:propfind>"""


def _tag(ns: str, local: str) -> str:
    return f"{{{ns}}}{local}"


def _parse_calendars(xml_text: str) -> list[dict]:
    root = ET.fromstring(xml_text)
    results = []
    for response in root.iter(_tag(_DAV_NS, "response")):
        href_el = response.find(_tag(_DAV_NS, "href"))
        href = href_el.text.strip() if href_el is not None and href_el.text else ""

        resourcetype = response.find(f".//{_tag(_DAV_NS, 'resourcetype')}")
        if resourcetype is None:
            continue
        if resourcetype.find(_tag(_CAL_NS, "calendar")) is None:
            continue

        displayname_el = response.find(f".//{_tag(_DAV_NS, 'displayname')}")
        displayname = (
            displayname_el.text.strip()
            if displayname_el is not None and displayname_el.text
            else ""
        )

        desc_el = response.find(f".//{_tag(_CAL_NS, 'calendar-description')}")
        description = (
            desc_el.text.strip() if desc_el is not None and desc_el.text else ""
        )

        color_el = response.find(f".//{_tag(_APPLE_NS, 'calendar-color')}")
        color = color_el.text.strip() if color_el is not None and color_el.text else ""

        results.append(
            {
                "href": href,
                "displayname": displayname,
                "description": description,
                "color": color,
            }
        )
    return results


def _calendar_query_xml(start: str, end: str) -> str:
    return f"""<?xml version="1.0" encoding="UTF-8"?>
<C:calendar-query xmlns:D="DAV:" xmlns:C="urn:ietf:params:xml:ns:caldav">
  <D:prop>
    <D:getetag/>
    <C:calendar-data/>
  </D:prop>
  <C:filter>
    <C:comp-filter name="VCALENDAR">
      <C:comp-filter name="VEVENT">
        <C:time-range start="{start}" end="{end}"/>
      </C:comp-filter>
    </C:comp-filter>
  </C:filter>
</C:calendar-query>"""


def _parse_events(xml_text: str) -> list[dict]:
    root = ET.fromstring(xml_text)
    results = []
    for response in root.iter(_tag(_DAV_NS, "response")):
        href_el = response.find(_tag(_DAV_NS, "href"))
        href = href_el.text.strip() if href_el is not None and href_el.text else ""

        cal_data_el = response.find(f".//{_tag(_CAL_NS, 'calendar-data')}")
        if cal_data_el is None or not cal_data_el.text:
            continue

        try:
            cal = icalendar.Calendar.from_ical(cal_data_el.text)
        except (FastmailError, requests.RequestException, ValueError):
            continue

        for comp in cal.walk():
            if comp.name != "VEVENT":
                continue
            dtstart = comp.get("DTSTART")
            dtend = comp.get("DTEND")
            results.append(
                {
                    "href": href,
                    "uid": str(comp.get("UID", "")),
                    "summary": str(comp.get("SUMMARY", "")),
                    "dtstart": str(dtstart.dt) if dtstart else "",
                    "dtend": str(dtend.dt) if dtend else "",
                    "location": str(comp.get("LOCATION", "")),
                    "description": str(comp.get("DESCRIPTION", "")),
                }
            )
    results.sort(key=lambda e: e["dtstart"])
    return results


def register(server: FastMCP, dav_client: DAVClient) -> None:
    @server.tool()
    async def calendar_list_calendars() -> str:
        """List all CalDAV calendars for the authenticated Fastmail account."""
        try:
            home_url = dav_client.discover_caldav_home()
            dav_client.validate_dav_url(home_url)
            resp = dav_client.propfind(home_url, depth="1", body=_PROPFIND_CALENDARS)
            calendars = _parse_calendars(resp.text)
            return json.dumps(calendars, indent=2)
        except (FastmailError, requests.RequestException, ValueError) as exc:
            return json.dumps({"error": str(exc)})

    @server.tool()
    async def calendar_list_events(
        calendar_href: str,
        start_date: str | None = None,
        end_date: str | None = None,
    ) -> str:
        """List events in a CalDAV calendar within a date range.

        Args:
            calendar_href: The href of the calendar (from calendar_list_calendars).
            start_date: ISO date string (YYYY-MM-DD). Defaults to today.
            end_date: ISO date string (YYYY-MM-DD). Defaults to 7 days from start.
        """
        try:
            now = datetime.now(tz=timezone.utc)
            start_dt = (
                datetime.fromisoformat(start_date).replace(tzinfo=timezone.utc)
                if start_date
                else now.replace(hour=0, minute=0, second=0, microsecond=0)
            )
            end_dt = (
                datetime.fromisoformat(end_date).replace(tzinfo=timezone.utc)
                if end_date
                else start_dt + timedelta(days=7)
            )

            start_str = start_dt.strftime("%Y%m%dT%H%M%SZ")
            end_str = end_dt.strftime("%Y%m%dT%H%M%SZ")

            url = (
                f"{CALDAV_BASE}{calendar_href}"
                if not calendar_href.startswith("http")
                else calendar_href
            )
            dav_client.validate_dav_url(url)
            body = _calendar_query_xml(start_str, end_str)
            resp = dav_client.report(url, body)
            events = _parse_events(resp.text)
            return json.dumps(events, indent=2)
        except (FastmailError, requests.RequestException, ValueError) as exc:
            return json.dumps({"error": str(exc)})
