import { connectDB } from "@/lib/db";
import { Calendar } from "@/models/Calendar";
import { getSiteUrl } from "@/lib/seo";

// Einseitige Push-Sync (unsere Termine → Google), keine Rückrichtung — siehe
// Absprache mit dem Nutzer (06.08.2026). Bewusst per fetch gegen die REST-API
// statt der googleapis-Bibliothek, um keine zusätzliche schwere Abhängigkeit
// für die paar gebrauchten Endpunkte einzuführen.
const SCOPES = "https://www.googleapis.com/auth/calendar.events";

export function getGoogleRedirectUri() {
  return `${getSiteUrl()}/api/auth/google/callback`;
}

export function isGoogleConfigured() {
  return Boolean(process.env.GOOGLE_CLIENT_ID && process.env.GOOGLE_CLIENT_SECRET);
}

export function buildGoogleAuthUrl(state: string) {
  const params = new URLSearchParams({
    client_id: process.env.GOOGLE_CLIENT_ID!,
    redirect_uri: getGoogleRedirectUri(),
    response_type: "code",
    scope: SCOPES,
    access_type: "offline",
    prompt: "consent",
    state,
  });
  return `https://accounts.google.com/o/oauth2/v2/auth?${params.toString()}`;
}

type TokenResponse = {
  access_token: string;
  refresh_token?: string;
  expires_in: number;
  error?: string;
  error_description?: string;
};

export async function exchangeCodeForTokens(code: string): Promise<TokenResponse> {
  const res = 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!,
      code,
      redirect_uri: getGoogleRedirectUri(),
      grant_type: "authorization_code",
    }),
  });
  return res.json();
}

async function refreshAccessToken(refreshToken: string): Promise<TokenResponse> {
  const res = 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",
    }),
  });
  return res.json();
}

// Liefert einen gültigen Access-Token für den gegebenen Kalender, erneuert
// ihn bei Bedarf über den Refresh-Token und speichert das Ergebnis. Liefert
// null, wenn der Kalender nicht (mehr) mit Google verbunden ist.
export async function ensureValidAccessToken(calendarId: string): Promise<string | null> {
  await connectDB();
  const cal = await Calendar.findById(calendarId);
  if (!cal || !cal.googleRefreshToken) return null;

  const stillValid = cal.googleAccessToken && cal.googleTokenExpiry && cal.googleTokenExpiry.getTime() - 60_000 > Date.now();
  if (stillValid) return cal.googleAccessToken;

  const tokens = await refreshAccessToken(cal.googleRefreshToken);
  if (!tokens.access_token) {
    console.error("Google-Token-Erneuerung fehlgeschlagen:", tokens.error, tokens.error_description);
    return null;
  }

  cal.googleAccessToken = tokens.access_token;
  cal.googleTokenExpiry = new Date(Date.now() + tokens.expires_in * 1000);
  await cal.save();
  return tokens.access_token;
}

type GoogleEventInput = {
  title: string;
  description?: string;
  location?: string;
  start: Date;
  end?: Date | null;
  allDay?: boolean;
};

function toGoogleDateTime(d: Date, allDay?: boolean) {
  return allDay ? { date: d.toISOString().slice(0, 10) } : { dateTime: d.toISOString() };
}

