TOOL_NAME = "outlook_update_calendar_event_v2"
TOOL_DESCRIPTION = """Update Outlook calendar event with timezone handling, recurrence awareness, and robust error handling."""
TOOL_ARGS = '{"event_id": "str", "summary": "str (optional)", "start": "str (optional, ISO 8601 datetime)", "end": "str (optional, ISO 8601 datetime)", "location": "str (optional)", "description": "str (optional)"}'

import requests, json, time
from dateutil import parser, tz

from tools._std import tool_error, tool_ok

GRAPH_BASE_URL = "https://graph.microsoft.com/v1.0/me/events"

# Placeholder for authentication header retrieval. Replace with actual implementation.
def get_auth_header():
    token = "PLACEHOLDER_ACCESS_TOKEN"
    return {"Authorization": f"Bearer {token}", "Content-Type": "application/json"}

def log(message: str):
    # Simple logger; replace with proper logging in production.
    print(message)

def enforce_amsterdam_timezone(dt_str: str) -> str:
    """Parse a datetime string, convert it to Europe/Amsterdam timezone, and return ISO string."""
    dt = parser.isoparse(dt_str)
    amsterdam = tz.gettz("Europe/Amsterdam")
    if dt.tzinfo is None:
        dt = dt.replace(tzinfo=amsterdam)
    else:
        dt = dt.astimezone(amsterdam)
    return dt.isoformat()

def fetch_event(event_id: str) -> dict:
    url = f"{GRAPH_BASE_URL}/{event_id}"
    headers = get_auth_header()
    headers["Prefer"] = "outlook.timezone=\"Europe/Amsterdam\""
    resp = requests.get(url, headers=headers)
    log(f"GET {url} -> {resp.status_code}")
    log(f"Response body: {resp.text}")
    resp.raise_for_status()
    return resp.json()

def patch_event(event_id: str, payload: dict) -> requests.Response:
    url = f"{GRAPH_BASE_URL}/{event_id}"
    headers = get_auth_header()
    headers["Prefer"] = "outlook.timezone=\"Europe/Amsterdam\""
    resp = requests.patch(url, headers=headers, data=json.dumps(payload))
    log(f"PATCH {url} payload: {json.dumps(payload)} -> {resp.status_code}")
    log(f"Response body: {resp.text}")
    return resp

def split_occurrence(event: dict) -> dict:
    """Create a new single event based on the occurrence, removing recurrence, then delete the original occurrence."""
    new_event = {
        "subject": event.get("subject"),
        "start": event.get("start"),
        "end": event.get("end"),
        "location": event.get("location"),
        "body": event.get("body"),
    }
    # Ensure no recurrence field
    new_event.pop("recurrence", None)
    url = GRAPH_BASE_URL
    headers = get_auth_header()
    headers["Prefer"] = "outlook.timezone=\"Europe/Amsterdam\""
    resp = requests.post(url, headers=headers, data=json.dumps(new_event))
    log(f"POST split event -> {resp.status_code}")
    log(f"Response body: {resp.text}")
    resp.raise_for_status()
    created = resp.json()
    # Delete original occurrence
    del_resp = requests.delete(f"{GRAPH_BASE_URL}/{event['id']}", headers=get_auth_header())
    log(f"DELETE original occurrence {event['id']} -> {del_resp.status_code}")
    log(f"Response body: {del_resp.text}")
    return created

def run(args: dict) -> str:
    if "event_id" not in args:
        return tool_error("Missing required argument 'event_id'.")
    event_id = args["event_id"]
    # Build payload from provided optional fields
    payload = {}
    if args.get("summary"):
        payload["subject"] = args["summary"]
    if args.get("start"):
        payload["start"] = {"dateTime": enforce_amsterdam_timezone(args["start"]), "timeZone": "Europe/Amsterdam"}
    if args.get("end"):
        payload["end"] = {"dateTime": enforce_amsterdam_timezone(args["end"]), "timeZone": "Europe/Amsterdam"}
    if args.get("location"):
        payload["location"] = {"displayName": args["location"]}
    if args.get("description"):
        payload["body"] = {"contentType": "HTML", "content": args["description"]}
    # Fetch current event details
    try:
        event = fetch_event(event_id)
    except Exception as e:
        return tool_error(
            f"Failed to fetch event {event_id}: {e}",
            {"event_id": event_id},
        )
    event_type = event.get("type", "singleInstance")
    # Attempt update with retries
    attempts = 0
    last_status = None
    last_text = ""
    while attempts < 3:
        attempts += 1
        resp = patch_event(event_id, payload)
        last_status = resp.status_code
        last_text = resp.text
        if 200 <= resp.status_code < 300:
            return tool_ok(
                {"event_id": event_id, "attempts": attempts, "payload": payload},
                f"Outlook event {event_id} updated successfully (attempt {attempts}).",
            )
        # Parse error code if possible
        try:
            err_json = resp.json()
            err_code = err_json.get("error", {}).get("code", "")
        except Exception:
            err_code = ""
        if resp.status_code == 400 and err_code == "ErrorOccurrenceCrossingBoundary" and event_type == "occurrence":
            log("Detected ErrorOccurrenceCrossingBoundary – attempting to split occurrence.")
            try:
                new_event = split_occurrence(event)
                new_id = new_event.get("id")
                # Retry update on the new event
                resp2 = patch_event(new_id, payload)
                if 200 <= resp2.status_code < 300:
                    return tool_ok(
                        {
                            "event_id": event_id,
                            "new_event_id": new_id,
                            "attempts": attempts,
                            "payload": payload,
                        },
                        f"Occurrence split and updated successfully (new ID {new_id}).",
                    )
                else:
                    return tool_error(
                        f"Split succeeded but update failed: {resp2.status_code} {resp2.text}",
                        {
                            "event_id": event_id,
                            "new_event_id": new_id,
                            "status_code": resp2.status_code,
                            "response_text": resp2.text,
                        },
                    )
            except Exception as e:
                return tool_error(
                    f"Failed to split occurrence: {e}",
                    {"event_id": event_id},
                )
        # Other errors – exponential backoff
        wait_seconds = 2 ** attempts
        log(f"Retrying after {wait_seconds}s (attempt {attempts}) due to error {resp.status_code}: {resp.text}")
        time.sleep(wait_seconds)
    return tool_error(
        f"Failed to update Outlook event after {attempts} attempts. Last response: {last_status} {last_text}",
        {
            "event_id": event_id,
            "attempts": attempts,
            "status_code": last_status,
            "response_text": last_text,
        },
    )
