import { OAuth2Client } from "google-auth-library";
import { google } from "googleapis";
import { redis } from "@/infrastructure/cache/redis";
import { getCache, setCache, deleteCache, deleteCachePattern } from "@/infrastructure/cache/redis";

// Google OAuth2 client
const oauth2Client = new OAuth2Client(
  process.env.GOOGLE_CLIENT_ID!,
  process.env.GOOGLE_CLIENT_SECRET!,
  process.env.GOOGLE_REDIRECT_URI!
);

// Calendar ID
const CALENDAR_ID = process.env.GOOGLE_CALENDAR_ID || "primary";

// Cache TTL in seconds (15 minutes)
const CACHE_TTL = 900;

// Lock TTL in seconds (15 minutes)
const LOCK_TTL = 900;

// Event duration in minutes
const EVENT_DURATION_MINUTES = 60;

export interface TimeSlot {
  startTime: Date;
  endTime: Date;
  available: boolean;
  eventId?: string;
}

export interface CreateEventRequest {
  summary: string;
  description?: string;
  start: Date;
  patientPhone: string;
  patientName: string;
  email?: string;
  isCrisis: boolean;
}

export interface AvailabilityRequest {
  startDate: Date;
  endDate: Date;
}

// Helper function to format date to ISO string
function formatDate(date: Date): string {
  return date.toISOString();
}

// Generate cache key for availability
function getAvailabilityCacheKey(startDate: Date, endDate: Date): string {
  const key = `availability:${formatDate(startDate)}:${formatDate(endDate)}`;
  return key;
}

// Generate lock key for time slot
function getSlotLockKey(startTime: Date, endTime: Date): string {
  return `slot:lock:${formatDate(startTime)}:${formatDate(endTime)}`;
}

export async function getAvailability(request: AvailabilityRequest): Promise<TimeSlot[]> {
  const { startDate, endDate } = request;
  const cacheKey = getAvailabilityCacheKey(startDate, endDate);

  try {
    // Try to get availability from cache
    const cached = await getCache<TimeSlot[]>(cacheKey);
    if (cached) {
      console.log("Returning cached availability");
      return cached;
    }

    // If not cached, fetch from Google Calendar
    const auth = new google.auth.OAuth2();
    auth.setCredentials(oauth2Client.credentials);

    const calendar = google.calendar({ version: "v3", auth });

    const response = await calendar.events.list({
      calendarId: CALENDAR_ID,
      timeMin: formatDate(startDate),
      timeMax: formatDate(endDate),
      singleEvents: true,
      orderBy: "startTime",
    });

    const events = response.data.items || [];

    // Generate hourly slots
    const slots: TimeSlot[] = [];
    const startHour = startDate.getHours();
    const endHour = endDate.getHours();

    for (let hour = startHour; hour < endHour; hour++) {
      const slotStart = new Date(startDate);
      slotStart.setHours(hour, 0, 0, 0);

      const slotEnd = new Date(startDate);
      slotEnd.setHours(hour + 1, 0, 0, 0);

      // Check if this slot is free
      const isAvailable = !events.some((event) => {
        const eventStart = new Date(event.start?.dateTime || "");
        const eventEnd = new Date(event.end?.dateTime || "");

        // Check for overlap
        return slotStart < eventEnd && slotEnd > eventStart;
      });

      slots.push({
        startTime: slotStart,
        endTime: slotEnd,
        available: isAvailable,
        eventId: undefined,
      });
    }

    // Cache the result
    await setCache(cacheKey, slots, CACHE_TTL);

    return slots;
  } catch (error) {
    console.error("Error fetching availability:", error);
    throw new Error("Failed to fetch availability from Google Calendar");
  }
}

