// Helpers du flow OAuth Google.
// Pour l'agence solo : un seul admin Novelia connecte son compte Google,
// le refresh_token est stocke en DB et utilise pour interroger GSC + GA4
// sur les sites ou il a un acces personnel.

import "server-only";
import { OAuth2Client } from "google-auth-library";
import { searchconsole_v1 } from "@googleapis/searchconsole";
import { analyticsdata_v1beta } from "@googleapis/analyticsdata";
import { eq } from "drizzle-orm";
import { db } from "@/lib/db";
import { googleOauthToken } from "@/lib/db/schema";

const SINGLETON_ID = "default";

// Scopes minimums pour lire GSC + GA4 + obtenir l'email du compte connecte.
export const OAUTH_SCOPES = [
  "https://www.googleapis.com/auth/webmasters.readonly",
  "https://www.googleapis.com/auth/analytics.readonly",
  "openid",
  "email",
];

function getRedirectUri(): string {
  const baseUrl = process.env.BETTER_AUTH_URL?.trim();
  if (!baseUrl) throw new Error("BETTER_AUTH_URL manquante");
  return `${baseUrl.replace(/\/$/, "")}/api/google/oauth/callback`;
}

// Cree un OAuth2Client sans tokens, utilise pour generer l'URL de consentement
// et pour echanger un code contre des tokens.
function createBaseOAuthClient(): OAuth2Client {
  const clientId = process.env.GOOGLE_OAUTH_CLIENT_ID?.trim();
  const clientSecret = process.env.GOOGLE_OAUTH_CLIENT_SECRET?.trim();
  if (!clientId || !clientSecret) {
    throw new Error(
      "GOOGLE_OAUTH_CLIENT_ID / GOOGLE_OAUTH_CLIENT_SECRET non configures.",
    );
  }
  return new OAuth2Client({
    clientId,
    clientSecret,
    redirectUri: getRedirectUri(),
  });
}

// URL a presenter a l'admin pour qu'il accorde les acces.
// `state` est un nonce signe par Better Auth (CSRF protection).
export function buildConsentUrl(state: string): string {
  const client = createBaseOAuthClient();
  return client.generateAuthUrl({
    access_type: "offline", // requis pour obtenir un refresh_token
    prompt: "consent", // force le refresh_token a chaque fois (sinon Google peut l'omettre)
    scope: OAUTH_SCOPES,
    state,
    include_granted_scopes: true,
  });
}

// Echange le code OAuth contre un access + refresh token, recupere l'email du
// compte connecte via l'endpoint userinfo, et persiste en DB.
export async function exchangeCodeAndPersist(
  code: string,
  userId: string,
): Promise<{ email: string | null }> {
  const client = createBaseOAuthClient();
  const { tokens } = await client.getToken(code);

  if (!tokens.refresh_token) {
    throw new Error(
      "Google n'a pas retourne de refresh_token. Revoque l'acces existant sur https://myaccount.google.com/permissions et reessaie.",
    );
  }

  // Recupere l'email du compte connecte.
  client.setCredentials(tokens);
  let connectedEmail: string | null = null;
  try {
    const userinfo = await client.request<{ email?: string }>({
      url: "https://www.googleapis.com/oauth2/v2/userinfo",
    });
    connectedEmail = userinfo.data.email ?? null;
  } catch {
    // Tant pis pour l'email, le token reste valide.
  }

  const expiresAt = tokens.expiry_date ? new Date(tokens.expiry_date) : null;

  // Upsert singleton.
  const existing = await db
    .select({ id: googleOauthToken.id })
    .from(googleOauthToken)
    .where(eq(googleOauthToken.id, SINGLETON_ID))
    .limit(1);

  if (existing.length === 0) {
    await db.insert(googleOauthToken).values({
      id: SINGLETON_ID,
      refreshToken: tokens.refresh_token,
      accessToken: tokens.access_token ?? null,
      scope: tokens.scope ?? OAUTH_SCOPES.join(" "),
      tokenType: tokens.token_type ?? "Bearer",
      expiresAt,
      connectedEmail,
      connectedByUserId: userId,
    });
  } else {
    await db
      .update(googleOauthToken)
      .set({
        refreshToken: tokens.refresh_token,
        accessToken: tokens.access_token ?? null,
        scope: tokens.scope ?? OAUTH_SCOPES.join(" "),
        tokenType: tokens.token_type ?? "Bearer",
        expiresAt,
        connectedEmail,
        connectedByUserId: userId,
        updatedAt: new Date(),
      })
      .where(eq(googleOauthToken.id, SINGLETON_ID));
  }

  return { email: connectedEmail };
}

// Statut affiche dans l'UI.
export async function getOauthStatus(): Promise<{
  connected: boolean;
  email: string | null;
  connectedAt: Date | null;
}> {
  const rows = await db
    .select({
      email: googleOauthToken.connectedEmail,
      connectedAt: googleOauthToken.connectedAt,
    })
    .from(googleOauthToken)
    .where(eq(googleOauthToken.id, SINGLETON_ID))
    .limit(1);

  if (rows.length === 0) {
    return { connected: false, email: null, connectedAt: null };
  }
  return {
    connected: true,
    email: rows[0].email,
    connectedAt: rows[0].connectedAt,
  };
}

// Renvoie un OAuth2Client deja credentialise avec le refresh_token de la DB.
// Le client se charge automatiquement de rafraichir l'access_token quand il
// expire. Renvoie null si pas connecte.
export async function getAuthorizedOAuthClient(): Promise<OAuth2Client | null> {
  const rows = await db
    .select()
    .from(googleOauthToken)
    .where(eq(googleOauthToken.id, SINGLETON_ID))
    .limit(1);

  if (rows.length === 0) return null;
  const row = rows[0];

  const client = createBaseOAuthClient();
  client.setCredentials({
    refresh_token: row.refreshToken,
    access_token: row.accessToken ?? undefined,
    expiry_date: row.expiresAt?.getTime() ?? undefined,
    scope: row.scope,
    token_type: row.tokenType,
  });

  // Quand google-auth-library rafraichit, on persiste le nouvel access_token.
  client.on("tokens", async (newTokens) => {
    try {
      await db
        .update(googleOauthToken)
        .set({
          accessToken: newTokens.access_token ?? row.accessToken,
          expiresAt: newTokens.expiry_date ? new Date(newTokens.expiry_date) : row.expiresAt,
          updatedAt: new Date(),
        })
        .where(eq(googleOauthToken.id, SINGLETON_ID));
    } catch (err) {
      console.error("[google-oauth] Persist refreshed token failed", err);
    }
  });

  return client;
}

// Clients APIs OAuth-based, prioritaires sur les clients SA.
export async function getOAuthSearchConsoleClient() {
  const auth = await getAuthorizedOAuthClient();
  if (!auth) return null;
  return new searchconsole_v1.Searchconsole({ auth });
}

export async function getOAuthAnalyticsDataClient() {
  const auth = await getAuthorizedOAuthClient();
  if (!auth) return null;
  return new analyticsdata_v1beta.Analyticsdata({ auth });
}

// Supprime le token (deconnexion). Optionnellement revoque-le cote Google.
export async function disconnectGoogleOAuth(): Promise<void> {
  const auth = await getAuthorizedOAuthClient();
  if (auth) {
    try {
      await auth.revokeCredentials();
    } catch {
      // Si la revocation echoue, on supprime quand meme cote DB.
    }
  }
  await db.delete(googleOauthToken).where(eq(googleOauthToken.id, SINGLETON_ID));
}
