import { parseISO } from 'date-fns';
import type { CalendarEvent } from '@/lib/jmap/types';

const DURATION_RE = /^P(?:(\d+)W)?(?:(\d+)D)?(?:T(?:(\d+)H)?(?:(\d+)M)?(?:(\d+)S)?)?$/;

function isDateOnlyValue(value: string): boolean {
  return /^\d{4}-\d{2}-\d{2}$/.test(value);
}

function isMidnightValue(value: string): boolean {
  if (isDateOnlyValue(value)) {
    return true;
  }

  const match = value.match(/T(\d{2}):(\d{2})(?::(\d{2})(?:\.\d+)?)?(?:Z|[+-]\d{2}:\d{2})?$/);
  if (!match) {
    return false;
  }

  return match[1] === '00' && match[2] === '00' && (match[3] ?? '00') === '00';
}

function parseDurationSeconds(duration: string | undefined): number | null {
  if (!duration) {
    return null;
  }

  const match = DURATION_RE.exec(duration);
  if (!match) {
    return null;
  }

  const weeks = parseInt(match[1] || '0', 10);
  const days = parseInt(match[2] || '0', 10);
  const hours = parseInt(match[3] || '0', 10);
  const minutes = parseInt(match[4] || '0', 10);
  const seconds = parseInt(match[5] || '0', 10);

  return (((weeks * 7 + days) * 24 + hours) * 60 + minutes) * 60 + seconds;
}

export function normalizeAllDayDurationValue(duration: string | undefined): string | undefined {
  const totalSeconds = parseDurationSeconds(duration);
  if (totalSeconds === null || totalSeconds < 86400 || totalSeconds % 86400 !== 0) {
    return duration;
  }

  return `P${totalSeconds / 86400}D`;
}

export function isAllDayEventLike(event: Pick<Partial<CalendarEvent>, 'start' | 'duration' | 'showWithoutTime'>): boolean {
  if (event.showWithoutTime) {
    return true;
  }

  if (!event.start || !event.duration || !isMidnightValue(event.start)) {
    return false;
  }

  const totalSeconds = parseDurationSeconds(event.duration);
  if (totalSeconds === null || totalSeconds < 86400 || totalSeconds % 86400 !== 0) {
    return false;
  }

  const start = parseISO(event.start);
  if (Number.isNaN(start.getTime())) {
    return false;
  }

  const end = new Date(start.getTime() + totalSeconds * 1000);
  return end.getHours() === 0
    && end.getMinutes() === 0
    && end.getSeconds() === 0
    && end.getMilliseconds() === 0;
}

/**
 * Stalwart returns "recurrenceRule" (singular) instead of RFC 8984 "recurrenceRules" (plural).
 * Normalize server responses to match the client's internal type.
 */
function normalizeStalwartPropertyNames<T extends Partial<CalendarEvent>>(event: T): T {
  const raw = event as Record<string, unknown>;
  let patched = false;
  const updates: Partial<CalendarEvent> = {};

  if ('recurrenceRule' in raw && !('recurrenceRules' in raw)) {
    // JSCalendar 2.0 (jscalendarbis-15) defines recurrenceRule as a single object,
    // but Stalwart may also return it as an array (for JMAP-created events).
    // Normalize both forms to our internal array type.
    const val = raw.recurrenceRule;
    if (val != null && !Array.isArray(val) && typeof val === 'object') {
      updates.recurrenceRules = [val] as CalendarEvent['recurrenceRules'];
    } else {
      updates.recurrenceRules = val as CalendarEvent['recurrenceRules'];
    }
    patched = true;
  }
  if ('excludedRecurrenceRule' in raw && !('excludedRecurrenceRules' in raw)) {
    const val = raw.excludedRecurrenceRule;
    if (val != null && !Array.isArray(val) && typeof val === 'object') {
      updates.excludedRecurrenceRules = [val] as CalendarEvent['excludedRecurrenceRules'];
    } else {
      updates.excludedRecurrenceRules = val as CalendarEvent['excludedRecurrenceRules'];
    }
    patched = true;
  }

  if (!patched) return event;

  const result = { ...event, ...updates } as T;
  delete (result as Record<string, unknown>).recurrenceRule;
  delete (result as Record<string, unknown>).excludedRecurrenceRule;
  return result;
}

export function normalizeCalendarEventLike<T extends Partial<CalendarEvent>>(event: T): T {
  // First normalize Stalwart's singular property names to RFC 8984 plural forms
  const normalized = normalizeStalwartPropertyNames(event);

  if (!isAllDayEventLike(normalized)) {
    return normalized;
  }

  return {
    ...normalized,
    showWithoutTime: true,
    duration: normalizeAllDayDurationValue(normalized.duration),
  } as T;
}

export function sanitizeOutgoingCalendarEventData<T extends Partial<CalendarEvent>>(event: T): T {
  const normalized = normalizeCalendarEventLike(event);
  if (!normalized.showWithoutTime) {
    return normalized;
  }

  const normalizedStart = normalized.start
    ? `${normalized.start.slice(0, 10)}T00:00:00`
    : normalized.start;

  return {
    ...normalized,
    start: normalizedStart,
    duration: normalizeAllDayDurationValue(normalized.duration),
    timeZone: null,
  } as T;
}