import { DAVClient } from 'tsdav';
import ICAL from 'ical.js';

export interface SynologyCalendarEvent {
  id: string;
  title: string;
  startDate: string;
  endDate: string;
  description?: string;
  location?: string;
  url?: string;
  recurringEventId?: string;
  isRecurring?: boolean;
}

/**
 * Format Date to local YYYY-MM-DD string without timezone shift.
 */
function formatDateToLocalISO(date: Date): string {
  const year = date.getFullYear();
  const month = String(date.getMonth() + 1).padStart(2, '0');
  const day = String(date.getDate()).padStart(2, '0');
  return `${year}-${month}-${day}`;
}

export interface SynologyCalendar {
  id: string;
  title: string;
  color?: string;
  isPrimary?: boolean;
}

/**
 * Create a configured DAV client for Synology Calendar
 */
const createClient = (serverUrl: string, username: string, password: string) => {
  let url = serverUrl.replace(/\/$/, '');
  if (!url.includes('/caldav')) {
    url = `${url}/caldav/${username}`;
  }
  return new DAVClient({
    serverUrl: url,
    credentials: {
      username,
      password,
    },
    authMethod: 'Basic',
    defaultAccountType: 'caldav',
  });
};

/**
 * Login with fallback: Synology's CalDAV doesn't support well-known discovery,
 * so if login() fails with "cannot find homeUrl", retry with the serverUrl as homeUrl.
 */
const loginClient = async (client: DAVClient, serverUrl: string, username: string) => {
  try {
    await client.login();
  } catch (err: any) {
    if (err?.message?.includes('homeUrl')) {
      // Synology doesn't support .well-known discovery — derive homeUrl from serverUrl
      let homeUrl = serverUrl.replace(/\/$/, '');
      if (!homeUrl.includes('/caldav')) {
        homeUrl = `${homeUrl}/caldav/${username}`;
      }
      // Strip any calendar-specific path segments (UUID), keep up to username
      const match = homeUrl.match(/^(https?:\/\/[^/]+\/caldav(?:\.php)?\/[^/]+)\/?/);
      if (match) homeUrl = match[1] + '/';

      console.log('[SYNOLOGY CALENDAR] Well-known discovery failed, retrying with homeUrl:', homeUrl);
      (client as any).account = {
        serverUrl: client.serverUrl,
        accountType: 'caldav',
        homeUrl,
        rootUrl: homeUrl, // tsdav requires rootUrl for fetchCalendars
      };
    } else {
      throw err;
    }
  }
};

/**
 * Validate credentials by attempting to fetch calendars
 * @returns List of found calendars if successful
 */
export const validateCredentials = async (serverUrl: string, username: string, password: string): Promise<SynologyCalendar[]> => {
  try {
    const client = createClient(serverUrl, username, password);
    await loginClient(client, serverUrl, username);

    const calendars = await client.fetchCalendars();

    // Filter to VEVENT-only calendars — exclude VTODO (Reminders) collections
    const eventCalendars = calendars.filter(cal => {
      const components: string[] = (cal as any).components || [];
      // Keep if components include VEVENT, or if components is empty/undefined
      if (components.length === 0) return true;
      return components.includes('VEVENT');
    });

    const mappedCalendars = eventCalendars.map(cal => ({
      id: cal.url, // Using URL as ID for CalDAV
      title: (cal.displayName as string) || 'Untitled Calendar',
      color: cal.calendarColor,
      isPrimary: false,
    }));

    console.log('[SYNOLOGY CALENDAR] Found calendars:', mappedCalendars.length, '(filtered from', calendars.length, 'total)');
    return mappedCalendars;
  } catch (error) {
    console.error('Synology Calendar validation failed:', error);
    throw new Error('Invalid credentials or unable to connect to Synology Server.');
  }
};

/**
 * Get user calendars (wrapper around validateCredentials for now as they do the same)
 */
export const getUserCalendars = validateCredentials;

/**
 * Get upcoming events for a specified time period from a specific calendar
 */
