/**
 * Outlook Calendar Service
 *
 * Handles Microsoft Graph API calendar operations using user OAuth tokens.
 * Manages token refresh, calendar CRUD operations, and Mawidi booking sync.
 *
 * @module lib/services/outlook-calendar.service
 */

import prisma from '@/lib/db';
import { integrationsAdapter } from '@/lib/adapters/integrations';
import { logger } from '@/lib/logger';
import { decrypt, encrypt } from '@/lib/encryption';

// Microsoft Graph API Base URL
const GRAPH_API = 'https://graph.microsoft.com/v1.0';
const TOKEN_URL = 'https://login.microsoftonline.com/common/oauth2/v2.0/token';

// Token expiry buffer (5 minutes)
const TOKEN_BUFFER_MS = 5 * 60 * 1000;

/**
 * Outlook Calendar Interface
 */
export interface OutlookCalendar {
  id: string;
  name: string;
  color?: string;
  isDefaultCalendar?: boolean;
  canEdit: boolean;
  canShare?: boolean;
  canViewPrivateItems?: boolean;
  owner?: {
    name?: string;
    address?: string;
  };
}

/**
 * Outlook Event Interface
 */
export interface OutlookEvent {
  id: string;
  subject: string;
  body?: {
    contentType: 'HTML' | 'Text';
    content: string;
  };
  start: {
    dateTime: string;
    timeZone: string;
  };
  end: {
    dateTime: string;
    timeZone: string;
  };
  location?: {
    displayName?: string;
    locationType?: string;
    address?: unknown;
  };
  attendees?: Array<{
    type: 'required' | 'optional' | 'resource';
    status: {
      response: string;
      time: string;
    };
    emailAddress: {
      name?: string;
      address: string;
    };
  }>;
  organizer?: {
    emailAddress: {
      name?: string;
      address: string;
    };
  };
  webLink?: string;
  onlineMeeting?: {
    joinUrl: string;
    conferenceId?: string;
  };
  isOnlineMeeting?: boolean;
  onlineMeetingProvider?: 'teamsForBusiness' | 'skypeForBusiness' | 'skypeForConsumer';
  showAs?: 'free' | 'tentative' | 'busy' | 'oof' | 'workingElsewhere' | 'unknown';
  sensitivity?: 'normal' | 'personal' | 'private' | 'confidential';
  isCancelled?: boolean;
  createdDateTime?: string;
  lastModifiedDateTime?: string;
}

/**
 * Input for creating/updating Outlook events
 */
export interface CreateOutlookEventInput {
  subject: string;
  body?: {
    contentType: 'HTML' | 'Text';
    content: string;
  };
  start: {
    dateTime: string;
    timeZone: string;
  };
  end: {
    dateTime: string;
    timeZone: string;
  };
  location?: {
    displayName?: string;
  };
  attendees?: Array<{
    emailAddress: {
      address: string;
      name?: string;
    };
    type?: 'required' | 'optional' | 'resource';
  }>;
  isOnlineMeeting?: boolean;
  onlineMeetingProvider?: 'teamsForBusiness';
  showAs?: 'free' | 'tentative' | 'busy' | 'oof' | 'workingElsewhere';
  sensitivity?: 'normal' | 'personal' | 'private' | 'confidential';
  isReminderOn?: boolean;
  reminderMinutesBeforeStart?: number;
}

/**
 * Sync Result Interface
 */
export interface SyncResult {
  success: boolean;
  created: number;
  updated: number;
  deleted: number;
  errors: string[];
  skipped: number;
}

/**
 * Token refresh response
 */
interface TokenRefreshResponse {
  access_token: string;
  refresh_token?: string;
  expires_in: number;
  token_type: string;
  scope?: string;
}

/**
 * Get valid Outlook access token for a user
 * Automatically refreshes if expired
 *
 * @param userId - User ID
 * @returns Valid access token or null if unavailable
 */
