import { randomUUID } from 'crypto';

const BASE = () => (process.env.RADICALE_URL || 'http://localhost:5232').replace(/\/$/, '');
const DEFAULT_USER = () => process.env.RADICALE_USER || 'alvis';

let _password = null;

export function initRadicale(password, _user) {
  _password = password;
  console.log('Radicale: ready');
}

function authHeader(user) {
  if (!_password) throw new Error('Radicale not initialized');
  const u = user || DEFAULT_USER();
  return 'Basic ' + Buffer.from(`${u}:${_password}`).toString('base64');
}

async function dav(method, path, { user, headers = {}, body } = {}) {
  const res = await fetch(`${BASE()}${path}`, {
    method,
    headers: { Authorization: authHeader(user), ...headers },
    body,
  });
  const text = await res.text();
  if (!res.ok && res.status !== 207) {
    throw new Error(`Radicale ${method} ${path}: ${res.status} ${text}`);
  }
  return { status: res.status, text, headers: res.headers };
}

function userPath(user) {
  return `/${encodeURIComponent(user || DEFAULT_USER())}`;
}

// Extract <response> blocks; for each, pull href + displayname + resourcetype components
function parseMultistatus(xml) {
  const responses = [];
  const re = /<(?:[a-zA-Z0-9]+:)?response\b[^>]*>([\s\S]*?)<\/(?:[a-zA-Z0-9]+:)?response>/g;
  let m;
  while ((m = re.exec(xml)) !== null) {
    const block = m[1];
    const href = (block.match(/<(?:[a-zA-Z0-9]+:)?href\b[^>]*>([\s\S]*?)<\/(?:[a-zA-Z0-9]+:)?href>/) || [])[1];
    const displayname = (block.match(/<(?:[a-zA-Z0-9]+:)?displayname\b[^>]*>([\s\S]*?)<\/(?:[a-zA-Z0-9]+:)?displayname>/) || [])[1];
    const isCalendar = /<(?:[a-zA-Z0-9]+:)?calendar\b/.test(block);
    const isAddressbook = /<(?:[a-zA-Z0-9]+:)?addressbook\b/.test(block);
    const isCollection = /<(?:[a-zA-Z0-9]+:)?collection\b/.test(block);
    const getetag = (block.match(/<(?:[a-zA-Z0-9]+:)?getetag\b[^>]*>([\s\S]*?)<\/(?:[a-zA-Z0-9]+:)?getetag>/) || [])[1];
    const componentSet = [];
    const csRe = /<(?:[a-zA-Z0-9]+:)?comp\b[^>]*\sname="([^"]+)"/g;
    let cm;
    while ((cm = csRe.exec(block)) !== null) componentSet.push(cm[1]);
    responses.push({ href, displayname, isCalendar, isAddressbook, isCollection, getetag, componentSet });
  }
  return responses;
}

const PROPFIND_COLLECTIONS = `<?xml version="1.0" encoding="utf-8"?>
<d:propfind xmlns:d="DAV:" xmlns:c="urn:ietf:params:xml:ns:caldav" xmlns:ic="http://apple.com/ns/ical/">
  <d:prop>
    <d:displayname/>
    <d:resourcetype/>
    <c:supported-calendar-component-set/>
    <ic:calendar-color/>
  </d:prop>
</d:propfind>`;

const PROPFIND_EVENTS = `<?xml version="1.0" encoding="utf-8"?>
<d:propfind xmlns:d="DAV:">
  <d:prop>
    <d:getetag/>
    <d:resourcetype/>
  </d:prop>
</d:propfind>`;

export async function radicaleListCalendars(user) {
  const { text } = await dav('PROPFIND', `${userPath(user)}/`, {
    user,
    headers: { Depth: '1', 'Content-Type': 'application/xml' },
    body: PROPFIND_COLLECTIONS,
  });
  const items = parseMultistatus(text);
  const out = [];
  for (const r of items) {
    if (!r.href || !r.isCalendar) continue;
    // skip the parent (the user principal itself, which usually has no displayname)
    const id = decodeURIComponent(r.href.replace(/\/$/, '').split('/').pop());
    if (!id || id === (user || DEFAULT_USER())) continue;
    out.push({
      id,
      displayname: r.displayname || null,
      components: r.componentSet,
      href: r.href,
    });
  }
  return out;
}