export async function createEvent(request: CreateEventRequest): Promise<string> {
  const { summary, description, start, patientPhone, patientName, email, isCrisis } = request;

  const lockKey = getSlotLockKey(start, new Date(start.getTime() + EVENT_DURATION_MINUTES * 60000));

  try {
    // Try to acquire lock
    const lockAcquired = await redis.set(lockKey, "1", "PX", LOCK_TTL, "NX");
    if (!lockAcquired) {
      throw new Error("This time slot is no longer available. Please select another time.");
    }

    // Authenticate
    const auth = new google.auth.OAuth2();
    auth.setCredentials(oauth2Client.credentials);

    const calendar = google.calendar({ version: "v3", auth });

    // Create event
    const event = {
      summary,
      description: `Paciente: ${patientName}\nTeléfono: ${patientPhone}${email ? `\nEmail: ${email}` : ""}${description ? `\n\nMotivo: ${description}` : ""}`,
      start: {
        dateTime: formatDate(start),
      },
      end: {
        dateTime: formatDate(new Date(start.getTime() + EVENT_DURATION_MINUTES * 60000)),
      },
      colorId: isCrisis ? "11" : "6", // Red for crisis, green for regular
      reminders: {
        useDefault: false,
        overrides: [
          {
            method: "email",
            minutes: 1440, // 24 hours before
          },
        ],
      },
    };

    const response = (await calendar.events.insert({
      calendarId: CALENDAR_ID,
      requestBody: event,
    })) as any;

    const eventId = response.data.id!;

    // Invalidate availability cache
    const today = new Date();
    const tomorrow = new Date(today);
    tomorrow.setDate(tomorrow.getDate() + 1);
    await deleteCachePattern("availability:*");

    // Return event ID
    return eventId;
  } catch (error) {
    // Release lock on error
    await redis.del(lockKey);
    console.error("Error creating event:", error);
    throw new Error("Failed to create event in Google Calendar");
  }
}

export async function cancelEvent(eventId: string): Promise<void> {
  try {
    const auth = new google.auth.OAuth2();
    auth.setCredentials(oauth2Client.credentials);

    const calendar = google.calendar({ version: "v3", auth });

    await calendar.events.delete({
      calendarId: CALENDAR_ID,
      eventId,
    });

    // Invalidate availability cache
    await deleteCachePattern("availability:*");

    console.log(`Event ${eventId} cancelled successfully`);
  } catch (error) {
    console.error("Error canceling event:", error);
    throw new Error("Failed to cancel event in Google Calendar");
  }
}

export async function getUpcomingEvents(): Promise<any[]> {
  try {
    const auth = new google.auth.OAuth2();
    auth.setCredentials(oauth2Client.credentials);

    const calendar = google.calendar({ version: "v3", auth });

    const now = new Date();
    const oneMonthLater = new Date(now.getTime() + 30 * 24 * 60 * 60 * 1000);

    const response = await calendar.events.list({
      calendarId: CALENDAR_ID,
      timeMin: formatDate(now),
      timeMax: formatDate(oneMonthLater),
      singleEvents: true,
      orderBy: "startTime",
    });

    return response.data.items || [];
  } catch (error) {
    console.error("Error fetching upcoming events:", error);
    throw new Error("Failed to fetch upcoming events from Google Calendar");
  }
}

// Helper function to format phone number
export function formatPhoneNumber(phone: string): string {
  // Remove all non-numeric characters
  return phone.replace(/\D/g, "");
}

// Helper function to validate date is in the future
export function validateFutureDate(date: Date): boolean {
  const now = new Date();
  const oneWeekLater = new Date(now.getTime() + 7 * 24 * 60 * 60 * 1000);
  return date >= now && date <= oneWeekLater;
}

export async function updateEvent(
  eventId: string,
  newStart: Date,
  summary?: string,
  description?: string
): Promise<void> {
  try {
    const auth = new google.auth.OAuth2();
    auth.setCredentials(oauth2Client.credentials);

    const calendar = google.calendar({ version: "v3", auth });

    const event = {
      start: {
        dateTime: formatDate(newStart),
      },
      end: {
        dateTime: formatDate(new Date(newStart.getTime() + EVENT_DURATION_MINUTES * 60000)),
      },
      ...(summary && { summary }),
      ...(description && { description }),
    };

    await calendar.events.update({
      calendarId: CALENDAR_ID,
      eventId,
      requestBody: event,
    });

    // Invalidate availability cache
    await deleteCachePattern("availability:*");

    console.log(`Event ${eventId} updated successfully to ${newStart}`);
  } catch (error) {
    console.error("Error updating event:", error);
    throw new Error("Failed to update event in Google Calendar");
  }
}