export const getUpcomingEvents = async (
  serverUrl: string,
  username: string,
  password: string,
  calendarUrl: string,
  timeMin: string,
  timeMax: string
): Promise<SynologyCalendarEvent[]> => {
  try {
    const client = createClient(serverUrl, username, password);
    await loginClient(client, serverUrl, username);

    const calendars = await client.fetchCalendars();
    const getPath = (url: string) => {
      try { return new URL(url).pathname; } catch { return url; }
    };
    const targetPath = getPath(calendarUrl);
    const targetCalendar = calendars.find(c => getPath(c.url) === targetPath);

    if (!targetCalendar) {
      throw new Error(`Calendar not found: ${calendarUrl}`);
    }

    const events = await client.fetchCalendarObjects({
      calendar: targetCalendar,
      timeRange: {
        start: new Date(timeMin).toISOString(),
        end: new Date(timeMax).toISOString(),
      },
    });

    const parsedEvents: SynologyCalendarEvent[] = [];
    const minTime = new Date(timeMin).getTime();
    const maxTime = new Date(timeMax).getTime();

    events.forEach(eventObj => {
      const data = (eventObj as any).data;
      if (!data) return;

      try {
        const jcalData = ICAL.parse(data);
        const comp = new ICAL.Component(jcalData);
        const vevents = comp.getAllSubcomponents('vevent');

        // Separate base events (with RRULE) from recurrence exceptions (with RECURRENCE-ID)
        const baseEvents: any[] = [];
        const exceptions: Map<string, any[]> = new Map();

        vevents.forEach((vevent: any) => {
          const recurrenceId = vevent.getFirstPropertyValue('recurrence-id');
          if (recurrenceId) {
            const event = new ICAL.Event(vevent);
            const uid = event.uid;
            if (!exceptions.has(uid)) exceptions.set(uid, []);
            exceptions.get(uid)!.push(vevent);
          } else {
            baseEvents.push(vevent);
          }
        });

        baseEvents.forEach((vevent: any) => {
          const event = new ICAL.Event(vevent);
          const rrule = vevent.getFirstPropertyValue('rrule');

          if (rrule) {
            const uid = event.uid;
            const exceptionVevents = exceptions.get(uid) || [];
            const exceptionDates = new Set<string>();
            const isAllDayRecurring = event.startDate.isDate === true;

            exceptionVevents.forEach((exVevent: any) => {
              const exEvent = new ICAL.Event(exVevent);
              const recId = exVevent.getFirstPropertyValue('recurrence-id');
              if (recId) {
                exceptionDates.add(recId.toJSDate().toISOString());
              }

              const exStart = exEvent.startDate.toJSDate();
              const exEnd = exEvent.endDate.toJSDate();
              const exIsAllDay = exEvent.startDate.isDate === true;

              if (exEnd.getTime() >= minTime && exStart.getTime() <= maxTime) {
                parsedEvents.push({
                  id: `caldav::${eventObj.url}::${exEvent.uid}::${exStart.toISOString()}`,
                  title: exEvent.summary || 'Untitled Event',
                  startDate: exIsAllDay ? formatDateToLocalISO(exStart) : exStart.toISOString(),
                  endDate: exIsAllDay ? formatDateToLocalISO(exEnd) : exEnd.toISOString(),
                  description: exEvent.description,
                  location: exEvent.location,
                  url: exVevent.getFirstPropertyValue('url')?.toString() || undefined,
                  recurringEventId: exEvent.uid,
                  isRecurring: true,
                });
              }
            });

            try {
              const duration = event.endDate.toJSDate().getTime() - event.startDate.toJSDate().getTime();
              const iter = event.iterator();
              let next;
              let safetyCount = 0;

              while ((next = iter.next()) && safetyCount < 500) {
                safetyCount++;
                const occStart = next.toJSDate();
                const occEnd = new Date(occStart.getTime() + duration);

                if (occStart.getTime() > maxTime) break;
                if (occEnd.getTime() < minTime) continue;
                if (exceptionDates.has(occStart.toISOString())) continue;

                parsedEvents.push({
                  id: `caldav::${eventObj.url}::${event.uid}::${occStart.toISOString()}`,
                  title: event.summary || 'Untitled Event',
                  startDate: isAllDayRecurring ? formatDateToLocalISO(occStart) : occStart.toISOString(),
                  endDate: isAllDayRecurring ? formatDateToLocalISO(occStart) : occEnd.toISOString(),
                  description: event.description,
                  location: event.location,
                  url: vevent.getFirstPropertyValue('url')?.toString() || undefined,
                  recurringEventId: event.uid,
                  isRecurring: true,
                });
              }
            } catch (expandErr: any) {
              console.error(`[SYNOLOGY CALENDAR] Error expanding recurrence for "${event.summary}":`, expandErr);
            }
          } else {
            const start = event.startDate.toJSDate();
            const end = event.endDate.toJSDate();
            const isAllDay = event.startDate.isDate === true;

            if (end.getTime() < minTime || start.getTime() > maxTime) return;

            parsedEvents.push({
              id: `caldav::${eventObj.url}::${event.uid || 'unknown'}`,
              title: event.summary || 'Untitled Event',
              startDate: isAllDay ? formatDateToLocalISO(start) : start.toISOString(),
              endDate: isAllDay ? formatDateToLocalISO(end) : end.toISOString(),
              description: event.description,
              location: event.location,
              url: vevent.getFirstPropertyValue('url')?.toString() || undefined,
            });
          }
        });
      } catch (parseErr: any) {
        console.error(`[SYNOLOGY CALENDAR] Error parsing event data for calendar ${calendarUrl}:`, parseErr);
      }
    });

    return parsedEvents;
  } catch (error: any) {
    const msg = error?.message || '';
    const status = error?.response?.status || error?.status;
    // Silently handle expected failures (stale/deleted calendars, permission issues)
    if (status === 405 || status === 404 || msg.includes('405') || msg.includes('Not Allowed') || msg.includes('not found')) {
      console.warn(`[SYNOLOGY CALENDAR] Calendar unavailable (${status || msg.slice(0, 60)}): ${calendarUrl}`);
    } else {
      console.error(`[SYNOLOGY CALENDAR] Error fetching events for ${calendarUrl}:`, error);
    }
    return [];
  }
};

