import { GoogleCalendarEvent, getUserCalendars as getGoogleCalendars, getUpcomingEvents as getGoogleEvents, initializeOAuth as initializeGoogleOAuth } from './google-calendar';
import { AppleCalendarEvent, getUserCalendars as getAppleCalendars, getUpcomingEvents as getAppleEvents } from './apple-calendar';
import { getUpcomingEvents as getOutlookEvents, refreshAccessToken as refreshOutlookTokenAPI, createEvent as createOutlookEvent, updateEvent as updateOutlookEvent, deleteEvent as deleteOutlookEvent } from './outlook-calendar';
import { getUserCalendars as getSynologyCalendars, getUpcomingEvents as getSynologyEvents } from './synology-calendar';
import { getUpcomingEvents as getNotionEvents, refreshAccessToken as refreshNotionToken } from './notion-calendar';
import { PrismaClient } from '@prisma/client';

const prisma = new PrismaClient();

export interface EventReminder {
  method: 'popup' | 'email' | 'display'; // display = VALARM DISPLAY, popup = Google popup, email = email reminder
  minutes: number; // minutes before event
}

export interface EventAttendee {
  email: string;
  displayName?: string;
  responseStatus?: 'needsAction' | 'accepted' | 'declined' | 'tentative';
}

export interface EventAttachment {
  url: string;
  title?: string;
}

export type BusyStatus = 'free' | 'tentative' | 'busy' | 'oof' | 'workingElsewhere';
export type EventVisibility = 'default' | 'public' | 'private' | 'confidential';

export interface CalendarEvent {
  id: string;
  title: string;
  description?: string;
  start: {
    dateTime?: string;
    date?: string;
  };
  end: {
    dateTime?: string;
    date?: string;
  };
  location?: string;
  url?: string;
  recurrence?: string;
  recurringEventId?: string;
  isRecurring?: boolean;
  source: 'google' | 'apple' | 'outlook' | 'synology' | 'notion';
  calendarId: string;
  calendarTitle: string;
  backgroundColor?: string;
  allDay?: boolean;
  reminders?: EventReminder[];
  busyStatus?: BusyStatus;
  visibility?: EventVisibility;
  attendees?: EventAttendee[];
  attachments?: EventAttachment[];
}

/**
 * Convert friendly recurrence name to RRULE string
 */
function toRRule(recurrence?: string): string | null {
  switch (recurrence) {
    case 'daily': return 'RRULE:FREQ=DAILY';
    case 'weekly': return 'RRULE:FREQ=WEEKLY';
    case 'biweekly': return 'RRULE:FREQ=WEEKLY;INTERVAL=2';
    case 'monthly': return 'RRULE:FREQ=MONTHLY';
    case 'yearly': return 'RRULE:FREQ=YEARLY';
    default: return null;
  }
}

/**
 * Convert friendly recurrence name to Outlook Graph recurrence object
 */
