import { decryptSecret, encryptSecret } from "@/lib/ai/crypto";
import type { WebhookEventEnvelope } from "@/lib/developers/webhooks";
import { createAdminClient } from "@/lib/supabase/admin";
import type {
  CalendarEvent,
  GoogleCalendarCalendar,
  GoogleCalendarConnectionConfig,
  IntegrationConnection,
} from "@/types";

interface GoogleCalendarCredentials {
  access_token: string;
  refresh_token?: string;
  expiry_date?: number;
  scope?: string;
  token_type?: string;
  connected_email?: string | null;
}

interface GoogleCalendarStatePayload {
  userId: string;
  teamId: string | null;
  issuedAt: number;
}

function getGoogleCalendarClientConfig() {
  const clientId = process.env.GOOGLE_CLIENT_ID;
  const clientSecret = process.env.GOOGLE_CLIENT_SECRET;

  if (!clientId || !clientSecret) {
    throw new Error("Missing GOOGLE_CLIENT_ID or GOOGLE_CLIENT_SECRET");
  }

  return { clientId, clientSecret };
}

export function getGoogleCalendarRedirectUri(origin?: string) {
  const configured = process.env.GOOGLE_CALENDAR_REDIRECT_URI || process.env.NEXT_PUBLIC_APP_URL;
  const base = (configured || origin || "").replace(/\/$/, "");
  if (!base) {
    throw new Error("Missing NEXT_PUBLIC_APP_URL or GOOGLE_CALENDAR_REDIRECT_URI");
  }

  return base.endsWith("/api/integrations/google-calendar/callback")
    ? base
    : `${base}/api/integrations/google-calendar/callback`;
}

export function createGoogleCalendarState(payload: GoogleCalendarStatePayload) {
  return encodeURIComponent(encryptSecret(JSON.stringify(payload)));
}

export function parseGoogleCalendarState(value: string) {
  const parsed = JSON.parse(decryptSecret(decodeURIComponent(value))) as GoogleCalendarStatePayload;
  return parsed;
}

export function buildGoogleCalendarConnectUrl(input: {
  state: string;
  redirectUri: string;
}) {
  const { clientId } = getGoogleCalendarClientConfig();
  const params = new URLSearchParams({
    client_id: clientId,
    redirect_uri: input.redirectUri,
    response_type: "code",
    access_type: "offline",
    include_granted_scopes: "true",
    prompt: "consent",
    scope: [
      "https://www.googleapis.com/auth/calendar.events",
      "https://www.googleapis.com/auth/calendar.calendarlist.readonly",
    ].join(" "),
    state: input.state,
  });

  return `https://accounts.google.com/o/oauth2/v2/auth?${params.toString()}`;
}

export async function exchangeGoogleCalendarCode(input: {
  code: string;
  redirectUri: string;
}) {
  const { clientId, clientSecret } = getGoogleCalendarClientConfig();
  const params = new URLSearchParams({
    code: input.code,
    client_id: clientId,
    client_secret: clientSecret,
    redirect_uri: input.redirectUri,
    grant_type: "authorization_code",
  });

  const response = await fetch("https://oauth2.googleapis.com/token", {
    method: "POST",
    headers: {
      "Content-Type": "application/x-www-form-urlencoded",
    },
    body: params.toString(),
  });

  if (!response.ok) {
    const errorText = await response.text();
    throw new Error(errorText || "Google token exchange failed");
  }

  const token = (await response.json()) as {
    access_token: string;
    refresh_token?: string;
    expires_in: number;
    scope?: string;
    token_type?: string;
  };

  return {
    access_token: token.access_token,
    refresh_token: token.refresh_token,
    expiry_date: Date.now() + token.expires_in * 1000,
    scope: token.scope,
    token_type: token.token_type,
  } satisfies GoogleCalendarCredentials;
}

function parseConnectionConfig(config: Record<string, unknown> | null | undefined) {
  return {
    calendarId: typeof config?.calendarId === "string" ? config.calendarId : null,
    calendarSummary: typeof config?.calendarSummary === "string" ? config.calendarSummary : null,
    connectedEmail: typeof config?.connectedEmail === "string" ? config.connectedEmail : null,
  } satisfies GoogleCalendarConnectionConfig;
}

function parseCredentials(secretEncrypted: string) {
  return JSON.parse(decryptSecret(secretEncrypted)) as GoogleCalendarCredentials;
}

function serializeCredentials(credentials: GoogleCalendarCredentials) {
  return encryptSecret(JSON.stringify(credentials));
}