/**
 * Create a new event in the specified calendar
 */
export const createEvent = async (
  serverUrl: string,
  username: string,
  password: string,
  calendarUrl: string,
  eventData: {
    title: string;
    description?: string;
    location?: string;
    url?: string;
    recurrence?: string;
    start: { dateTime?: string; date?: string };
    end: { dateTime?: string; date?: string };
  }
): Promise<SynologyCalendarEvent> => {
  try {
    const client = createClient(serverUrl, username, password);
    await loginClient(client, serverUrl, username);

    const calendars = await client.fetchCalendars();
    const targetCalendar = calendars.find(c => c.url === calendarUrl);

    if (!targetCalendar) {
      throw new Error(`Calendar not found: ${calendarUrl}`);
    }

    const now = new Date();
    const uid = crypto.randomUUID();

    const dtStamp = now.toISOString().replace(/[-:.]/g, '').substring(0, 15) + 'Z';

    let dtStart = '';
    let dtEnd = '';
    let dtStartParam = '';
    let dtEndParam = '';

    if (eventData.start.date) {
      dtStart = eventData.start.date.replace(/-/g, '');
      dtEnd = eventData.end.date ? eventData.end.date.replace(/-/g, '') : dtStart; // Fallback
      dtStartParam = ';VALUE=DATE';
      dtEndParam = ';VALUE=DATE';
    } else if (eventData.start.dateTime) {
      dtStart = new Date(eventData.start.dateTime).toISOString().replace(/[-:.]/g, '').substring(0, 15) + 'Z';
      dtEnd = eventData.end.dateTime
        ? new Date(eventData.end.dateTime).toISOString().replace(/[-:.]/g, '').substring(0, 15) + 'Z'
        : dtStart;
    }

    const description = eventData.description ? `DESCRIPTION:${eventData.description.replace(/\n/g, '\\n')}\r\n` : '';
    const location = eventData.location ? `LOCATION:${eventData.location.replace(/,/g, '\\,')}\r\n` : '';
    const url = eventData.url ? `URL:${eventData.url}\r\n` : '';
    let rruleLine = '';
    if (eventData.recurrence) {
      const rruleMap: Record<string, string> = {
        daily: 'RRULE:FREQ=DAILY',
        weekly: 'RRULE:FREQ=WEEKLY',
        biweekly: 'RRULE:FREQ=WEEKLY;INTERVAL=2',
        monthly: 'RRULE:FREQ=MONTHLY',
        yearly: 'RRULE:FREQ=YEARLY',
      };
      if (rruleMap[eventData.recurrence]) {
        rruleLine = `${rruleMap[eventData.recurrence]}\r\n`;
      }
    }

    const iCalString = `BEGIN:VCALENDAR
VERSION:2.0
PRODID:-//My Weekly ToDo List//EN
BEGIN:VEVENT
UID:${uid}
DTSTAMP:${dtStamp}
DTSTART${dtStartParam}:${dtStart}
DTEND${dtEndParam}:${dtEnd}
SUMMARY:${eventData.title}
${description}${location}${url}${rruleLine}END:VEVENT
END:VCALENDAR`;

    console.log('[SYNOLOGY CALENDAR] Creating event with iCal:', iCalString);

    const filename = `${uid}.ics`;

    await client.createCalendarObject({
      calendar: targetCalendar,
      filename,
      iCalString
    });

    return {
      id: `${uid}-${filename}`,
      title: eventData.title,
      startDate: eventData.start.dateTime || eventData.start.date || '',
      endDate: eventData.end.dateTime || eventData.end.date || '',
      description: eventData.description,
      location: eventData.location,
      url: eventData.url
    };
  } catch (error) {
    console.error('[SYNOLOGY CALENDAR] Error creating event:', error);
    throw error;
  }
};

