/**
 * Google Calendar User OAuth Service
 *
 * Handles user OAuth-based Google Calendar operations (user's own calendar via their OAuth tokens).
 * This is DIFFERENT from the service account implementation in /lib/google/calendar.ts.
 *
 * Features:
 * - Token management (refresh, validation)
 * - Calendar operations (list, create, update, delete events)
 * - Sync Mawidi bookings to user's Google Calendar
 *
 * @module lib/services/google-calendar-user.service
 */

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

// Types for Google Calendar API responses
export interface Calendar {
  id: string;
  summary: string;
  primary?: boolean;
  accessRole: string;
  backgroundColor?: string;
  foregroundColor?: string;
  timeZone?: string;
}

export interface CalendarEvent {
  id: string;
  summary: string;
  description?: string;
  start: { dateTime: string; timeZone: string };
  end: { dateTime: string; timeZone: string };
  htmlLink?: string;
  hangoutLink?: string;
  status?: string;
  attendees?: Array<{
    email: string;
    displayName?: string;
    responseStatus?: string;
  }>;
}

export interface CreateEventInput {
  summary: string;
  description?: string;
  location?: string;
  start: { dateTime: string; timeZone: string };
  end: { dateTime: string; timeZone: string };
  attendees?: { email: string; displayName?: string }[];
  conferenceData?: {
    createRequest: {
      requestId: string;
      conferenceSolutionKey: { type: string };
    };
  };
}

export interface SyncResult {
  success: boolean;
  created: number;
  updated: number;
  skipped: number;
  errors: string[];
}

export interface GoogleCalendarListResponse {
  kind: string;
  etag: string;
  items: Calendar[];
  nextPageToken?: string;
}

export interface GoogleCalendarEventsResponse {
  kind: string;
  etag: string;
  items: CalendarEvent[];
  nextPageToken?: string;
}

// Default timezone for GCC region
const DEFAULT_TIMEZONE = 'Asia/Qatar';

/**
 * Get valid Google Calendar access token for a user
 * Automatically refreshes if expired (< 5 min buffer)
 */
export async function getValidGoogleCalendarToken(
  userId: string
): Promise<string | null> {
  try {
    // Get integration from database
    const integration = await integrationsAdapter.findUnique({
      where: {
        userId_type: {
          userId,
          type: 'google_calendar',
        },
      },
    });

    if (!integration || !integration.accessToken) {
      logger.warn('No Google Calendar integration found', { userId });
      return null;
    }

    // Check if token is expired or expiring soon (5 min buffer)
    const expiryBuffer = 5 * 60 * 1000; // 5 minutes
    const now = Date.now();
    const tokenExpiry = integration.tokenExpiry
      ? new Date(integration.tokenExpiry).getTime()
      : 0;

    if (tokenExpiry > now + expiryBuffer) {
      // Token is still valid, decrypt and return
      try {
        return decrypt(integration.accessToken);
      } catch (error) {
        logger.error('Failed to decrypt access token', { userId, error });
        return null;
      }
    }

    // Token expired or expiring soon, refresh it
    logger.info('Access token expired or expiring soon, refreshing', { userId });
    const refreshed = await refreshGoogleCalendarToken(userId);

    if (!refreshed) {
      return null;
    }

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

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

    return decrypt(updatedIntegration.accessToken);
  } catch (error) {
    logger.error('Error getting valid Google Calendar token', { userId, error });
    return null;
  }
}

/**
 * Refresh Google Calendar OAuth access token using refresh token
 */
export async function refreshGoogleCalendarToken(
  userId: string
): Promise<boolean> {
  try {
    // Get integration with refresh token
    const integration = await integrationsAdapter.findUnique({
      where: {
        userId_type: {
          userId,
          type: 'google_calendar',
        },
      },
    });

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

    // Decrypt refresh token
    let refreshToken: string;
    try {
      refreshToken = decrypt(integration.refreshToken);
    } catch (error) {
      logger.error('Failed to decrypt refresh token', { userId, error });
      await integrationsAdapter.update({
        where: { id: integration.id },
        data: {
          status: 'error',
          lastError: 'Failed to decrypt refresh token. Please reconnect.',
          updatedAt: new Date(),
        },
      });
      return false;
    }

    // Call Google OAuth token endpoint
    const response = await fetch('https://oauth2.googleapis.com/token', {
      method: 'POST',
      headers: {
        'Content-Type': 'application/x-www-form-urlencoded',
      },
      body: new URLSearchParams({
        client_id: process.env.GOOGLE_CLIENT_ID || '',
        client_secret: process.env.GOOGLE_CLIENT_SECRET || '',
        refresh_token: refreshToken,
        grant_type: 'refresh_token',
      }),
    });

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

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

      return false;
    }

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

    // Encrypt new access token
    const encryptedAccessToken = encrypt(tokenData.access_token);

    // Update integration with new token
    await integrationsAdapter.update({
      where: { id: integration.id },
      data: {
        accessToken: encryptedAccessToken,
        tokenExpiry: expiresAt,
        status: 'connected',
        lastError: null,
        updatedAt: new Date(),
      },
    });

    logger.info('Google Calendar token refreshed successfully', {
      userId,
      expiresAt,
    });

    return true;
  } catch (error) {
    logger.error('Error refreshing Google Calendar token', { userId, error });
    return false;
  }
}