async function refreshGoogleCalendarAccessToken(
  admin: ReturnType<typeof createAdminClient>,
  connection: IntegrationConnection & { secret_encrypted: string }
) {
  const { clientId, clientSecret } = getGoogleCalendarClientConfig();
  const credentials = parseCredentials(connection.secret_encrypted);

  if (
    credentials.access_token &&
    credentials.expiry_date &&
    credentials.expiry_date > Date.now() + 60_000
  ) {
    return credentials;
  }

  if (!credentials.refresh_token) {
    throw new Error("Google Calendar refresh token missing");
  }

  const params = new URLSearchParams({
    client_id: clientId,
    client_secret: clientSecret,
    refresh_token: credentials.refresh_token,
    grant_type: "refresh_token",
  });

  const response = await fetch("https://oauth2.googleapis.com/token", {
    method: "POST",
    headers: {
      "Content-Type": "application/x-www-form-urlencoded",
    },
    body: params.toString(),
  });

  if (!response.ok) {
    const errorText = await response.text();
    throw new Error(errorText || "Google token refresh failed");
  }

  const token = (await response.json()) as {
    access_token: string;
    expires_in: number;
    scope?: string;
    token_type?: string;
  };

  const nextCredentials: GoogleCalendarCredentials = {
    ...credentials,
    access_token: token.access_token,
    expiry_date: Date.now() + token.expires_in * 1000,
    scope: token.scope || credentials.scope,
    token_type: token.token_type || credentials.token_type,
  };

  await admin
    .from("integration_connections")
    .update({
      secret_encrypted: serializeCredentials(nextCredentials),
    })
    .eq("id", connection.id);

  return nextCredentials;
}

async function googleCalendarFetch<T>(
  admin: ReturnType<typeof createAdminClient>,
  connection: IntegrationConnection & { secret_encrypted: string },
  input: {
    path: string;
    method?: "GET" | "POST" | "PUT" | "DELETE";
    body?: Record<string, unknown>;
  }
) {
  const credentials = await refreshGoogleCalendarAccessToken(admin, connection);
  const response = await fetch(`https://www.googleapis.com/calendar/v3${input.path}`, {
    method: input.method || "GET",
    headers: {
      Authorization: `Bearer ${credentials.access_token}`,
      "Content-Type": "application/json",
      "User-Agent": "Mindavio-GoogleCalendar/1.0",
    },
    body: input.body ? JSON.stringify(input.body) : undefined,
  });

  if (!response.ok) {
    const errorText = await response.text();
    throw new Error(errorText || "Google Calendar request failed");
  }

  if (response.status === 204) {
    return null as T;
  }

  return (await response.json()) as T;
}

function isLikelyEmail(value: string | null | undefined) {
  return Boolean(value && value.includes("@"));
}

export async function listGoogleCalendars(
  admin: ReturnType<typeof createAdminClient>,
  connection: IntegrationConnection & { secret_encrypted: string }
) {
  const payload = await googleCalendarFetch<{
    items?: Array<{
      id: string;
      summary?: string;
      description?: string;
      primary?: boolean;
      accessRole?: string;
      backgroundColor?: string;
    }>;
  }>(admin, connection, {
    path: "/users/me/calendarList?minAccessRole=writer&showDeleted=false",
  });

  return (payload.items || []).map((calendar) => ({
    id: calendar.id,
    summary: calendar.summary || calendar.id,
    description: calendar.description || null,
    primary: Boolean(calendar.primary),
    accessRole: calendar.accessRole || null,
    backgroundColor: calendar.backgroundColor || null,
  })) satisfies GoogleCalendarCalendar[];
}

function addDay(dateIso: string) {
  const next = new Date(dateIso);
  next.setUTCDate(next.getUTCDate() + 1);
  return next.toISOString().slice(0, 10);
}

function toGoogleCalendarEvent(event: CalendarEvent) {
  const startDate = event.start_time.slice(0, 10);
  const endDate = event.end_time.slice(0, 10);

  return {
    summary: event.title,
    description: event.description || "",
    start: event.all_day
      ? { date: startDate }
      : { dateTime: event.start_time },
    end: event.all_day
      ? { date: addDay(endDate) }
      : { dateTime: event.end_time },
    extendedProperties: {
      private: {
        mindavio_event_id: event.id,
        team_id: event.team_id || "",
        user_id: event.user_id,
      },
    },
  };
}