/**
 * Parse the new caldav:: ID format to extract the object URL and UID.
 */
function parseCaldavId(eventId: string): { objectUrl: string; uid: string } | null {
  if (!eventId.startsWith('caldav::')) return null;
  const parts = eventId.split('::');
  if (parts.length >= 3) {
    return { objectUrl: parts[1], uid: parts[2] };
  }
  return null;
}

/**
 * Update an existing event
 */
export const updateEvent = async (
  serverUrl: string,
  username: string,
  password: string,
  calendarUrl: string,
  eventId: string,
  eventData: {
    title?: string;
    description?: string;
    location?: string;
    url?: string;
    start?: { dateTime?: string; date?: string };
    end?: { dateTime?: string; date?: string };
  }
): Promise<SynologyCalendarEvent> => {
  try {
    const client = createClient(serverUrl, username, password);
    await loginClient(client, serverUrl, username);

    let targetObject: any = null;

    const parsed = parseCaldavId(eventId);
    if (parsed) {
      const objects = await client.fetchCalendarObjects({
        calendar: { url: calendarUrl } as any,
        objectUrls: [parsed.objectUrl],
      });
      targetObject = objects?.[0] || null;
      if (!targetObject) {
        throw new Error('Event not found on server at URL: ' + parsed.objectUrl);
      }
    } else {
      const calendars = await client.fetchCalendars();
      const targetCalendar = calendars.find(c => c.url === calendarUrl);

      if (!targetCalendar) {
        throw new Error(`Calendar not found: ${calendarUrl}`);
      }

      const uid = eventId.split('-')[0];

      const allObjects = await client.fetchCalendarObjects({
        calendar: targetCalendar,
      });

      targetObject = allObjects.find(obj => {
        if (obj.data) {
          return obj.data.includes(`UID:${uid}`);
        }
        return obj.url.includes(uid);
      });

      if (!targetObject) {
        throw new Error('Event not found on server');
      }
    }

    const jcal = ICAL.parse(targetObject.data);
    const comp = new ICAL.Component(jcal);
    const vevent = comp.getFirstSubcomponent('vevent');

    if (!vevent) {
      throw new Error('No VEVENT found in calendar object');
    }
    const event = new ICAL.Event(vevent);

    if (eventData.title) event.summary = eventData.title;
    if (eventData.description) event.description = eventData.description;
    if (eventData.location) event.location = eventData.location;

    if (eventData.url !== undefined) {
      if (eventData.url) {
        vevent.updatePropertyWithValue('url', eventData.url);
      } else {
        vevent.removeProperty('url');
      }
    }

    if (eventData.start) {
      if (eventData.start.date) {
        event.startDate = ICAL.Time.fromJSDate(new Date(eventData.start.date), true);
        event.startDate.isDate = true;
      } else if (eventData.start.dateTime) {
        event.startDate = ICAL.Time.fromJSDate(new Date(eventData.start.dateTime), true);
        event.startDate.isDate = false;
      }
    }

    if (eventData.end) {
      if (eventData.end.date) {
        event.endDate = ICAL.Time.fromJSDate(new Date(eventData.end.date), true);
        event.endDate.isDate = true;
      } else if (eventData.end.dateTime) {
        event.endDate = ICAL.Time.fromJSDate(new Date(eventData.end.dateTime), true);
        event.endDate.isDate = false;
      }
    }

    event.sequence = (event.sequence || 0) + 1;
    if (vevent) {
      vevent.updatePropertyWithValue('dtstamp', ICAL.Time.now());
    }

    const updatedIcalString = comp.toString();
    console.log('[SYNOLOGY CALENDAR] Updating event with iCal:', updatedIcalString);

    await client.updateObject({
      url: targetObject.url,
      data: updatedIcalString,
      etag: targetObject.etag
    } as any);

    return {
      id: eventId,
      title: event.summary,
      startDate: event.startDate.toString(),
      endDate: event.endDate.toString(),
      description: event.description,
      location: event.location,
      url: vevent.getFirstPropertyValue('url')?.toString() || undefined
    };

  } catch (error) {
    console.error('[SYNOLOGY CALENDAR] Error updating event:', error);
    throw error;
  }
};

