// Google Calendar bidirectional sync — graceful-disabled if env vars empty.
// OAuth 2.0 authorization-code flow (with PKCE recommended pra mobile mas web client tá ok).
//
// Pra ativar em produção:
//   GOOGLE_CLIENT_ID=...apps.googleusercontent.com
//   GOOGLE_CLIENT_SECRET=...
//   GOOGLE_REDIRECT_URI=https://shivao.pontualtech.work/api/google/callback
//
// Sem essas env vars, todos os endpoints retornam 503 com mensagem clara.

import * as db from './db.js';

const CLIENT_ID = process.env.GOOGLE_CLIENT_ID || '';
const CLIENT_SECRET = process.env.GOOGLE_CLIENT_SECRET || '';
const REDIRECT_URI = process.env.GOOGLE_REDIRECT_URI || '';

const SCOPES = [
  'https://www.googleapis.com/auth/calendar.events',
  'https://www.googleapis.com/auth/userinfo.email',
];

export function isEnabled() {
  return !!(CLIENT_ID && CLIENT_SECRET && REDIRECT_URI);
}

export function disabledResponse(res) {
  return res.status(503).json({
    error: 'google_calendar_disabled',
    detail: 'Backend não configurado. Falta GOOGLE_CLIENT_ID/SECRET/REDIRECT_URI no servidor.',
  });
}

// Inicia o OAuth: gera URL de autorização do Google e devolve pra cliente
export function buildAuthUrl(userId, returnTo = '/') {
  const state = Buffer.from(JSON.stringify({ uid: userId, rt: returnTo, n: Math.random().toString(36).slice(2) })).toString('base64url');
  const u = new URL('https://accounts.google.com/o/oauth2/v2/auth');
  u.searchParams.set('client_id', CLIENT_ID);
  u.searchParams.set('redirect_uri', REDIRECT_URI);
  u.searchParams.set('response_type', 'code');
  u.searchParams.set('scope', SCOPES.join(' '));
  u.searchParams.set('access_type', 'offline');
  u.searchParams.set('prompt', 'consent');
  u.searchParams.set('state', state);
  return u.toString();
}

// Troca authorization code por tokens
export async function exchangeCodeForTokens(code) {
  const body = new URLSearchParams({
    code,
    client_id: CLIENT_ID,
    client_secret: CLIENT_SECRET,
    redirect_uri: REDIRECT_URI,
    grant_type: 'authorization_code',
  });
  const r = await fetch('https://oauth2.googleapis.com/token', {
    method: 'POST',
    headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
    body: body.toString(),
  });
  if (!r.ok) {
    const t = await r.text();
    throw new Error(`Google token exchange failed (${r.status}): ${t.slice(0, 200)}`);
  }
  return r.json(); // { access_token, refresh_token, expires_in, scope, token_type, id_token }
}

export async function refreshAccessToken(refreshToken) {
  const body = new URLSearchParams({
    refresh_token: refreshToken,
    client_id: CLIENT_ID,
    client_secret: CLIENT_SECRET,
    grant_type: 'refresh_token',
  });
  const r = await fetch('https://oauth2.googleapis.com/token', {
    method: 'POST',
    headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
    body: body.toString(),
  });
  if (!r.ok) {
    const t = await r.text();
    throw new Error(`Google token refresh failed (${r.status}): ${t.slice(0, 200)}`);
  }
  return r.json();
}

export async function getUserInfo(accessToken) {
  const r = await fetch('https://www.googleapis.com/oauth2/v2/userinfo', {
    headers: { Authorization: `Bearer ${accessToken}` },
  });
  if (!r.ok) throw new Error(`userinfo failed (${r.status})`);
  return r.json();
}

// Wrapper que renova token automaticamente se expirado (401)
export async function withFreshToken(userId, fn) {
  const conn = db.getGoogleConnection(userId);
  if (!conn) throw new Error('not_connected');
  let accessToken = conn.access_token;
  // Se expirou (com 60s de margem), renova proativamente
  if (conn.expires_at && conn.expires_at < Date.now() + 60000) {
    const fresh = await refreshAccessToken(conn.refresh_token);
    accessToken = fresh.access_token;
    db.saveGoogleConnection(userId, {
      ...conn,
      access_token: accessToken,
      expires_at: Date.now() + (fresh.expires_in * 1000),
    });
  }
  try {
    return await fn(accessToken);
  } catch (e) {
    if (String(e.message).includes('401')) {
      const fresh = await refreshAccessToken(conn.refresh_token);
      db.saveGoogleConnection(userId, {
        ...conn,
        access_token: fresh.access_token,
        expires_at: Date.now() + (fresh.expires_in * 1000),
      });
      return fn(fresh.access_token);
    }
    throw e;
  }
}