async function ensureGoogleCalendarEventSynced(
  admin: ReturnType<typeof createAdminClient>,
  connection: IntegrationConnection & { secret_encrypted: string },
  event: CalendarEvent
) {
  const config = parseConnectionConfig(connection.config);
  if (!config.calendarId) {
    throw new Error("Google Calendar integration has no selected calendar");
  }

  const { data: existingLink } = await admin
    .from("google_calendar_event_links")
    .select("id, connection_id, event_id, user_id, team_id, google_calendar_id, google_event_id")
    .eq("connection_id", connection.id)
    .eq("event_id", event.id)
    .maybeSingle();

  const payload = toGoogleCalendarEvent(event);
  let googleEventId = existingLink?.google_event_id || null;
  let googleCalendarId = existingLink?.google_calendar_id || config.calendarId;

  if (existingLink && existingLink.google_calendar_id !== config.calendarId) {
    try {
      await googleCalendarFetch(admin, connection, {
        path: `/calendars/${encodeURIComponent(existingLink.google_calendar_id)}/events/${encodeURIComponent(existingLink.google_event_id)}?sendUpdates=none`,
        method: "DELETE",
      });
    } catch {
      // Best effort cleanup when the target calendar changes.
    }
    googleEventId = null;
    googleCalendarId = config.calendarId;
  }

  if (!googleEventId) {
    const created = await googleCalendarFetch<{ id: string }>(admin, connection, {
      path: `/calendars/${encodeURIComponent(config.calendarId)}/events?sendUpdates=none`,
      method: "POST",
      body: payload,
    });

    googleEventId = created.id;
  } else {
    await googleCalendarFetch(admin, connection, {
      path: `/calendars/${encodeURIComponent(googleCalendarId)}/events/${encodeURIComponent(googleEventId)}?sendUpdates=none`,
      method: "PUT",
      body: payload,
    });
  }

  await admin.from("google_calendar_event_links").upsert({
    connection_id: connection.id,
    event_id: event.id,
    user_id: connection.user_id,
    team_id: connection.team_id,
    google_calendar_id: googleCalendarId,
    google_event_id: googleEventId,
  });
}

async function deleteGoogleCalendarEventLink(
  admin: ReturnType<typeof createAdminClient>,
  connection: IntegrationConnection & { secret_encrypted: string },
  eventId: string
) {
  const { data: existingLink } = await admin
    .from("google_calendar_event_links")
    .select("id, google_calendar_id, google_event_id")
    .eq("connection_id", connection.id)
    .eq("event_id", eventId)
    .maybeSingle();

  if (!existingLink) {
    return;
  }

  try {
    await googleCalendarFetch(admin, connection, {
      path: `/calendars/${encodeURIComponent(existingLink.google_calendar_id)}/events/${encodeURIComponent(existingLink.google_event_id)}?sendUpdates=none`,
      method: "DELETE",
    });
  } catch {
    // Best effort delete. We still clear the local link below.
  }

  await admin
    .from("google_calendar_event_links")
    .delete()
    .eq("id", existingLink.id);
}

export async function deliverGoogleCalendarIntegrationEvent(
  admin: ReturnType<typeof createAdminClient>,
  connection: IntegrationConnection & { secret_encrypted: string },
  envelope: WebhookEventEnvelope
) {
  if (!envelope.type.startsWith("event.")) {
    return;
  }

  const eventRecord =
    envelope.type === "event.deleted"
      ? null
      : ((envelope.data.event as CalendarEvent | null | undefined) || null);

  if (envelope.type === "event.deleted") {
    const eventId =
      typeof envelope.data.event_id === "string"
        ? envelope.data.event_id
        : typeof (envelope.data.event as { id?: string } | null)?.id === "string"
          ? (envelope.data.event as { id: string }).id
          : null;

    if (eventId) {
      await deleteGoogleCalendarEventLink(admin, connection, eventId);
    }
    return;
  }

  if (!eventRecord) {
    return;
  }

  await ensureGoogleCalendarEventSynced(admin, connection, eventRecord);
}

export async function syncAllWorkspaceEventsToGoogleCalendar(
  admin: ReturnType<typeof createAdminClient>,
  connection: IntegrationConnection & { secret_encrypted: string }
) {
  let query = admin
    .from("events")
    .select("id, user_id, team_id, title, description, start_time, end_time, all_day, color, created_at, updated_at")
    .order("start_time", { ascending: true });

  query = connection.team_id
    ? query.eq("team_id", connection.team_id)
    : query.eq("user_id", connection.user_id).is("team_id", null);

  const { data: events, error } = await query;
  if (error) throw error;

  const typedEvents = (events || []) as CalendarEvent[];
  for (const event of typedEvents) {
    await ensureGoogleCalendarEventSynced(admin, connection, event);
  }

  return { synced: typedEvents.length };
}

export function extractConnectedEmailFromCalendars(calendars: GoogleCalendarCalendar[]) {
  const primary = calendars.find((calendar) => calendar.primary);
  const candidate = primary?.summary || calendars[0]?.summary || null;
  return isLikelyEmail(candidate) ? candidate : null;
}

export function parseGoogleCalendarConfig(config: Record<string, unknown> | null | undefined) {
  return parseConnectionConfig(config);
}