/**
 * Delete an event
 */
export const deleteEvent = async (
  serverUrl: string,
  username: string,
  password: string,
  calendarUrl: string,
  eventId: string
): Promise<void> => {
  try {
    const client = createClient(serverUrl, username, password);
    await loginClient(client, serverUrl, username);

    const parsed = parseCaldavId(eventId);
    if (parsed) {
      await client.deleteObject({
        url: parsed.objectUrl,
        etag: undefined,
      } as any);
      console.log('[SYNOLOGY CALENDAR] Event deleted via direct URL (O(1))');
      return;
    }

    const calendars = await client.fetchCalendars();
    const targetCalendar = calendars.find(c => c.url === calendarUrl);

    if (!targetCalendar) {
      throw new Error(`Calendar not found: ${calendarUrl}`);
    }

    const uid = eventId.split('-')[0];

    const allObjects = await client.fetchCalendarObjects({
      calendar: targetCalendar,
    });

    const targetObject = allObjects.find(obj => {
      if (obj.data) {
        return obj.data.includes(`UID:${uid}`);
      }
      return obj.url.includes(uid);
    });

    if (!targetObject) {
      console.warn('[SYNOLOGY CALENDAR] Event to delete not found, maybe already deleted?');
      return;
    }

    await client.deleteObject({
      url: targetObject.url,
      etag: targetObject.etag
    } as any);

    console.log('[SYNOLOGY CALENDAR] Event deleted via legacy scan');

  } catch (error) {
    console.error('[SYNOLOGY CALENDAR] Error deleting event:', error);
    throw error;
  }
};
