// Fetchers Search Console + GA4 pour le dashboard du portail client.
//
// Toutes les fonctions gerent leurs erreurs et renvoient null en cas d'echec
// (pas d'API key, propriete non connectee, quota depasse, etc.). Le composant
// appelant decide d'afficher un EmptyState contextuel.

import "server-only";
import { getSearchConsoleClient, getAnalyticsDataClient } from "@/lib/google";

// === Periode par defaut ===
// 28 derniers jours, standard GSC.
export const DEFAULT_WINDOW_DAYS = 28;

function isoDate(d: Date): string {
  return d.toISOString().slice(0, 10);
}

function periodForDays(days: number) {
  const end = new Date();
  end.setUTCHours(0, 0, 0, 0);
  end.setUTCDate(end.getUTCDate() - 1); // GSC : donnees dispo a J-1
  const start = new Date(end);
  start.setUTCDate(start.getUTCDate() - (days - 1));
  // Periode precedente de meme longueur, pour les deltas.
  const prevEnd = new Date(start);
  prevEnd.setUTCDate(prevEnd.getUTCDate() - 1);
  const prevStart = new Date(prevEnd);
  prevStart.setUTCDate(prevStart.getUTCDate() - (days - 1));
  return {
    start: isoDate(start),
    end: isoDate(end),
    prevStart: isoDate(prevStart),
    prevEnd: isoDate(prevEnd),
  };
}

function pctDelta(current: number, previous: number): number {
  if (previous === 0) return current === 0 ? 0 : 100;
  return ((current - previous) / previous) * 100;
}

// === Search Console ===

export type GscMetrics = {
  clicks: number;
  impressions: number;
  ctr: number; // 0-1
  position: number;
  deltas: {
    clicks: number; // pourcentage
    impressions: number;
    ctr: number; // points (pas pourcentage)
    position: number; // delta en points
  };
  dailySeries: Array<{ date: string; clicks: number; impressions: number }>;
  topQueries: Array<{ query: string; clicks: number; impressions: number; ctr: number; position: number }>;
  topPages: Array<{ page: string; clicks: number; impressions: number; ctr: number; position: number }>;
};

export async function fetchSearchConsoleMetrics(
  siteUrl: string,
  days: number = DEFAULT_WINDOW_DAYS,
): Promise<GscMetrics | null> {
  const client = await getSearchConsoleClient();
  if (!client) return null;

  const { start, end, prevStart, prevEnd } = periodForDays(days);

  try {
    // 4 requetes parallel : current totals, previous totals, daily, top queries+pages.
    const [current, previous, daily, queries, pages] = await Promise.all([
      client.searchanalytics.query({
        siteUrl,
        requestBody: { startDate: start, endDate: end, dimensions: [], rowLimit: 1 },
      }),
      client.searchanalytics.query({
        siteUrl,
        requestBody: { startDate: prevStart, endDate: prevEnd, dimensions: [], rowLimit: 1 },
      }),
      client.searchanalytics.query({
        siteUrl,
        requestBody: { startDate: start, endDate: end, dimensions: ["date"], rowLimit: days + 5 },
      }),
      client.searchanalytics.query({
        siteUrl,
        requestBody: { startDate: start, endDate: end, dimensions: ["query"], rowLimit: 10 },
      }),
      client.searchanalytics.query({
        siteUrl,
        requestBody: { startDate: start, endDate: end, dimensions: ["page"], rowLimit: 10 },
      }),
    ]);

    const cur = current.data.rows?.[0] ?? { clicks: 0, impressions: 0, ctr: 0, position: 0 };
    const prev = previous.data.rows?.[0] ?? { clicks: 0, impressions: 0, ctr: 0, position: 0 };

    return {
      clicks: cur.clicks ?? 0,
      impressions: cur.impressions ?? 0,
      ctr: cur.ctr ?? 0,
      position: cur.position ?? 0,
      deltas: {
        clicks: pctDelta(cur.clicks ?? 0, prev.clicks ?? 0),
        impressions: pctDelta(cur.impressions ?? 0, prev.impressions ?? 0),
        ctr: ((cur.ctr ?? 0) - (prev.ctr ?? 0)) * 100, // en points
        position: (cur.position ?? 0) - (prev.position ?? 0),
      },
      dailySeries: (daily.data.rows ?? []).map((r) => ({
        date: String(r.keys?.[0] ?? ""),
        clicks: r.clicks ?? 0,
        impressions: r.impressions ?? 0,
      })),
      topQueries: (queries.data.rows ?? []).map((r) => ({
        query: String(r.keys?.[0] ?? ""),
        clicks: r.clicks ?? 0,
        impressions: r.impressions ?? 0,
        ctr: r.ctr ?? 0,
        position: r.position ?? 0,
      })),
      topPages: (pages.data.rows ?? []).map((r) => ({
        page: String(r.keys?.[0] ?? ""),
        clicks: r.clicks ?? 0,
        impressions: r.impressions ?? 0,
        ctr: r.ctr ?? 0,
        position: r.position ?? 0,
      })),
    };
  } catch (err) {
    console.error("[gsc] fetch failed", err);
    return null;
  }
}

// === Google Analytics 4 ===

export type Ga4Metrics = {
  sessions: number;
  users: number;
  avgDurationSec: number;
  bounceRate: number; // 0-1
  deltas: {
    sessions: number;
    users: number;
    avgDurationSec: number;
    bounceRate: number; // points
  };
  dailySeries: Array<{ date: string; sessions: number }>;
  trafficSources: Array<{ source: string; sessions: number; share: number }>;
  topPages: Array<{ path: string; title: string; sessions: number; avgDurationSec: number }>;
};