// Legt das Event bei Google neu an (kein googleEventId) oder aktualisiert es
// (vorhandene googleEventId) — Fehler werden geloggt, aber nie geworfen, da
// eine kaputte Google-Verbindung nie das eigentliche Speichern bei uns
// verhindern darf.
export async function pushEventToGoogle(
  calendarId: string,
  existingGoogleEventId: string,
  event: GoogleEventInput
): Promise<string | null> {
  try {
    const accessToken = await ensureValidAccessToken(calendarId);
    if (!accessToken) return null;

    await connectDB();
    const cal = await Calendar.findById(calendarId).lean();
    if (!cal) return null;

    // Googles end.date bei ganztägigen Terminen ist exklusiv (muss der Tag
    // NACH dem letzten Tag sein) — ohne explizites Ende sonst ein Termin mit
    // Länge null/negativ, den Google ablehnt oder falsch darstellt.
    const ONE_DAY_MS = 24 * 60 * 60 * 1000;
    const end =
      event.end ?? new Date(event.start.getTime() + (event.allDay ? ONE_DAY_MS : 60 * 60 * 1000));
    const body = {
      summary: event.title,
      description: event.description || "",
      // Eigenes Feld statt nur in der Beschreibung — nur so erkennt Google
      // die Adresse als Ort (Kartenvorschau, Wegbeschreibung, Suche).
      location: event.location || "",
      start: toGoogleDateTime(event.start, event.allDay),
      end: toGoogleDateTime(end, event.allDay),
    };

    const base = `https://www.googleapis.com/calendar/v3/calendars/${encodeURIComponent(cal.googleCalendarId)}/events`;
    const url = existingGoogleEventId ? `${base}/${existingGoogleEventId}` : base;
    const res = await fetch(url, {
      method: existingGoogleEventId ? "PUT" : "POST",
      headers: { Authorization: `Bearer ${accessToken}`, "Content-Type": "application/json" },
      body: JSON.stringify(body),
    });
    if (!res.ok) {
      console.error("Google-Calendar-Push fehlgeschlagen:", res.status, await res.text());
      return existingGoogleEventId || null;
    }
    const data = await res.json();
    return data.id ?? existingGoogleEventId ?? null;
  } catch (err) {
    console.error("Google-Calendar-Push fehlgeschlagen:", err);
    return existingGoogleEventId || null;
  }
}

export type GooglePulledEvent = { start: Date; end: Date | null; allDay: boolean; cancelled: boolean };

// Holt den aktuellen Stand eines einzelnen Google-Events — für den
// Rück-Sync (Änderungen, die direkt in Google gemacht wurden, zurück in die
// Seite übernehmen). Bewusst nur Zeiten lesen, keine Titel/Beschreibung
// zurückschreiben, um nicht versehentlich echte Auftrags-/Kundendaten mit
// einem in Google frei editierbaren Text zu überschreiben.
export async function fetchGoogleEvent(calendarId: string, googleEventId: string): Promise<GooglePulledEvent | null> {
  try {
    const accessToken = await ensureValidAccessToken(calendarId);
    if (!accessToken) return null;

    await connectDB();
    const cal = await Calendar.findById(calendarId).lean();
    if (!cal) return null;

    const url = `https://www.googleapis.com/calendar/v3/calendars/${encodeURIComponent(cal.googleCalendarId)}/events/${googleEventId}`;
    const res = await fetch(url, { headers: { Authorization: `Bearer ${accessToken}` } });
    if (res.status === 404 || res.status === 410) return null;
    if (!res.ok) {
      console.error("Google-Calendar-Abruf fehlgeschlagen:", res.status, await res.text());
      return null;
    }
    const data = await res.json();
    if (data.status === "cancelled") return { start: new Date(0), end: null, allDay: false, cancelled: true };

    const allDay = Boolean(data.start?.date);
    const start = new Date(data.start?.dateTime || data.start?.date);
    const end = data.end?.dateTime || data.end?.date ? new Date(data.end.dateTime || data.end.date) : null;
    return { start, end, allDay, cancelled: false };
  } catch (err) {
    console.error("Google-Calendar-Abruf fehlgeschlagen:", err);
    return null;
  }
}

export async function deleteEventFromGoogle(calendarId: string, googleEventId: string): Promise<void> {
  if (!googleEventId) return;
  try {
    const accessToken = await ensureValidAccessToken(calendarId);
    if (!accessToken) return;

    await connectDB();
    const cal = await Calendar.findById(calendarId).lean();
    if (!cal) return;

    const url = `https://www.googleapis.com/calendar/v3/calendars/${encodeURIComponent(cal.googleCalendarId)}/events/${googleEventId}`;
    const res = await fetch(url, { method: "DELETE", headers: { Authorization: `Bearer ${accessToken}` } });
    if (!res.ok && res.status !== 404 && res.status !== 410) {
      console.error("Google-Calendar-Löschen fehlgeschlagen:", res.status, await res.text());
    }
  } catch (err) {
    console.error("Google-Calendar-Löschen fehlgeschlagen:", err);
  }
}