/**
 * List user's Google Calendars
 */
export async function listUserCalendars(
  userId: string
): Promise<Calendar[] | null> {
  try {
    const accessToken = await getValidGoogleCalendarToken(userId);
    if (!accessToken) {
      logger.warn('No valid access token for listing calendars', { userId });
      return null;
    }

    const response = await fetch(
      'https://www.googleapis.com/calendar/v3/users/me/calendarList',
      {
        headers: {
          Authorization: `Bearer ${accessToken}`,
        },
      }
    );

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

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

      return null;
    }

    const data: GoogleCalendarListResponse = await response.json();

    logger.info('Listed Google Calendars successfully', {
      userId,
      count: data.items?.length || 0,
    });

    return data.items || [];
  } catch (error) {
    logger.error('Error listing Google Calendars', { userId, error });
    return null;
  }
}

/**
 * Get calendar events within a time range
 */
export async function getCalendarEvents(
  userId: string,
  calendarId: string,
  timeMin: Date,
  timeMax: Date
): Promise<CalendarEvent[] | null> {
  try {
    const accessToken = await getValidGoogleCalendarToken(userId);
    if (!accessToken) {
      return null;
    }

    const params = new URLSearchParams({
      timeMin: timeMin.toISOString(),
      timeMax: timeMax.toISOString(),
      singleEvents: 'true',
      orderBy: 'startTime',
      maxResults: '250',
    });

    const response = await fetch(
      `https://www.googleapis.com/calendar/v3/calendars/${encodeURIComponent(
        calendarId
      )}/events?${params}`,
      {
        headers: {
          Authorization: `Bearer ${accessToken}`,
        },
      }
    );

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

    const data: GoogleCalendarEventsResponse = await response.json();

    logger.info('Fetched calendar events successfully', {
      userId,
      calendarId,
      count: data.items?.length || 0,
    });

    return data.items || [];
  } catch (error) {
    logger.error('Error getting calendar events', { userId, calendarId, error });
    return null;
  }
}

/**
 * Create a Google Calendar event
 * Optionally include Google Meet conference data
 */
