/**
 * CalDAV write wrappers — broker boundary enforcement (D-12).
 *
 * This file is the ONLY place outside client.ts/sync.ts/poller.ts that issues
 * PUT/DELETE to Fastmail via tsdav. Route handlers MUST NOT import tsdav directly
 * (D-12 broker boundary). The outbox worker (outboxWorker.ts) calls these functions.
 *
 * T-03-04 (Spoofing / etag forgery): etag is sourced server-side from calendarEvents.etag
 * by the worker — never accepted from the browser. write.ts only forwards what the
 * server supplies.
 *
 * T-03-05 (Elevation of privilege): Calendar ownership is enforced at the route layer
 * (Plan 04, V4). write.ts is a low-level primitive with no auth context.
 *
 * Status code interpretation is intentionally deferred to the caller (outboxWorker.ts):
 *   - 201 / 204: success
 *   - 412: conflict (D-08) → worker routes to conflict flow, no retry
 *   - 400 / 401 / 403: hard fail → worker stops retrying immediately (D-07)
 *   - 5xx / network error: transient → worker applies exponential backoff (D-07)
 *
 * Sources:
 *   - https://tsdav.vercel.app/docs/caldav/createCalendarObject
 *   - https://tsdav.vercel.app/docs/caldav/updateCalendarObject
 *   - https://github.com/natelindev/tsdav/blob/main/src/request.ts (If-Match confirmed)
 */

import type { FastmailClient } from './client.js';
import type { DAVCalendar } from 'tsdav';

/**
 * Creates a new CalDAV object via PUT with If-None-Match: * (create semantics).
 *
 * @param client      - Authenticated tsdav DAVClient
 * @param calendar    - Target DAVCalendar (includes the calendar URL)
 * @param uid         - Event UID; used as the object filename (`${uid}.ics`)
 * @param icsString   - Full VCALENDAR/VEVENT string (from buildVeventString)
 * @returns           Raw Response — caller interprets status codes
 */
export async function createCalendarEvent(
  client: FastmailClient,
  calendar: DAVCalendar,
  uid: string,
  icsString: string,
): Promise<Response> {
  return client.createCalendarObject({
    calendar,
    filename: `${uid}.ics`,
    iCalString: icsString,
  });
}

/**
 * Updates an existing CalDAV object via PUT with If-Match: <etag> (D-08 conflict check).
 *
 * @param client             - Authenticated tsdav DAVClient
 * @param calendarObjectUrl  - Full URL of the object (from calendarEvents.objectUrl)
 * @param icsString          - Updated VCALENDAR/VEVENT string
 * @param etag               - Cached etag from calendarEvents.etag; null is passed as ''
 *                             (tsdav: etag → If-Match header; '' = no If-Match = unconditional)
 * @returns                  Raw Response — 412 signals conflict (D-08)
 */
export async function updateCalendarEvent(
  client: FastmailClient,
  calendarObjectUrl: string,
  icsString: string,
  etag: string | null,
): Promise<Response> {
  // WR-03: a missing etag maps to NO If-Match header → an UNCONDITIONAL PUT, which
  // defeats D-08 conflict detection for exactly the rows most likely to be stale (an
  // event cached before an etag was captured, or one Fastmail omitted the etag for).
  // We do not block the write (it would strand the user's edit), but we make the
  // unconditional-write path observable so it can be diagnosed instead of silently
  // overwriting a concurrent external edit with no 412.
  if (etag == null || etag === '') {
    console.warn(
      `[write] updateCalendarObject dispatching with NO If-Match (unconditional PUT) — conflict detection disabled for url=${calendarObjectUrl}`,
    );
  }
  return client.updateCalendarObject({
    calendarObject: {
      url: calendarObjectUrl,
      data: icsString,
      etag: etag ?? '', // tsdav maps etag → If-Match header; '' skips the header (safe default)
    },
  });
}

/**
 * Deletes a CalDAV object via DELETE with If-Match: <etag> (D-08 conflict check).
 *
 * @param client             - Authenticated tsdav DAVClient
 * @param calendarObjectUrl  - Full URL of the object (from calendarEvents.objectUrl)
 * @param etag               - Cached etag from calendarEvents.etag; null is passed as ''
 * @returns                  Raw Response — 412 signals conflict (D-08)
 */
export async function deleteCalendarEvent(
  client: FastmailClient,
  calendarObjectUrl: string,
  etag: string | null,
): Promise<Response> {
  // WR-03: see updateCalendarEvent — a missing etag is an unconditional DELETE that
  // bypasses D-08 conflict detection. Log so the path is observable rather than silent.
  if (etag == null || etag === '') {
    console.warn(
      `[write] deleteCalendarObject dispatching with NO If-Match (unconditional DELETE) — conflict detection disabled for url=${calendarObjectUrl}`,
    );
  }
  return client.deleteCalendarObject({
    calendarObject: {
      url: calendarObjectUrl,
      data: '', // tsdav deleteCalendarObject requires the DAVCalendarObject shape; data unused
      etag: etag ?? '',
    },
  });
}