function parseIcsField(ics, field, { component = 'VEVENT' } = {}) {
  // restrict search to the named component block (default VEVENT)
  const block = (() => {
    if (!component) return ics;
    const re = new RegExp(`BEGIN:${component}([\\s\\S]*?)END:${component}`);
    const m = ics.match(re);
    return m ? m[1] : ics;
  })();
  const re = new RegExp(`(?:^|\\n)${field}(?:;[^:\\n]*)?:([^\\n\\r]*)`);
  const m = block.match(re);
  return m ? m[1].trim() : null;
}

export async function radicaleListEvents(calendarId, user) {
  const path = `${userPath(user)}/${encodeURIComponent(calendarId)}/`;
  const { text } = await dav('PROPFIND', path, {
    user,
    headers: { Depth: '1', 'Content-Type': 'application/xml' },
    body: PROPFIND_EVENTS,
  });
  const items = parseMultistatus(text);
  const out = [];
  for (const r of items) {
    if (!r.href || !r.href.endsWith('.ics')) continue;
    const filename = decodeURIComponent(r.href.split('/').pop());
    // GET event to extract summary + start time
    const ev = await dav('GET', r.href, { user });
    const summary = parseIcsField(ev.text, 'SUMMARY');
    const dtstart = parseIcsField(ev.text, 'DTSTART');
    const dtend = parseIcsField(ev.text, 'DTEND');
    const uid = parseIcsField(ev.text, 'UID');
    out.push({ filename, uid, summary, dtstart, dtend, etag: r.getetag, href: r.href });
  }
  return out;
}

export async function radicaleGetEvent(calendarId, filename, user) {
  const path = `${userPath(user)}/${encodeURIComponent(calendarId)}/${encodeURIComponent(filename)}`;
  const { text } = await dav('GET', path, { user });
  return text;
}

export async function radicaleCreateCalendar({ displayname, color, components = ['VEVENT'], user, id }) {
  if (!displayname) throw new Error('displayname required');
  const calId = id || randomUUID();
  const compXml = components.map(c => `<c:comp name="${c}"/>`).join('');
  const body = `<?xml version="1.0" encoding="utf-8"?>
<c:mkcalendar xmlns:d="DAV:" xmlns:c="urn:ietf:params:xml:ns:caldav" xmlns:ic="http://apple.com/ns/ical/">
  <d:set>
    <d:prop>
      <d:displayname>${escapeXml(displayname)}</d:displayname>
      <c:supported-calendar-component-set>${compXml}</c:supported-calendar-component-set>
      ${color ? `<ic:calendar-color>${escapeXml(color)}</ic:calendar-color>` : ''}
    </d:prop>
  </d:set>
</c:mkcalendar>`;
  const path = `${userPath(user)}/${calId}/`;
  await dav('MKCALENDAR', path, {
    user,
    headers: { 'Content-Type': 'application/xml' },
    body,
  });
  return { id: calId, displayname, components };
}

export async function radicaleDeleteCalendar(calendarId, user) {
  await dav('DELETE', `${userPath(user)}/${encodeURIComponent(calendarId)}/`, { user });
  return { deleted: calendarId };
}

export async function radicalePutEvent(calendarId, filename, ics, user) {
  const name = filename.endsWith('.ics') ? filename : `${filename}.ics`;
  const path = `${userPath(user)}/${encodeURIComponent(calendarId)}/${encodeURIComponent(name)}`;
  await dav('PUT', path, {
    user,
    headers: { 'Content-Type': 'text/calendar; charset=utf-8' },
    body: ics,
  });
  return { put: name };
}

export async function radicaleDeleteEvent(calendarId, filename, user) {
  const path = `${userPath(user)}/${encodeURIComponent(calendarId)}/${encodeURIComponent(filename)}`;
  await dav('DELETE', path, { user });
  return { deleted: filename };
}

export async function radicaleMoveEvent({ fromCalendar, toCalendar, filename, user }) {
  if (!fromCalendar || !toCalendar || !filename) {
    throw new Error('fromCalendar, toCalendar, filename required');
  }
  const ics = await radicaleGetEvent(fromCalendar, filename, user);
  await radicalePutEvent(toCalendar, filename, ics, user);
  await radicaleDeleteEvent(fromCalendar, filename, user);
  return { moved: filename, from: fromCalendar, to: toCalendar };
}

function escapeXml(s) {
  return String(s).replace(/[<>&'"]/g, c => ({ '<': '&lt;', '>': '&gt;', '&': '&amp;', "'": '&apos;', '"': '&quot;' }[c]));
}