export async function getValidOutlookToken(userId: string): Promise<string | null> {
  try {
    // Get integration from database
    const integration = await integrationsAdapter.findUnique({
      where: {
        userId_type: {
          userId,
          type: 'outlook_calendar',
        },
      },
    });

    if (!integration) {
      logger.warn('No Outlook calendar integration found', { userId });
      return null;
    }

    if (!integration.accessToken) {
      logger.warn('No access token stored for Outlook integration', { userId });
      return null;
    }

    // Decrypt the access token
    const accessToken = decrypt(integration.accessToken);

    // Check if token is still valid (with buffer)
    if (integration.tokenExpiry) {
      const expiryTime = new Date(integration.tokenExpiry).getTime();
      const now = Date.now();

      // If token expires in more than 5 minutes, return it
      if (expiryTime > now + TOKEN_BUFFER_MS) {
        return accessToken;
      }
    }

    // Token expired or about to expire, refresh it
    logger.info('Outlook token expired or expiring soon, refreshing', { userId });
    const refreshed = await refreshOutlookToken(userId);

    if (!refreshed) {
      logger.error('Failed to refresh Outlook token', { userId });
      return null;
    }

    // Get the refreshed token
    const updatedIntegration = await integrationsAdapter.findUnique({
      where: {
        userId_type: {
          userId,
          type: 'outlook_calendar',
        },
      },
    });

    if (!updatedIntegration?.accessToken) {
      return null;
    }

    return decrypt(updatedIntegration.accessToken);
  } catch (error) {
    logger.error('Error getting valid Outlook token', error instanceof Error ? error : new Error(String(error)), { userId });
    return null;
  }
}

/**
 * Refresh Outlook OAuth access token
 *
 * @param userId - User ID
 * @returns True if refresh successful, false otherwise
 */
export async function refreshOutlookToken(userId: string): Promise<boolean> {
  try {
    // Get the stored integration
    const integration = await integrationsAdapter.findUnique({
      where: {
        userId_type: {
          userId,
          type: 'outlook_calendar',
        },
      },
    });

    if (!integration || !integration.refreshToken) {
      logger.warn('No Outlook integration or refresh token found', { userId });
      return false;
    }

    // Decrypt refresh token
    const refreshToken = decrypt(integration.refreshToken);

    // Request new access token
    const response = await fetch(TOKEN_URL, {
      method: 'POST',
      headers: {
        'Content-Type': 'application/x-www-form-urlencoded',
      },
      body: new URLSearchParams({
        client_id: process.env.MICROSOFT_CLIENT_ID || '',
        client_secret: process.env.MICROSOFT_CLIENT_SECRET || '',
        refresh_token: refreshToken,
        grant_type: 'refresh_token',
      }),
    });

    if (!response.ok) {
      const errorText = await response.text();
      logger.error('Failed to refresh Outlook token', { userId, error: errorText, status: response.status });

      // Update integration status to error
      await integrationsAdapter.update({
        where: { id: integration.id },
        data: {
          status: 'error',
          lastError: `Token refresh failed: ${errorText}`,
          updatedAt: new Date(),
        },
      });

      return false;
    }

    const tokenData: TokenRefreshResponse = await response.json();
    const expiresAt = new Date(Date.now() + (tokenData.expires_in || 3600) * 1000);

    // Encrypt tokens before storing
    const encryptedAccessToken = encrypt(tokenData.access_token);
    const updateData: {
      accessToken: string;
      refreshToken?: string;
      tokenExpiry: Date;
      status: string;
      lastError: null;
      updatedAt: Date;
      scope?: string;
    } = {
      accessToken: encryptedAccessToken,
      tokenExpiry: expiresAt,
      status: 'connected',
      lastError: null,
      updatedAt: new Date(),
    };

    // Update refresh token if provided (not always included)
    if (tokenData.refresh_token) {
      updateData.refreshToken = encrypt(tokenData.refresh_token);
    }

    // Update scope if provided
    if (tokenData.scope) {
      updateData.scope = tokenData.scope;
    }

    // Update the stored tokens
    await integrationsAdapter.update({
      where: { id: integration.id },
      data: updateData,
    });

    logger.info('Outlook token refreshed successfully', { userId });
    return true;
  } catch (error) {
    logger.error('Error refreshing Outlook token', error instanceof Error ? error : new Error(String(error)), { userId });
    return false;
  }
}

/**
 * List all calendars for a user
 *
 * @param userId - User ID
 * @returns Array of calendars or null on error
 */