// Cria/atualiza um evento no Google Calendar a partir de uma pendência
// pending: { id, title, notes?, dueDate?, completed?, googleEventId? }
export async function upsertEventForPending(userId, pending, calendarId = 'primary') {
  return withFreshToken(userId, async (accessToken) => {
    const event = pendingToEvent(pending);
    let url, method;
    if (pending.googleEventId) {
      url = `https://www.googleapis.com/calendar/v3/calendars/${encodeURIComponent(calendarId)}/events/${encodeURIComponent(pending.googleEventId)}`;
      method = 'PATCH';
    } else {
      url = `https://www.googleapis.com/calendar/v3/calendars/${encodeURIComponent(calendarId)}/events`;
      method = 'POST';
    }
    const r = await fetch(url, {
      method,
      headers: { Authorization: `Bearer ${accessToken}`, 'Content-Type': 'application/json' },
      body: JSON.stringify(event),
    });
    if (!r.ok) {
      const t = await r.text();
      throw new Error(`Google event ${method} failed (${r.status}): ${t.slice(0, 200)}`);
    }
    return r.json();
  });
}

export async function deleteEvent(userId, eventId, calendarId = 'primary') {
  return withFreshToken(userId, async (accessToken) => {
    const r = await fetch(`https://www.googleapis.com/calendar/v3/calendars/${encodeURIComponent(calendarId)}/events/${encodeURIComponent(eventId)}`, {
      method: 'DELETE',
      headers: { Authorization: `Bearer ${accessToken}` },
    });
    if (r.status !== 204 && r.status !== 410 && r.status !== 404) {
      const t = await r.text();
      throw new Error(`Google delete failed (${r.status}): ${t.slice(0, 200)}`);
    }
    return { ok: true };
  });
}

// Lista eventos modificados desde um momento (pra pull periódico)
export async function listChangedEvents(userId, syncToken, calendarId = 'primary') {
  return withFreshToken(userId, async (accessToken) => {
    const u = new URL(`https://www.googleapis.com/calendar/v3/calendars/${encodeURIComponent(calendarId)}/events`);
    if (syncToken) u.searchParams.set('syncToken', syncToken);
    else {
      // Primeira sync: pega eventos do "Shivao" criados nos últimos 90 dias
      u.searchParams.set('q', 'Shivão');
      u.searchParams.set('timeMin', new Date(Date.now() - 90 * 86400 * 1000).toISOString());
    }
    const r = await fetch(u.toString(), { headers: { Authorization: `Bearer ${accessToken}` } });
    if (!r.ok) {
      const t = await r.text();
      throw new Error(`Google list failed (${r.status}): ${t.slice(0, 200)}`);
    }
    return r.json(); // { items: [...], nextSyncToken }
  });
}

function pendingToEvent(p) {
  const ev = {
    summary: `⚓ ${p.title || 'Pendência'}`,
    description: (p.notes || '') + '\n\n— do Diário do Shivão',
    extendedProperties: {
      private: {
        shivaoPendingId: String(p.id),
        shivaoCompleted: p.completed ? '1' : '0',
      },
    },
  };
  if (p.completed) {
    ev.summary = '✅ ' + ev.summary;
  }
  if (p.dueDate) {
    // dueDate formato YYYY-MM-DD vira evento all-day
    ev.start = { date: p.dueDate };
    const d = new Date(p.dueDate + 'T00:00:00Z');
    d.setUTCDate(d.getUTCDate() + 1);
    ev.end = { date: d.toISOString().slice(0, 10) };
  } else {
    // Sem data: defaulta pra hoje all-day
    const today = new Date().toISOString().slice(0, 10);
    ev.start = { date: today };
    const d = new Date(today + 'T00:00:00Z');
    d.setUTCDate(d.getUTCDate() + 1);
    ev.end = { date: d.toISOString().slice(0, 10) };
  }
  return ev;
}

export function eventToPending(ev) {
  // Reconstrói pending parcial a partir de evento Google. Cliente faz merge.
  const p = {
    googleEventId: ev.id,
    googleUpdated: ev.updated,
  };
  // Tira emoji do começo do summary se houver
  let title = ev.summary || '';
  title = title.replace(/^(✅\s*)?⚓\s*/, '').trim();
  p.title = title;
  if (ev.description) {
    p.notes = ev.description.replace(/\n\n— do Diário do Shivão$/, '');
  }
  if (ev.start?.date) p.dueDate = ev.start.date;
  if (ev.extendedProperties?.private?.shivaoPendingId) {
    p.id = ev.extendedProperties.private.shivaoPendingId;
  }
  if (ev.extendedProperties?.private?.shivaoCompleted === '1') p.completed = true;
  if (ev.status === 'cancelled') p.deleted = true;
  return p;
}