function toOutlookRecurrence(recurrence?: string, startDate?: Date): any {
  if (!recurrence || recurrence === 'none') return undefined;
  const start = startDate || new Date();
  const range = {
    type: 'noEnd',
    startDate: start.toISOString().split('T')[0],
  };
  switch (recurrence) {
    case 'daily':
      return { pattern: { type: 'daily', interval: 1 }, range };
    case 'weekly':
      return { pattern: { type: 'weekly', interval: 1, daysOfWeek: [['sunday', 'monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'saturday'][start.getDay()]] }, range };
    case 'biweekly':
      return { pattern: { type: 'weekly', interval: 2, daysOfWeek: [['sunday', 'monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'saturday'][start.getDay()]] }, range };
    case 'monthly':
      return { pattern: { type: 'absoluteMonthly', interval: 1, dayOfMonth: start.getDate() }, range };
    case 'yearly':
      return { pattern: { type: 'absoluteYearly', interval: 1, dayOfMonth: start.getDate(), month: start.getMonth() + 1 }, range };
    default: return undefined;
  }
}

// Google Calendar event color mapping (colorId -> hex color)
const GOOGLE_EVENT_COLORS: Record<string, string> = {
  '1': '#7986cb', // Lavender
  '2': '#33b679', // Sage
  '3': '#8e24aa', // Grape
  '4': '#e67c73', // Flamingo
  '5': '#f6bf26', // Banana
  '6': '#f4511e', // Tangerine
  '7': '#039be5', // Peacock
  '8': '#616161', // Graphite
  '9': '#3f51b5', // Blueberry
  '10': '#0b8043', // Basil
  '11': '#d50000', // Tomato
};

function getGoogleEventColor(colorId: string): string {
  return GOOGLE_EVENT_COLORS[colorId] || '#039be5'; // Default to Peacock
}

export interface CalendarConnection {
  id: string;
  provider: 'google' | 'apple' | 'outlook' | 'synology';
  accessToken: string;
  refreshToken?: string;
  expiresAt?: Date;
  calendars?: Array<{
    id: string;
    title: string;
    isPrimary?: boolean;
    selected?: boolean;
    backgroundColor?: string;
  }> | any; // Type it loosely for JSON compatibility
}

/**
 * Refresh the Google access token using the refresh token
 */
async function refreshGoogleToken(connection: CalendarConnection): Promise<string | null> {
  if (!connection.refreshToken) {
    console.log('[CALENDAR] No refresh token available for connection:', connection.id);
    return null;
  }

  try {
    const oauth2Client = initializeGoogleOAuth(
      process.env.GOOGLE_CLIENT_ID || '',
      process.env.GOOGLE_CLIENT_SECRET || '',
      process.env.GOOGLE_REDIRECT_URI || ''
    );

    oauth2Client.setCredentials({
      refresh_token: connection.refreshToken
    });

    console.log('[CALENDAR] Refreshing Google access token...');
    const { credentials } = await oauth2Client.refreshAccessToken();

    if (credentials.access_token) {
      // Update the token in the database
      await prisma.calendarConnection.update({
        where: { id: connection.id },
        data: {
          accessToken: credentials.access_token,
          expiresAt: credentials.expiry_date ? new Date(credentials.expiry_date) : null
        }
      });
      console.log('[CALENDAR] Token refreshed successfully');
      return credentials.access_token;
    }
  } catch (error) {
    console.error('[CALENDAR] Failed to refresh token:', error);
  }
  return null;
}

/**
 * Refresh the Outlook access token using the refresh token
 */
async function refreshOutlookToken(connection: CalendarConnection): Promise<string | null> {
  if (!connection.refreshToken) {
    console.log('[CALENDAR] No refresh token available for connection:', connection.id);
    return null;
  }

  try {
    console.log('[CALENDAR] Refreshing Outlook access token...');
    const data = await refreshOutlookTokenAPI(connection.refreshToken);

    if (data.access_token) {
      // Update the token in the database
      const expiresAt = new Date();
      expiresAt.setSeconds(expiresAt.getSeconds() + data.expires_in);

      await prisma.calendarConnection.update({
        where: { id: connection.id },
        data: {
          accessToken: data.access_token,
          refreshToken: data.refresh_token || connection.refreshToken, // Update refresh token if provided
          expiresAt
        }
      });
      console.log('[CALENDAR] Outlook token refreshed successfully');
      return data.access_token;
    }
  } catch (error) {
    console.error('[CALENDAR] Failed to refresh Outlook token:', error);
  }
  return null;
}

/**
 * Check if the token is expired or about to expire
 */
function isTokenExpired(expiresAt?: Date): boolean {
  if (!expiresAt) return true; // Assume expired if no expiry info

  // Consider token expired if it expires within the next 5 minutes
  const bufferMs = 5 * 60 * 1000;
  return new Date().getTime() > (new Date(expiresAt).getTime() - bufferMs);
}

/**
 * Fetch calendar events from connected accounts
 */
export const getCalendarEvents = async (
  connections: CalendarConnection[],
  timeMin: string,
  timeMax: string
): Promise<CalendarEvent[]> => {
  const allEvents: CalendarEvent[] = [];

  console.log('[CALENDAR] Fetching events from', connections.length, 'connections');
  console.log('[CALENDAR] Date range:', timeMin, 'to', timeMax);

  for (const connection of connections) {
    try {
      let events: any[] = [];
      let accessToken = connection.accessToken;

      if (connection.provider === 'google') {
        console.log('[CALENDAR] Processing Google connection:', connection.id);

        // Check if token is expired and refresh if needed
        if (isTokenExpired(connection.expiresAt)) {
          console.log('[CALENDAR] Token expired, attempting refresh...');
          const newToken = await refreshGoogleToken(connection);
          if (newToken) {
            accessToken = newToken;
          } else {
            console.error('[CALENDAR] Failed to refresh token, skipping connection');
            continue;
          }
        }

        // Initialize OAuth client
        const oauth2Client = initializeGoogleOAuth(
          process.env.GOOGLE_CLIENT_ID || '',
          process.env.GOOGLE_CLIENT_SECRET || '',
          process.env.GOOGLE_REDIRECT_URI || ''
        );

        // Identify which calendars to fetch events from
        let calendarIds: string[] = [];
        let calendars: any[] = [];

        // Use stored calendars if available and filtered by selection
        if (connection.calendars && Array.isArray(connection.calendars)) {
          // We have stored preferences - but check if they have backgroundColor
          const storedCalendars = connection.calendars as any[];
          const hasMissingColors = storedCalendars.some((c: any) => !c.backgroundColor);

          if (hasMissingColors) {
            // Refresh from API to get colors
            console.log('[CALENDAR] Stored calendars missing backgroundColor, refreshing from API...');
            const freshCalendars = await getGoogleCalendars(oauth2Client, accessToken);
            // Merge fresh data with stored selection preferences
            calendars = storedCalendars.map((stored: any) => {
              const fresh = freshCalendars.find(f => f.id === stored.id);
              return {
                ...stored,
                backgroundColor: fresh?.backgroundColor || stored.backgroundColor,
                summary: fresh?.summary || stored.title // Ensure summary is available
              };
            });
            console.log('[CALENDAR] Refreshed calendars with colors:', calendars.map(c => ({ id: c.id, bg: c.backgroundColor })));
          } else {
            calendars = storedCalendars;
          }

          calendarIds = calendars
            .filter((c: any) => c.selected !== false) // Include unless explicitly false
            .map((c: any) => c.id);
          console.log('[CALENDAR] Using calendars, selected:', calendarIds.length, 'of', calendars.length);
        } else {
          // Fallback: fetch all if no stored list (legacy behavior)
          console.log('[CALENDAR] No stored calendars, fetching from API...');
          calendars = await getGoogleCalendars(oauth2Client, accessToken);
          calendarIds = calendars.map(c => c.id);
          console.log('[CALENDAR] Fetched', calendars.length, 'calendars from API');
        }

        if (calendarIds.length === 0) {
          console.log('[CALENDAR] No calendars selected, skipping');
          continue;
        }

        // Fetch events for each calendar
        console.log('[CALENDAR] Fetching events from', calendarIds.length, 'calendars...');
        for (const calendarId of calendarIds) {
          try {
            console.log('[CALENDAR] Fetching events from calendar:', calendarId);
            const calendarEvents = await getGoogleEvents(
              oauth2Client,
              accessToken,
              calendarId,
              timeMin,
              timeMax
            );

            console.log('[CALENDAR] Found', calendarEvents.length, 'events in calendar:', calendarId);

            // Get calendar data for color fallback
            const calendarData = calendars.find(c => c.id === calendarId);

            events = events.concat(calendarEvents.map((event: any) => {
              const eventColor = event.colorId ? getGoogleEventColor(event.colorId) : calendarData?.backgroundColor;
              // Map Google reminders to our format
              const reminders: EventReminder[] | undefined = event.reminders?.overrides?.map((r: any) => ({
                method: r.method === 'email' ? 'email' : 'popup',
                minutes: r.minutes,
              })) || undefined;
              // Map Google transparency to busyStatus
              const busyStatus: BusyStatus | undefined = event.transparency === 'transparent' ? 'free'
                : event.transparency === 'opaque' ? 'busy' : undefined;
              return {
                id: event.id,
                title: event.summary || '(No Title)',
                description: event.description,
                start: event.start,
                end: event.end,
                location: event.location,
                source: 'google' as const,
                calendarId,
                calendarTitle: calendarData?.summary || calendarData?.title || 'Google Calendar',
                backgroundColor: eventColor,
                reminders,
                busyStatus,
                visibility: event.visibility as EventVisibility || undefined,
                attendees: event.attendees?.map((a: any) => ({
                  email: a.email,
                  displayName: a.displayName,
                  responseStatus: a.responseStatus,
                })) as EventAttendee[] || undefined,
              };
            }));
          } catch (calError) {
            console.error(`[CALENDAR] Error fetching events from calendar ${calendarId}:`, calError);
            // Continue with other calendars
          }
        }
      } else if (connection.provider === 'apple') {
        const [email, appPassword] = connection.accessToken.split(':');

        if (!email || !appPassword) {
          console.error('[CALENDAR] Invalid Apple credentials format');
          continue;
        }

        // Get user calendars to identify which ones to fetch events from
        // We can pass null/dummy client if getUserCalendars just calls validateCredentials which creates its own client
        // Actually getUserCalendars in apple-calendar.ts (wrapper around validateCredentials) takes (email, password)
        // But here it was imported as getAppleCalendars(appleClient, accessToken) which matched the old signature?

        // Let's check apple-calendar.ts signature for getUserCalendars.
        // It is: export const getUserCalendars = validateCredentials;
        // validateCredentials: (email: string, appSpecificPassword: string)

        const freshCalendars = await getAppleCalendars(email, appPassword);

        // Use stored selection state if available, otherwise use all fresh calendars
        let calendars = freshCalendars;
        if (connection.calendars && Array.isArray(connection.calendars) && connection.calendars.length > 0) {
          const storedCalendars = connection.calendars as any[];
          // Only fetch from calendars that are selected
          calendars = freshCalendars.filter(fc => {
            const stored = storedCalendars.find((sc: any) => sc.id === fc.id);
            return stored ? stored.selected !== false : true;
          });
        }
        const calendarIds = calendars.map(c => c.id);

        // Fetch events for each calendar
        for (const calendarId of calendarIds) {
          const calendarEvents = await getAppleEvents(
            email,
            appPassword,
            calendarId,
            timeMin,
            timeMax
          );

          console.log(`[CALENDAR] Fetched ${calendarEvents.length} events from calendar ${calendarId}`);

          events = events.concat(calendarEvents.map((event: any) => {
            const isDateOnly = (s: string) => s && !s.includes('T');
            const startIsAllDay = isDateOnly(event.startDate);
            return {
              id: event.id,
              title: event.title,
              description: event.description,
              start: {
                dateTime: startIsAllDay ? undefined : event.startDate,
                date: startIsAllDay ? event.startDate : undefined,
              },
              end: {
                dateTime: startIsAllDay ? undefined : event.endDate,
                date: startIsAllDay ? event.endDate : undefined,
              },
              location: event.location,
              url: event.url,
              recurringEventId: event.recurringEventId,
              isRecurring: event.isRecurring,
              source: 'apple' as const,
              calendarId,
              calendarTitle: calendars.find(c => c.id === calendarId)?.title || 'Apple Calendar',
              backgroundColor: calendars.find(c => c.id === calendarId)?.color || '#FF3B30',
              reminders: event.reminders as EventReminder[] || undefined,
              busyStatus: event.busyStatus as BusyStatus || undefined,
              visibility: event.visibility as EventVisibility || undefined,
              attendees: event.attendees as EventAttendee[] || undefined,
              attachments: event.attachments as EventAttachment[] || undefined,
            };
          }));
        }
      } else if (connection.provider === 'synology') {
        const [username, password] = connection.accessToken.split(':');
        const serverUrl = connection.refreshToken;

        if (!username || !password || !serverUrl) {
          console.error('[CALENDAR] Invalid Synology credentials format');
          continue;
        }

        // Fetch live calendar list from Synology to detect deleted calendars
        let calendarIds: string[] = [];
        let calendars: any[] = [];
        let freshCalendars: any[] = [];

        try {
          freshCalendars = await getSynologyCalendars(serverUrl, username, password);
        } catch (err) {
          console.error('[CALENDAR] Synology: failed to fetch calendar list:', err);
        }

        const freshIds = new Set(freshCalendars.map(c => c.id));

        if (connection.calendars && Array.isArray(connection.calendars) && connection.calendars.length > 0) {
          const storedCalendars = connection.calendars as any[];
          // Remove calendars that no longer exist on the server
          calendars = storedCalendars.filter((c: any) => freshIds.has(c.id));

          if (calendars.length < storedCalendars.length) {
            const removed = storedCalendars.length - calendars.length;
            console.log(`[CALENDAR] Synology: pruned ${removed} deleted calendar(s) from stored list`);
            // Update stored calendars in DB to remove stale entries
            try {
              await prisma.calendarConnection.update({
                where: { id: connection.id },
                data: { calendars: calendars },
              });
            } catch (dbErr) {
              console.error('[CALENDAR] Synology: failed to update stored calendars:', dbErr);
            }
          }

          calendarIds = calendars
            .filter((c: any) => c.selected !== false)
            .map((c: any) => c.id);
          console.log('[CALENDAR] Synology: using stored calendars, selected:', calendarIds.length, 'of', calendars.length);
        } else {
          // No stored calendars: use fresh list
          console.log('[CALENDAR] Synology: no stored calendars, using fresh list');
          calendars = freshCalendars;
          calendarIds = freshCalendars.map(c => c.id);
        }

        if (calendarIds.length === 0) {
          console.log('[CALENDAR] Synology: no calendars selected, skipping');
          continue;
        }

        for (const calendarId of calendarIds) {
          try {
            const calendarEvents = await getSynologyEvents(
              serverUrl,
              username,
              password,
              calendarId,
              timeMin,
              timeMax
            );

            console.log(`[CALENDAR] Synology: fetched ${calendarEvents.length} events from calendar ${calendarId}`);
            const calendarData = calendars.find(c => c.id === calendarId);

            events = events.concat(calendarEvents.map((event: any) => {
              const isDateOnly = (s: string) => s && !s.includes('T');
              const startIsAllDay = isDateOnly(event.startDate);
              return {
                id: event.id,
                title: event.title,
                description: event.description,
                start: {
                  dateTime: startIsAllDay ? undefined : event.startDate,
                  date: startIsAllDay ? event.startDate : undefined,
                },
                end: {
                  dateTime: startIsAllDay ? undefined : event.endDate,
                  date: startIsAllDay ? event.endDate : undefined,
                },
                location: event.location,
                url: event.url,
                recurringEventId: event.recurringEventId,
                isRecurring: event.isRecurring,
                source: 'synology' as const,
                calendarId,
                calendarTitle: calendarData?.title || 'Synology Calendar',
                backgroundColor: calendarData?.color || '#1b85ff',
                reminders: event.reminders as EventReminder[] || undefined,
                busyStatus: event.busyStatus as BusyStatus || undefined,
                visibility: event.visibility as EventVisibility || undefined,
                attendees: event.attendees as EventAttendee[] || undefined,
                attachments: event.attachments as EventAttachment[] || undefined,
              };
            }));
          } catch (calError: any) {
            const msg = calError?.message || '';
            if (msg.includes('not found') || msg.includes('Not found') || calError?.status === 404) {
              console.warn(`[CALENDAR] Synology: calendar ${calendarId} not found, removing from stored list`);
              calendars = calendars.filter((c: any) => c.id !== calendarId);
              try {
                await prisma.calendarConnection.update({
                  where: { id: connection.id },
                  data: { calendars: calendars },
                });
              } catch (dbErr) {
                console.error('[CALENDAR] Synology: failed to prune calendar from DB:', dbErr);
              }
            } else {
              console.error(`[CALENDAR] Synology: error fetching events from calendar ${calendarId}:`, calError);
            }
          }
        }
      } else if (connection.provider === 'outlook') {
        console.log('[CALENDAR] Processing Outlook connection:', connection.id);

        if (isTokenExpired(connection.expiresAt)) {
          console.log('[CALENDAR] Outlook token expired, attempting refresh...');
          const newToken = await refreshOutlookToken(connection);
          if (newToken) {
            accessToken = newToken;
          } else {
            console.error('[CALENDAR] Failed to refresh Outlook token, skipping connection');
            continue;
          }
        }

        // Get calendars to fetch
        let calendarIds: string[] = [];
        let calendars: any[] = [];

        if (connection.calendars && Array.isArray(connection.calendars)) {
          calendars = connection.calendars as any[];
          calendarIds = calendars
            .filter((c: any) => c.selected !== false)
            .map((c: any) => c.id);
        }

        if (calendarIds.length > 0) {
          console.log('[CALENDAR] Fetching Outlook events from', calendarIds.length, 'calendars');
          for (const calendarId of calendarIds) {
            try {
              const outlookEvents = await getOutlookEvents(
                accessToken,
                calendarId,
                timeMin,
                timeMax
              );

              const calendarData = calendars.find(c => c.id === calendarId);

              events = events.concat(outlookEvents.map((event: any) => ({
                id: event.id,
                title: event.summary || '(No Title)',
                description: event.description,
                start: event.start,
                end: event.end,
                location: event.location || '',
                source: 'outlook' as const,
                calendarId,
                calendarTitle: calendarData?.title || 'Outlook Calendar',
                backgroundColor: '#0078d4',
                reminders: event.reminders as EventReminder[] || undefined,
                busyStatus: event.busyStatus as BusyStatus || undefined,
                visibility: event.visibility as EventVisibility || undefined,
                attendees: event.attendees as EventAttendee[] || undefined,
              })));

            } catch (calError) {
              console.error(`[CALENDAR] Error fetching Outlook events from ${calendarId}:`, calError);
            }
          }
        }
      } else if (connection.provider === 'notion') {
        console.log('[CALENDAR] Processing Notion connection:', connection.id);

        // Refresh token if available
        if (connection.refreshToken) {
          try {
            const refreshed = await refreshNotionToken(
              connection.refreshToken,
              process.env.NOTION_CLIENT_ID || '',
              process.env.NOTION_CLIENT_SECRET || '',
            );
            accessToken = refreshed.accessToken;
            await prisma.calendarConnection.update({
              where: { id: connection.id },
              data: {
                accessToken: refreshed.accessToken,
                refreshToken: refreshed.refreshToken,
                updatedAt: new Date(),
              },
            });
          } catch (refreshErr) {
            console.error('[CALENDAR] Notion token refresh failed, using existing token:', refreshErr);
          }
        }

        // Get selected databases
        const databases = (connection.calendars as any[] || []).filter((c: any) => c.selected !== false);
        if (databases.length === 0) {
          console.log('[CALENDAR] Notion: no databases selected, skipping');
        }

        for (const db of databases) {
          if (!db.dateProperty) {
            console.log(`[CALENDAR] Notion: database "${db.title}" has no date property, skipping`);
            continue;
          }

          try {
            const notionEvents = await getNotionEvents(
              accessToken,
              db.id,
              db.dateProperty,
              timeMin,
              timeMax,
              db.title,
            );

            console.log(`[CALENDAR] Notion: fetched ${notionEvents.length} events from "${db.title}"`);

            events = events.concat(notionEvents.map((ne) => ({
              id: ne.id,
              title: ne.title,
              description: ne.description,
              start: {
                dateTime: ne.allDay ? undefined : ne.start,
                date: ne.allDay ? ne.start : undefined,
              },
              end: {
                dateTime: ne.allDay ? undefined : (ne.end || ne.start),
                date: ne.allDay ? (ne.end || ne.start) : undefined,
              },
              location: undefined,
              url: ne.url,
              source: 'notion' as const,
              calendarId: db.id,
              calendarTitle: db.title,
              backgroundColor: '#000000',
            })));
          } catch (dbError) {
            console.error(`[CALENDAR] Notion: error fetching from "${db.title}":`, dbError);
          }
        }
      }

      console.log('[CALENDAR] Total events for connection:', events.length);
      allEvents.push(...events);
    } catch (error) {
      console.error(`[CALENDAR] Error fetching events from ${connection.provider} calendar:`, error);
      // Continue with other connections even if one fails
    }
  }

  console.log('[CALENDAR] Total events from all connections:', allEvents.length);
  return allEvents;
};

/**
 * Format event for display in the weekly view
 */
export const formatEventForDisplay = (event: CalendarEvent): CalendarEvent => {
  // Ensure consistent structure for display
  return {
    id: event.id,
    title: event.title,
    description: event.description,
    start: {
      dateTime: event.start.dateTime,
      date: event.start.date,
    },
    end: {
      dateTime: event.end.dateTime,
      date: event.end.date,
    },
    location: event.location,
    source: event.source,
    calendarId: event.calendarId,
    calendarTitle: event.calendarTitle,
  };
};

/**
 * Merge calendar events with existing tasks to avoid duplicates
 */
export const mergeWithTasks = (
  events: CalendarEvent[],
  tasks: Array<{ id: string; title: string }>
): CalendarEvent[] => {
  // Simple deduplication logic - if event title matches task title, consider it a duplicate
  const taskTitles = new Set(tasks.map(task => task.title.toLowerCase()));

  return events.filter(event => {
    // Only show events where the title doesn't match an existing task title
    return !taskTitles.has(event.title.toLowerCase());
  });
};

/**
 * Filter events by date range (week view)
 */
export const filterEventsByDateRange = (
  events: CalendarEvent[],
  startOfWeek: Date,
  endOfWeek: Date
): CalendarEvent[] => {
  const startTimestamp = startOfWeek.getTime();
  const endTimestamp = endOfWeek.getTime();

  return events.filter(event => {
    const eventStart = event.start.dateTime ? new Date(event.start.dateTime).getTime() :
      event.start.date ? new Date(event.start.date).getTime() : 0;

    return eventStart >= startTimestamp && eventStart <= endTimestamp;
  });
};

/**
 * Determine event visibility based on user settings
 */
export const determineEventVisibility = (
  event: CalendarEvent,
  userSettings: {
    showGoogleEvents?: boolean;
    showAppleEvents?: boolean;
    showPrivateEvents?: boolean;
  }
): boolean => {
  // Check if the user wants to see events from this provider
  if ((event.source === 'google' && !userSettings.showGoogleEvents) ||
    (event.source === 'apple' && !userSettings.showAppleEvents)) {
    return false;
  }

  // Check if private events should be shown (this is a simplified check)
  // In a real implementation, this would involve checking privacy settings
  if (!userSettings.showPrivateEvents) {
    // Simplified - in practice, private events would be filtered based on actual privacy flags
    return true;
  }

  return true;
};

/**
 * Create a new calendar event
 */
export const createCalendarEvent = async (
  connection: CalendarConnection,
  calendarId: string,
  event: Partial<CalendarEvent>
): Promise<CalendarEvent> => {
  if (connection.provider === 'google') {
    // Check key fields
    if (!event.title) throw new Error('Event title is required');
    if (!event.start || !event.end) throw new Error('Event start and end times are required');

    // Refresh token if needed
    let accessToken = connection.accessToken;
    if (isTokenExpired(connection.expiresAt)) {
      const newToken = await refreshGoogleToken(connection);
      if (newToken) accessToken = newToken;
      else throw new Error('Failed to refresh token');
    }

    const oauth2Client = initializeGoogleOAuth(
      process.env.GOOGLE_CLIENT_ID || '',
      process.env.GOOGLE_CLIENT_SECRET || '',
      process.env.GOOGLE_REDIRECT_URI || ''
    );

    // Map to Google format
    const rrule = toRRule(event.recurrence);
    const googleEvent: any = {
      summary: event.title,
      description: event.description,
      start: event.start,
      end: event.end,
      location: event.location,
      ...(rrule ? { recurrence: [rrule] } : {}),
      ...(event.allDay ? { allDay: true } : {}),
      ...(event.url ? { source: { url: event.url, title: event.url } } : {}),
      ...(event.reminders?.length ? {
        reminders: { useDefault: false, overrides: event.reminders.map(r => ({ method: r.method === 'email' ? 'email' : 'popup', minutes: r.minutes })) }
      } : {}),
      ...(event.busyStatus ? { transparency: event.busyStatus === 'free' ? 'transparent' : 'opaque' } : {}),
      ...(event.visibility ? { visibility: event.visibility } : {}),
      ...(event.attendees?.length ? { attendees: event.attendees.map(a => ({ email: a.email, displayName: a.displayName })) } : {}),
    };

    const createdEvent = await import('./google-calendar').then(m =>
      m.createEvent(oauth2Client, accessToken, calendarId, googleEvent)
    );

    return {
      id: createdEvent.id,
      title: createdEvent.summary,
      description: createdEvent.description,
      start: createdEvent.start,
      end: createdEvent.end,
      location: createdEvent.location,
      source: 'google',
      calendarId,
      calendarTitle: '', // We don't have this here, simpler to leave empty or fetch
    } as CalendarEvent;
  } else if (connection.provider === 'outlook') {
    if (!event.title) throw new Error('Event title is required');
    if (!event.start || !event.end) throw new Error('Event start and end times are required');

    let accessToken = connection.accessToken;
    if (isTokenExpired(connection.expiresAt)) {
      const newToken = await refreshOutlookToken(connection);
      if (newToken) accessToken = newToken;
      else throw new Error('Failed to refresh token');
    }

    const startDate = event.start?.dateTime ? new Date(event.start.dateTime) : new Date();
    const createdEvent = await createOutlookEvent(accessToken, calendarId, {
      summary: event.title,
      description: event.description,
      start: event.start,
      end: event.end,
      location: event.location,
      allDay: event.allDay,
      recurrence: toOutlookRecurrence(event.recurrence, startDate),
      reminders: event.reminders,
      busyStatus: event.busyStatus,
      visibility: event.visibility,
      attendees: event.attendees,
    });

    return {
      id: createdEvent.id,
      title: createdEvent.summary,
      description: createdEvent.description,
      start: createdEvent.start,
      end: createdEvent.end,
      location: createdEvent.location,
      allDay: createdEvent.allDay,
      source: 'outlook',
      calendarId,
      calendarTitle: '',
    } as CalendarEvent;
  } else if (connection.provider === 'apple') {
    const [email, appPassword] = connection.accessToken.split(':');

    // We need to map our event format to what createEvent expects
    // createEvent expects: { title, description?, location?, start: {dateTime?, date?}, end: ... }
    // Our 'event' arg is Partial<CalendarEvent>, which matches well.
    // However, event.start and event.end might be undefined in Partial, so we need checks.

    // Handle all-day event normalization for Apple (requires 'date' property, not 'dateTime')
    // AND exclusive end date (add 1 day if needed)
    const start = { ...event.start! };
    const end = { ...event.end! };

    if (event.allDay) {
      if (start.dateTime && !start.date) {
        start.date = start.dateTime.split('T')[0];
        delete start.dateTime;
      }
      if (end.dateTime && !end.date) {
        end.date = end.dateTime.split('T')[0];
        delete end.dateTime;
      }

      // Apple/iCloud requires DTEND to be the day AFTER the last day of the event
      if (start.date && end.date && start.date === end.date) {
        const d = new Date(end.date);
        d.setDate(d.getDate() + 1);
        end.date = d.toISOString().split('T')[0];
      }
    }

    const createdEvent = await import('./apple-calendar').then(m =>
      m.createEvent(email, appPassword, calendarId, {
        title: event.title!,
        description: event.description,
        location: event.location,
        url: event.url,
        recurrence: event.recurrence,
        start,
        end,
        reminders: event.reminders,
        attendees: event.attendees,
        attachments: event.attachments,
        busyStatus: event.busyStatus,
        visibility: event.visibility,
      })
    );

    return {
      id: createdEvent.id,
      title: createdEvent.title,
      description: createdEvent.description,
      start: { dateTime: createdEvent.startDate }, 
      end: { dateTime: createdEvent.endDate },
      location: createdEvent.location,
      source: 'apple',
      calendarId,
      calendarTitle: '', // Fetch if needed
    } as CalendarEvent;
  } else if (connection.provider === 'synology') {
    const [username, password] = connection.accessToken.split(':');
    const serverUrl = connection.refreshToken;

    if (!username || !password || !serverUrl) throw new Error('Invalid Synology credentials');
    if (!event.title) throw new Error('Event title is required');
    if (!event.start || !event.end) throw new Error('Event start and end times are required');

    const createdEvent = await import('./synology-calendar').then(m =>
      m.createEvent(serverUrl, username, password, calendarId, {
        title: event.title!,
        description: event.description,
        location: event.location,
        url: event.url,
        recurrence: event.recurrence,
        start: event.start!,
        end: event.end!,
        reminders: event.reminders,
        attendees: event.attendees,
        attachments: event.attachments,
        busyStatus: event.busyStatus,
        visibility: event.visibility,
      })
    );

    return {
      id: createdEvent.id,
      title: createdEvent.title,
      description: createdEvent.description,
      start: { dateTime: createdEvent.startDate },
      end: { dateTime: createdEvent.endDate },
      location: createdEvent.location,
      source: 'synology',
      calendarId,
      calendarTitle: '',
    } as CalendarEvent;
  } else if (connection.provider === 'notion') {
    if (!event.title) throw new Error('Event title is required');
    if (!event.start) throw new Error('Event start time is required');

    const databases = (connection.calendars as any[] || []);
    const db = databases.find((d: any) => d.id === calendarId);
    const dateProperty = db?.dateProperty;
    if (!dateProperty) throw new Error('No date property found for this Notion database');

    const startDate = event.start.dateTime || event.start.date || '';
    const endDate = event.end?.dateTime || event.end?.date || undefined;

    const { createEvent: createNotionEvent } = await import('./notion-calendar');
    const pageId = await createNotionEvent(
      connection.accessToken,
      calendarId,
      dateProperty,
      event.title,
      startDate,
      endDate,
    );

    return {
      id: pageId,
      title: event.title,
      start: event.start,
      end: event.end || event.start,
      source: 'notion',
      calendarId,
      calendarTitle: db?.title || '',
    } as CalendarEvent;
  }

  throw new Error(`Provider ${connection.provider} does not support creating events yet.`);
};

/**
 * Update an existing calendar event
 */
export const updateCalendarEvent = async (
  connection: CalendarConnection,
  calendarId: string,
  eventId: string,
  event: Partial<CalendarEvent>
): Promise<CalendarEvent> => {
  if (connection.provider === 'google') {
    // Refresh token if needed
    let accessToken = connection.accessToken;
    if (isTokenExpired(connection.expiresAt)) {
      const newToken = await refreshGoogleToken(connection);
      if (newToken) accessToken = newToken;
      else throw new Error('Failed to refresh token');
    }

    const oauth2Client = initializeGoogleOAuth(
      process.env.GOOGLE_CLIENT_ID || '',
      process.env.GOOGLE_CLIENT_SECRET || '',
      process.env.GOOGLE_REDIRECT_URI || ''
    );

    // Map to Google format
    const rrule = toRRule(event.recurrence);
    const googleEvent: any = {};
    if (event.title !== undefined) googleEvent.summary = event.title;
    if (event.description !== undefined) googleEvent.description = event.description;
    if (event.start !== undefined) googleEvent.start = event.start;
    if (event.end !== undefined) googleEvent.end = event.end;
    if (event.location !== undefined) googleEvent.location = event.location;
    if (rrule) googleEvent.recurrence = [rrule];
    if (event.url) googleEvent.source = { url: event.url, title: event.url };
    if (event.reminders?.length) {
      googleEvent.reminders = { useDefault: false, overrides: event.reminders.map(r => ({ method: r.method === 'email' ? 'email' : 'popup', minutes: r.minutes })) };
    }
    if (event.busyStatus) googleEvent.transparency = event.busyStatus === 'free' ? 'transparent' : 'opaque';
    if (event.visibility) googleEvent.visibility = event.visibility;
    if (event.attendees) googleEvent.attendees = event.attendees.map(a => ({ email: a.email, displayName: a.displayName }));

    // Google adds _date suffix for instances. Editing base series only.
    const baseEventId = eventId.split('_')[0];

    const updatedEvent = await import('./google-calendar').then(m =>
      m.updateEvent(oauth2Client, accessToken, calendarId, baseEventId, googleEvent)
    );

    return {
      id: updatedEvent.id,
      title: updatedEvent.summary,
      description: updatedEvent.description,
      start: updatedEvent.start,
      end: updatedEvent.end,
      location: updatedEvent.location,
      source: 'google',
      calendarId,
      calendarTitle: '',
    } as CalendarEvent;
  } else if (connection.provider === 'outlook') {
    let accessToken = connection.accessToken;
    if (isTokenExpired(connection.expiresAt)) {
      const newToken = await refreshOutlookToken(connection);
      if (newToken) accessToken = newToken;
      else throw new Error('Failed to refresh token');
    }

    // Extract base series ID for Outlook
    const baseEventId = eventId.includes('::') ? eventId.split('::')[0] : eventId;

    const startDate = event.start?.dateTime ? new Date(event.start.dateTime) : new Date();
    const updatedEvent = await updateOutlookEvent(accessToken, calendarId, baseEventId, {
      summary: event.title,
      description: event.description,
      start: event.start,
      end: event.end,
      location: event.location,
      allDay: event.allDay,
      recurrence: toOutlookRecurrence(event.recurrence, startDate),
      reminders: event.reminders,
      busyStatus: event.busyStatus,
      visibility: event.visibility,
      attendees: event.attendees,
    });

    return {
      id: updatedEvent.id,
      title: updatedEvent.summary,
      description: updatedEvent.description,
      start: updatedEvent.start,
      end: updatedEvent.end,
      location: updatedEvent.location,
      allDay: updatedEvent.allDay,
      source: 'outlook',
      calendarId,
      calendarTitle: '',
    } as CalendarEvent;
  } else if (connection.provider === 'apple') {
    const [email, appPassword] = connection.accessToken.split(':');

    // Normalize for Apple updates too
    if (event.allDay) {
      if (event.start && event.start.dateTime && !event.start.date) {
        event.start.date = event.start.dateTime.split('T')[0];
        delete event.start.dateTime;
      }
      if (event.end && event.end.dateTime && !event.end.date) {
        event.end.date = event.end.dateTime.split('T')[0];
        delete event.end.dateTime;
      }

      if (event.start && event.end && event.start.date && event.end.date && event.start.date === event.end.date) {
        const d = new Date(event.end.date);
        d.setDate(d.getDate() + 1);
        event.end.date = d.toISOString().split('T')[0];
      }
    }

    const updatedEvent = await import('./apple-calendar').then(m =>
      m.updateEvent(email, appPassword, calendarId, eventId, {
        title: event.title,
        description: event.description,
        location: event.location,
        url: event.url,
        start: event.start,
        end: event.end,
        reminders: event.reminders,
        attendees: event.attendees,
        attachments: event.attachments,
        busyStatus: event.busyStatus,
        visibility: event.visibility,
      })
    );

    return {
      id: updatedEvent.id,
      title: updatedEvent.title,
      description: updatedEvent.description,
      start: { dateTime: updatedEvent.startDate },
      end: { dateTime: updatedEvent.endDate },
      location: updatedEvent.location,
      url: updatedEvent.url,
      source: 'apple',
      calendarId,
      calendarTitle: '',
    } as CalendarEvent;
  } else if (connection.provider === 'synology') {
    const [username, password] = connection.accessToken.split(':');
    const serverUrl = connection.refreshToken;

    if (!username || !password || !serverUrl) throw new Error('Invalid Synology credentials');

    const updatedEvent = await import('./synology-calendar').then(m =>
      m.updateEvent(serverUrl, username, password, calendarId, eventId, {
        title: event.title,
        description: event.description,
        location: event.location,
        url: event.url,
        start: event.start,
        end: event.end,
        reminders: event.reminders,
        attendees: event.attendees,
        attachments: event.attachments,
        busyStatus: event.busyStatus,
        visibility: event.visibility,
      })
    );

    return {
      id: updatedEvent.id,
      title: updatedEvent.title,
      description: updatedEvent.description,
      start: { dateTime: updatedEvent.startDate },
      end: { dateTime: updatedEvent.endDate },
      location: updatedEvent.location,
      url: updatedEvent.url,
      source: 'synology',
      calendarId,
      calendarTitle: '',
    } as CalendarEvent;
  } else if (connection.provider === 'notion') {
    const databases = (connection.calendars as any[] || []);
    const db = databases.find((d: any) => d.id === calendarId);
    const dateProperty = db?.dateProperty;
    if (!dateProperty) throw new Error('No date property found for this Notion database');

    const startDate = event.start?.dateTime || event.start?.date || undefined;
    const endDate = event.end?.dateTime || event.end?.date || undefined;

    const { updateEvent: updateNotionEvent } = await import('./notion-calendar');
    await updateNotionEvent(
      connection.accessToken,
      eventId,
      dateProperty,
      event.title,
      startDate,
      endDate,
    );

    return {
      id: eventId,
      title: event.title || '',
      start: event.start || { dateTime: '' },
      end: event.end || event.start || { dateTime: '' },
      source: 'notion',
      calendarId,
      calendarTitle: db?.title || '',
    } as CalendarEvent;
  }

  throw new Error(`Provider ${connection.provider} does not support updating events yet.`);
};

/**
 * Delete a calendar event
 */
export const deleteCalendarEvent = async (
  connection: CalendarConnection,
  calendarId: string,
  eventId: string
): Promise<void> => {
  if (connection.provider === 'google') {
    // Refresh token if needed
    let accessToken = connection.accessToken;
    if (isTokenExpired(connection.expiresAt)) {
      const newToken = await refreshGoogleToken(connection);
      if (newToken) accessToken = newToken;
      else throw new Error('Failed to refresh token');
    }

    const oauth2Client = initializeGoogleOAuth(
      process.env.GOOGLE_CLIENT_ID || '',
      process.env.GOOGLE_CLIENT_SECRET || '',
      process.env.GOOGLE_REDIRECT_URI || ''
    );

    // Default to deleting the whole series if repeating
    const baseEventId = eventId.split('_')[0];

    await import('./google-calendar').then(m =>
      m.deleteEvent(oauth2Client, accessToken, calendarId, baseEventId)
    );
    return;
  } else if (connection.provider === 'outlook') {
    let accessToken = connection.accessToken;
    if (isTokenExpired(connection.expiresAt)) {
      const newToken = await refreshOutlookToken(connection);
      if (newToken) accessToken = newToken;
      else throw new Error('Failed to refresh token');
    }

    // Extract base series ID for Outlook
    const baseEventId = eventId.includes('::') ? eventId.split('::')[0] : eventId;

    await deleteOutlookEvent(accessToken, calendarId, baseEventId);
    return;
  } else if (connection.provider === 'apple') {
    const [email, appPassword] = connection.accessToken.split(':');

    await import('./apple-calendar').then(m =>
      m.deleteEvent(email, appPassword, calendarId, eventId)
    );
    return;
  } else if (connection.provider === 'synology') {
    const [username, password] = connection.accessToken.split(':');
    const serverUrl = connection.refreshToken;

    if (!username || !password || !serverUrl) throw new Error('Invalid Synology credentials');

    await import('./synology-calendar').then(m =>
      m.deleteEvent(serverUrl, username, password, calendarId, eventId)
    );
    return;
  } else if (connection.provider === 'notion') {
    const { deleteEvent: deleteNotionEvent } = await import('./notion-calendar');
    await deleteNotionEvent(connection.accessToken, eventId);
    return;
  }

  throw new Error(`Provider ${connection.provider} does not support deleting events yet.`);
};