export async function listUserCalendars(userId: string): Promise<OutlookCalendar[] | null> {
  try {
    const accessToken = await getValidOutlookToken(userId);

    if (!accessToken) {
      logger.warn('No valid Outlook token for listing calendars', { userId });
      return null;
    }

    const response = await fetch(`${GRAPH_API}/me/calendars`, {
      method: 'GET',
      headers: {
        Authorization: `Bearer ${accessToken}`,
        'Content-Type': 'application/json',
      },
    });

    if (!response.ok) {
      const error = await response.text();
      logger.error('Failed to list Outlook calendars', { userId, error, status: response.status });

      // If unauthorized, mark integration as needing reconnection
      if (response.status === 401) {
        await integrationsAdapter.update({
          where: {
            userId_type: {
              userId,
              type: 'outlook_calendar',
            },
          },
          data: {
            status: 'error',
            lastError: 'Authentication expired. Please reconnect your Outlook account.',
            updatedAt: new Date(),
          },
        });
      }

      return null;
    }

    const data = await response.json();

    logger.info('Outlook calendars listed successfully', {
      userId,
      count: data.value?.length || 0,
    });

    return data.value || [];
  } catch (error) {
    logger.error('Error listing Outlook calendars', error instanceof Error ? error : new Error(String(error)), { userId });
    return null;
  }
}

/**
 * Get calendar events within a date range
 *
 * @param userId - User ID
 * @param calendarId - Calendar ID
 * @param start - Start date
 * @param end - End date
 * @returns Array of events or null on error
 */
export async function getCalendarEvents(
  userId: string,
  calendarId: string,
  start: Date,
  end: Date
): Promise<OutlookEvent[] | null> {
  try {
    const accessToken = await getValidOutlookToken(userId);

    if (!accessToken) {
      logger.warn('No valid Outlook token for getting events', { userId });
      return null;
    }

    // Format dates for Microsoft Graph API
    const startDateTime = start.toISOString();
    const endDateTime = end.toISOString();

    const url = `${GRAPH_API}/me/calendars/${calendarId}/calendarView?startDateTime=${encodeURIComponent(startDateTime)}&endDateTime=${encodeURIComponent(endDateTime)}`;

    const response = await fetch(url, {
      method: 'GET',
      headers: {
        Authorization: `Bearer ${accessToken}`,
        'Content-Type': 'application/json',
        Prefer: 'outlook.timezone="UTC"',
      },
    });

    if (!response.ok) {
      const error = await response.text();
      logger.error('Failed to get Outlook calendar events', {
        userId,
        calendarId,
        error,
        status: response.status,
      });
      return null;
    }

    const data = await response.json();

    logger.info('Outlook calendar events retrieved', {
      userId,
      calendarId,
      count: data.value?.length || 0,
    });

    return data.value || [];
  } catch (error) {
    logger.error('Error getting Outlook calendar events', error instanceof Error ? error : new Error(String(error)), { userId, calendarId });
    return null;
  }
}

/**
 * Create a new calendar event
 *
 * @param userId - User ID
 * @param calendarId - Calendar ID
 * @param event - Event data
 * @returns Created event or null on error
 */
export async function createCalendarEvent(
  userId: string,
  calendarId: string,
  event: CreateOutlookEventInput
): Promise<OutlookEvent | null> {
  try {
    const accessToken = await getValidOutlookToken(userId);

    if (!accessToken) {
      logger.warn('No valid Outlook token for creating event', { userId });
      return null;
    }

    const url = `${GRAPH_API}/me/calendars/${calendarId}/events`;

    const response = await fetch(url, {
      method: 'POST',
      headers: {
        Authorization: `Bearer ${accessToken}`,
        'Content-Type': 'application/json',
      },
      body: JSON.stringify(event),
    });

    if (!response.ok) {
      const error = await response.text();
      logger.error('Failed to create Outlook calendar event', {
        userId,
        calendarId,
        error,
        status: response.status,
      });
      return null;
    }

    const createdEvent: OutlookEvent = await response.json();

    logger.info('Outlook calendar event created', {
      userId,
      calendarId,
      eventId: createdEvent.id,
      subject: createdEvent.subject,
    });

    return createdEvent;
  } catch (error) {
    logger.error('Error creating Outlook calendar event', error instanceof Error ? error : new Error(String(error)), { userId, calendarId });
    return null;
  }
}