export async function fetchAnalyticsMetrics(
  propertyId: string,
  days: number = DEFAULT_WINDOW_DAYS,
): Promise<Ga4Metrics | null> {
  const client = await getAnalyticsDataClient();
  if (!client) return null;

  const property = `properties/${propertyId}`;
  const { start, end, prevStart, prevEnd } = periodForDays(days);

  try {
    const [totalsResp, dailyResp, sourcesResp, topPagesResp] = await Promise.all([
      // Totals current + previous via runReport en 2 dateRanges.
      client.properties.runReport({
        property,
        requestBody: {
          dateRanges: [
            { startDate: start, endDate: end },
            { startDate: prevStart, endDate: prevEnd },
          ],
          metrics: [
            { name: "sessions" },
            { name: "totalUsers" },
            { name: "averageSessionDuration" },
            { name: "bounceRate" },
          ],
        },
      }),
      client.properties.runReport({
        property,
        requestBody: {
          dateRanges: [{ startDate: start, endDate: end }],
          dimensions: [{ name: "date" }],
          metrics: [{ name: "sessions" }],
          orderBys: [{ dimension: { dimensionName: "date" } }],
          limit: String(days + 5),
        },
      }),
      client.properties.runReport({
        property,
        requestBody: {
          dateRanges: [{ startDate: start, endDate: end }],
          dimensions: [{ name: "sessionDefaultChannelGroup" }],
          metrics: [{ name: "sessions" }],
          orderBys: [{ metric: { metricName: "sessions" }, desc: true }],
          limit: "10",
        },
      }),
      client.properties.runReport({
        property,
        requestBody: {
          dateRanges: [{ startDate: start, endDate: end }],
          dimensions: [{ name: "pagePath" }, { name: "pageTitle" }],
          metrics: [{ name: "sessions" }, { name: "averageSessionDuration" }],
          orderBys: [{ metric: { metricName: "sessions" }, desc: true }],
          limit: "10",
        },
      }),
    ]);

    // totalsResp a 2 rows : index 0 = current, index 1 = previous.
    const rows = totalsResp.data.rows ?? [];
    const curRow = rows[0]?.metricValues ?? [];
    const prevRow = rows[1]?.metricValues ?? [];
    const num = (v: string | null | undefined) => (v ? Number(v) : 0);

    const sessions = num(curRow[0]?.value);
    const users = num(curRow[1]?.value);
    const avgDurationSec = num(curRow[2]?.value);
    const bounceRate = num(curRow[3]?.value);
    const prevSessions = num(prevRow[0]?.value);
    const prevUsers = num(prevRow[1]?.value);
    const prevAvgDur = num(prevRow[2]?.value);
    const prevBounce = num(prevRow[3]?.value);

    const totalSessionsInSources = (sourcesResp.data.rows ?? []).reduce(
      (acc, r) => acc + num(r.metricValues?.[0]?.value),
      0,
    );

    return {
      sessions,
      users,
      avgDurationSec,
      bounceRate,
      deltas: {
        sessions: pctDelta(sessions, prevSessions),
        users: pctDelta(users, prevUsers),
        avgDurationSec: pctDelta(avgDurationSec, prevAvgDur),
        bounceRate: (bounceRate - prevBounce) * 100,
      },
      dailySeries: (dailyResp.data.rows ?? []).map((r) => ({
        date: gaDateToIso(String(r.dimensionValues?.[0]?.value ?? "")),
        sessions: num(r.metricValues?.[0]?.value),
      })),
      trafficSources: (sourcesResp.data.rows ?? []).map((r) => {
        const s = num(r.metricValues?.[0]?.value);
        return {
          source: String(r.dimensionValues?.[0]?.value ?? "Inconnu"),
          sessions: s,
          share: totalSessionsInSources > 0 ? s / totalSessionsInSources : 0,
        };
      }),
      topPages: (topPagesResp.data.rows ?? []).map((r) => ({
        path: String(r.dimensionValues?.[0]?.value ?? "/"),
        title: String(r.dimensionValues?.[1]?.value ?? ""),
        sessions: num(r.metricValues?.[0]?.value),
        avgDurationSec: num(r.metricValues?.[1]?.value),
      })),
    };
  } catch (err) {
    console.error("[ga4] fetch failed", err);
    return null;
  }
}

// GA4 renvoie les dates en YYYYMMDD compact. On normalise en ISO.
function gaDateToIso(s: string): string {
  if (s.length === 8) return `${s.slice(0, 4)}-${s.slice(4, 6)}-${s.slice(6, 8)}`;
  return s;
}

// Merge des series journalieres GSC + GA4 par date pour le graphique combine.
// On preserve aussi les impressions de GSC pour pouvoir les co-afficher.
export function mergeDailySeries(
  gsc: GscMetrics["dailySeries"] | null,
  ga4: Ga4Metrics["dailySeries"] | null,
): Array<{ date: string; clicks: number; impressions: number; sessions: number }> {
  const map = new Map<string, { clicks: number; impressions: number; sessions: number }>();
  if (gsc) for (const r of gsc) map.set(r.date, { clicks: r.clicks, impressions: r.impressions, sessions: 0 });
  if (ga4)
    for (const r of ga4) {
      const existing = map.get(r.date) ?? { clicks: 0, impressions: 0, sessions: 0 };
      existing.sessions = r.sessions;
      map.set(r.date, existing);
    }
  return Array.from(map.entries())
    .sort(([a], [b]) => a.localeCompare(b))
    .map(([date, v]) => ({ date, ...v }));
}