export async function createCalendarEvent(
  userId: string,
  calendarId: string,
  event: CreateEventInput
): Promise<CalendarEvent | null> {
  try {
    const accessToken = await getValidGoogleCalendarToken(userId);
    if (!accessToken) {
      return null;
    }

    // Determine if we need conference data version
    const hasConference = !!event.conferenceData;
    const url = hasConference
      ? `https://www.googleapis.com/calendar/v3/calendars/${encodeURIComponent(
          calendarId
        )}/events?conferenceDataVersion=1`
      : `https://www.googleapis.com/calendar/v3/calendars/${encodeURIComponent(
          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 errorText = await response.text();
      logger.error('Failed to create calendar event', {
        userId,
        calendarId,
        status: response.status,
        error: errorText,
      });
      return null;
    }

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

    logger.info('Created calendar event successfully', {
      userId,
      calendarId,
      eventId: createdEvent.id,
    });

    return createdEvent;
  } catch (error) {
    logger.error('Error creating calendar event', { userId, calendarId, error });
    return null;
  }
}

/**
 * Update an existing Google Calendar event
 */
export async function updateCalendarEvent(
  userId: string,
  calendarId: string,
  eventId: string,
  event: Partial<CreateEventInput>
): Promise<CalendarEvent | null> {
  try {
    const accessToken = await getValidGoogleCalendarToken(userId);
    if (!accessToken) {
      return null;
    }

    // Get existing event first
    const getResponse = await fetch(
      `https://www.googleapis.com/calendar/v3/calendars/${encodeURIComponent(
        calendarId
      )}/events/${encodeURIComponent(eventId)}`,
      {
        headers: {
          Authorization: `Bearer ${accessToken}`,
        },
      }
    );

    if (!getResponse.ok) {
      logger.error('Failed to get existing event for update', {
        userId,
        calendarId,
        eventId,
      });
      return null;
    }

    const existingEvent = await getResponse.json();

    // Merge with updates
    const updatedEvent = {
      ...existingEvent,
      ...event,
    };

    // Update event
    const hasConference = !!event.conferenceData;
    const url = hasConference
      ? `https://www.googleapis.com/calendar/v3/calendars/${encodeURIComponent(
          calendarId
        )}/events/${encodeURIComponent(eventId)}?conferenceDataVersion=1`
      : `https://www.googleapis.com/calendar/v3/calendars/${encodeURIComponent(
          calendarId
        )}/events/${encodeURIComponent(eventId)}`;

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

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

    const result: CalendarEvent = await response.json();

    logger.info('Updated calendar event successfully', {
      userId,
      calendarId,
      eventId,
    });

    return result;
  } catch (error) {
    logger.error('Error updating calendar event', {
      userId,
      calendarId,
      eventId,
      error,
    });
    return null;
  }
}

/**
 * Delete a Google Calendar event
 */
export async function deleteCalendarEvent(
  userId: string,
  calendarId: string,
  eventId: string
): Promise<boolean> {
  try {
    const accessToken = await getValidGoogleCalendarToken(userId);
    if (!accessToken) {
      return false;
    }

    const response = await fetch(
      `https://www.googleapis.com/calendar/v3/calendars/${encodeURIComponent(
        calendarId
      )}/events/${encodeURIComponent(eventId)}`,
      {
        method: 'DELETE',
        headers: {
          Authorization: `Bearer ${accessToken}`,
        },
      }
    );

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

    logger.info('Deleted calendar event successfully', {
      userId,
      calendarId,
      eventId,
    });

    return true;
  } catch (error) {
    logger.error('Error deleting calendar event', {
      userId,
      calendarId,
      eventId,
      error,
    });
    return false;
  }
}

/**
 * Sync Mawidi bookings to user's Google Calendar
 * Creates/updates events for all pending and confirmed bookings
 */
export async function syncMawidiBookingsToGoogle(
  userId: string,
  options: {
    calendarId?: string;
    startDate?: Date;
    endDate?: Date;
    includeGoogleMeet?: boolean;
  } = {}
): Promise<SyncResult> {
  const result: SyncResult = {
    success: false,
    created: 0,
    updated: 0,
    skipped: 0,
    errors: [],
  };

  try {
    // Check if user has Google Calendar integration
    const integration = await integrationsAdapter.findUnique({
      where: {
        userId_type: {
          userId,
          type: 'google_calendar',
        },
      },
    });

    if (!integration || integration.status !== 'connected') {
      result.errors.push('Google Calendar not connected');
      return result;
    }

    // Get user's primary calendar if not specified
    let calendarId = options.calendarId;
    if (!calendarId) {
      const calendars = await listUserCalendars(userId);
      if (!calendars || calendars.length === 0) {
        result.errors.push('No calendars found');
        return result;
      }
      const primaryCalendar = calendars.find((c) => c.primary) || calendars[0];
      calendarId = primaryCalendar.id;
    }

    // Get user's timezone or use default
    const user = await prisma.users.findUnique({
      where: { id: userId },
      select: { timezone: true },
    });
    const timeZone = user?.timezone || DEFAULT_TIMEZONE;

    // Build date filter
    const dateFilter: any = {};
    if (options.startDate) {
      dateFilter.gte = options.startDate;
    }
    if (options.endDate) {
      dateFilter.lte = options.endDate;
    }

    // Fetch bookings to sync
    const bookings = await prisma.bookings.findMany({
      where: {
        userId,
        status: {
          in: ['pending', 'confirmed'],
        },
        ...(Object.keys(dateFilter).length > 0
          ? { appointmentDate: dateFilter }
          : {}),
      },
      orderBy: {
        appointmentDate: 'asc',
      },
    });

    logger.info('Starting Google Calendar sync', {
      userId,
      calendarId,
      bookingCount: bookings.length,
    });

    // Sync each booking
    for (const booking of bookings) {
      try {
        // Parse appointment time
        const [hours, minutes] = booking.appointmentTime.split(':').map(Number);
        const startDateTime = new Date(booking.appointmentDate);
        startDateTime.setHours(hours, minutes, 0, 0);

        const endDateTime = new Date(startDateTime);
        endDateTime.setMinutes(endDateTime.getMinutes() + booking.duration);

        // Create event input
        const eventInput: CreateEventInput = {
          summary: `${booking.service} - ${booking.customerName}`,
          description: [
            `Customer: ${booking.customerName}`,
            `Phone: ${booking.customerPhone}`,
            booking.customerEmail ? `Email: ${booking.customerEmail}` : '',
            booking.staffMember ? `Staff: ${booking.staffMember}` : '',
            booking.notes ? `Notes: ${booking.notes}` : '',
          ]
            .filter(Boolean)
            .join('\n'),
          start: {
            dateTime: startDateTime.toISOString(),
            timeZone,
          },
          end: {
            dateTime: endDateTime.toISOString(),
            timeZone,
          },
          attendees: booking.customerEmail
            ? [
                {
                  email: booking.customerEmail,
                  displayName: booking.customerName,
                },
              ]
            : undefined,
        };

        // Add Google Meet if requested
        if (options.includeGoogleMeet) {
          eventInput.conferenceData = {
            createRequest: {
              requestId: `mawidi-booking-${booking.id}`,
              conferenceSolutionKey: {
                type: 'hangoutsMeet',
              },
            },
          };
        }

        // Check if event already exists (stored in booking notes)
        const existingEventId = await getGoogleEventIdFromBooking(booking.id);

        if (existingEventId) {
          // Update existing event
          const updated = await updateCalendarEvent(
            userId,
            calendarId,
            existingEventId,
            eventInput
          );

          if (updated) {
            result.updated++;
          } else {
            result.errors.push(
              `Failed to update event for booking ${booking.id}`
            );
          }
        } else {
          // Create new event
          const created = await createCalendarEvent(
            userId,
            calendarId,
            eventInput
          );

          if (created) {
            result.created++;
            // Store event ID in booking notes for future updates
            await storeGoogleEventIdInBooking(booking.id, created.id);
          } else {
            result.errors.push(
              `Failed to create event for booking ${booking.id}`
            );
          }
        }
      } catch (error: any) {
        logger.error('Error syncing individual booking', {
          bookingId: booking.id,
          error,
        });
        result.errors.push(
          `Booking ${booking.id}: ${error.message || 'Unknown error'}`
        );
      }
    }

    // Update last sync time
    await integrationsAdapter.update({
      where: { id: integration.id },
      data: {
        lastSyncAt: new Date(),
        updatedAt: new Date(),
      },
    });

    result.success = result.errors.length === 0;

    logger.info('Google Calendar sync completed', {
      userId,
      result,
    });

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

/**
 * Helper: Get Google Calendar event ID stored in booking notes
 */
async function getGoogleEventIdFromBooking(
  bookingId: string
): Promise<string | null> {
  try {
    const note = await bookingNotesAdapter.findFirst({
      where: {
        bookingId,
        note: {
          startsWith: 'google_calendar_event_id:',
        },
      },
    });

    if (note) {
      return note.note.replace('google_calendar_event_id:', '');
    }

    return null;
  } catch (error) {
    logger.error('Error getting Google event ID from booking', {
      bookingId,
      error,
    });
    return null;
  }
}

/**
 * Helper: Store Google Calendar event ID in booking notes
 */
async function storeGoogleEventIdInBooking(
  bookingId: string,
  eventId: string
): Promise<void> {
  try {
    await bookingNotesAdapter.create({
      data: {
        id: `gcal-${bookingId}-${Date.now()}`,
        bookingId,
        note: `google_calendar_event_id:${eventId}`,
        createdAt: new Date(),
      },
    });
  } catch (error) {
    logger.error('Error storing Google event ID in booking', {
      bookingId,
      eventId,
      error,
    });
  }
}

/**
 * Check if user has valid Google Calendar connection
 */
export async function hasGoogleCalendarConnection(userId: string): Promise<{
  connected: boolean;
  needsReconnect: boolean;
}> {
  try {
    const integration = await integrationsAdapter.findUnique({
      where: {
        userId_type: {
          userId,
          type: 'google_calendar',
        },
      },
    });

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

    const needsReconnect =
      integration.status === 'error' || !integration.refreshToken;

    return {
      connected: integration.status === 'connected',
      needsReconnect,
    };
  } catch (error) {
    logger.error('Error checking Google Calendar connection', {
      userId,
      error,
    });
    return {
      connected: false,
      needsReconnect: false,
    };
  }
}

/**
 * Disconnect Google Calendar for a user
 */
export async function disconnectGoogleCalendar(
  userId: string
): Promise<boolean> {
  try {
    await integrationsAdapter.delete({
      where: {
        userId_type: {
          userId,
          type: 'google_calendar',
        },
      },
    });

    logger.info('Google Calendar disconnected', { userId });
    return true;
  } catch (error) {
    logger.error('Error disconnecting Google Calendar', { userId, error });
    return false;
  }
}