/**
 * Update an existing calendar event
 *
 * @param userId - User ID
 * @param calendarId - Calendar ID
 * @param eventId - Event ID to update
 * @param event - Updated event data
 * @returns Updated event or null on error
 */
export async function updateCalendarEvent(
  userId: string,
  calendarId: string,
  eventId: string,
  event: Partial<CreateOutlookEventInput>
): Promise<OutlookEvent | null> {
  try {
    const accessToken = await getValidOutlookToken(userId);

    if (!accessToken) {
      logger.warn('No valid Outlook token for updating event', { userId });
      return null;
    }

    const url = `${GRAPH_API}/me/calendars/${calendarId}/events/${eventId}`;

    const response = await fetch(url, {
      method: 'PATCH',
      headers: {
        Authorization: `Bearer ${accessToken}`,
        'Content-Type': 'application/json',
      },
      body: JSON.stringify(event),
    });

    if (!response.ok) {
      const error = await response.text();
      logger.error('Failed to update Outlook calendar event', {
        userId,
        calendarId,
        eventId,
        error,
        status: response.status,
      });
      return null;
    }

    const updatedEvent: OutlookEvent = await response.json();

    logger.info('Outlook calendar event updated', {
      userId,
      calendarId,
      eventId,
      subject: updatedEvent.subject,
    });

    return updatedEvent;
  } catch (error) {
    logger.error('Error updating Outlook calendar event', error instanceof Error ? error : new Error(String(error)), { userId, calendarId, eventId });
    return null;
  }
}

/**
 * Delete a calendar event
 *
 * @param userId - User ID
 * @param calendarId - Calendar ID
 * @param eventId - Event ID to delete
 * @returns True if deleted successfully, false otherwise
 */
export async function deleteCalendarEvent(
  userId: string,
  calendarId: string,
  eventId: string
): Promise<boolean> {
  try {
    const accessToken = await getValidOutlookToken(userId);

    if (!accessToken) {
      logger.warn('No valid Outlook token for deleting event', { userId });
      return false;
    }

    const url = `${GRAPH_API}/me/calendars/${calendarId}/events/${eventId}`;

    const response = await fetch(url, {
      method: 'DELETE',
      headers: {
        Authorization: `Bearer ${accessToken}`,
      },
    });

    if (!response.ok && response.status !== 404) {
      // 404 means already deleted, which is OK
      const error = await response.text();
      logger.error('Failed to delete Outlook calendar event', {
        userId,
        calendarId,
        eventId,
        error,
        status: response.status,
      });
      return false;
    }

    logger.info('Outlook calendar event deleted', {
      userId,
      calendarId,
      eventId,
    });

    return true;
  } catch (error) {
    logger.error('Error deleting Outlook calendar event', error instanceof Error ? error : new Error(String(error)), { userId, calendarId, eventId });
    return false;
  }
}

/**
 * Sync Mawidi bookings to Outlook Calendar
 *
 * @param userId - User ID
 * @param calendarId - Optional specific calendar ID (uses default if not provided)
 * @returns Sync result with statistics
 */
export async function syncMawidiBookingsToOutlook(
  userId: string,
  calendarId?: string
): Promise<SyncResult> {
  const result: SyncResult = {
    success: false,
    created: 0,
    updated: 0,
    deleted: 0,
    errors: [],
    skipped: 0,
  };

  try {
    // Verify valid token
    const accessToken = await getValidOutlookToken(userId);
    if (!accessToken) {
      result.errors.push('No valid Outlook access token');
      return result;
    }

    // Get target calendar (use default if not specified)
    let targetCalendarId = calendarId;
    if (!targetCalendarId) {
      const calendars = await listUserCalendars(userId);
      if (!calendars || calendars.length === 0) {
        result.errors.push('No calendars found');
        return result;
      }
      const defaultCalendar = calendars.find((cal) => cal.isDefaultCalendar);
      targetCalendarId = defaultCalendar?.id || calendars[0].id;
    }

    // Get user's bookings from Mawidi (example - adjust based on your schema)
    // This assumes you have a bookings table, adjust as needed
    const bookings = await prisma.bookings?.findMany({
      where: {
        userId,
        status: 'confirmed',
      },
    }).catch(() => []);

    if (!bookings || bookings.length === 0) {
      logger.info('No bookings to sync', { userId });
      result.success = true;
      return result;
    }

    // Sync each booking
    for (const booking of bookings) {
      try {
        // Check if event already exists in Outlook
        // You might store the Outlook event ID in your booking record
        const outlookEventId = (booking as { outlookEventId?: string }).outlookEventId;

        const eventData: CreateOutlookEventInput = {
          subject: (booking as { title?: string }).title || 'Mawidi Appointment',
          body: {
            contentType: 'HTML',
            content: (booking as { description?: string }).description || '',
          },
          start: {
            dateTime: new Date((booking as { startTime?: Date }).startTime || new Date()).toISOString(),
            timeZone: 'UTC',
          },
          end: {
            dateTime: new Date((booking as { endTime?: Date }).endTime || new Date()).toISOString(),
            timeZone: 'UTC',
          },
          isOnlineMeeting: true,
          onlineMeetingProvider: 'teamsForBusiness',
          showAs: 'busy',
        };

        if (outlookEventId) {
          // Update existing event
          const updated = await updateCalendarEvent(
            userId,
            targetCalendarId,
            outlookEventId,
            eventData
          );
          if (updated) {
            result.updated++;
          } else {
            result.errors.push(`Failed to update booking ${booking.id}`);
          }
        } else {
          // Create new event
          const created = await createCalendarEvent(userId, targetCalendarId, eventData);
          if (created) {
            result.created++;
            // Store the Outlook event ID in your booking record
            // await prisma.bookings.update({
            //   where: { id: booking.id },
            //   data: { outlookEventId: created.id },
            // });
          } else {
            result.errors.push(`Failed to create event for booking ${booking.id}`);
          }
        }
      } catch (error) {
        result.errors.push(`Error syncing booking ${booking.id}: ${error}`);
        logger.error('Error syncing individual booking', error instanceof Error ? error : new Error(String(error)), { userId, bookingId: booking.id });
      }
    }

    // Update last sync time
    await integrationsAdapter.update({
      where: {
        userId_type: {
          userId,
          type: 'outlook_calendar',
        },
      },
      data: {
        lastSyncAt: new Date(),
        updatedAt: new Date(),
      },
    });

    result.success = result.errors.length === 0;
    logger.info('Outlook sync completed', {
      userId,
      created: result.created,
      updated: result.updated,
      errors: result.errors.length,
    });

    return result;
  } catch (error) {
    logger.error('Error syncing Mawidi bookings to Outlook', error instanceof Error ? error : new Error(String(error)), { userId });
    result.errors.push(`Sync failed: ${error instanceof Error ? error.message : String(error)}`);
    return result;
  }
}

/**
 * Check if user has a valid Outlook calendar connection
 *
 * @param userId - User ID
 * @returns Connection status details
 */
export async function hasOutlookConnection(userId: string): Promise<{
  connected: boolean;
  needsReconnect: boolean;
  hasCalendarScope: boolean;
}> {
  try {
    const integration = await integrationsAdapter.findUnique({
      where: {
        userId_type: {
          userId,
          type: 'outlook_calendar',
        },
      },
    });

    if (!integration) {
      return {
        connected: false,
        needsReconnect: false,
        hasCalendarScope: false,
      };
    }

    const hasCalendarScope =
      integration.scope?.includes('Calendars.Read') ||
      integration.scope?.includes('Calendars.ReadWrite') ||
      false;
    const needsReconnect = integration.status === 'error' || !integration.refreshToken;

    return {
      connected: integration.status === 'connected',
      needsReconnect,
      hasCalendarScope,
    };
  } catch (error) {
    logger.error('Error checking Outlook connection', error instanceof Error ? error : new Error(String(error)), { userId });
    return {
      connected: false,
      needsReconnect: false,
      hasCalendarScope: false,
    };
  }
}

/**
 * Disconnect Outlook calendar for a user
 *
 * @param userId - User ID
 * @returns True if disconnected successfully
 */
export async function disconnectOutlookCalendar(userId: string): Promise<boolean> {
  try {
    await integrationsAdapter.delete({
      where: {
        userId_type: {
          userId,
          type: 'outlook_calendar',
        },
      },
    });

    logger.info('Outlook calendar disconnected', { userId });
    return true;
  } catch (error) {
    logger.error('Error disconnecting Outlook calendar', error instanceof Error ? error : new Error(String(error)), { userId });
    return false;
  }
}
