/**
 * SET 19a — publish static site-wide stats for the Admin page.
 *
 * Writes web/public/api/site-stats.json (copied into dist via vite public/).
 *
 * Facets without a deterministic source are OMITTED (never estimated).
 * When nothing is available → { unavailable: true, generatedAt }.
 *
 * Optional env:
 *   CF_API_TOKEN + CF_ZONE_ID → Cloudflare GraphQL Analytics (hits, botHits)
 * Local telemetry file (if present):
 *   data/telemetry.json → downloads, runsExecuted, runsSucceeded, runsFailed
 */
import fs from 'node:fs';
import path from 'node:path';
import { fileURLToPath } from 'node:url';

const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
export const DEFAULT_OUT = path.join(ROOT, 'web', 'public', 'api', 'site-stats.json');
const TELEMETRY_PATH = path.join(ROOT, 'data', 'telemetry.json');

export type SiteStats = {
  generatedAt: string;
  unavailable?: true;
  hits?: number;
  botHits?: number;
  downloads?: number;
  runsExecuted?: number;
  runsSucceeded?: number;
  runsFailed?: number;
  provenance?: string[];
};

function readTelemetry(): Partial<SiteStats> | null {
  try {
    const raw = JSON.parse(fs.readFileSync(TELEMETRY_PATH, 'utf8')) as {
      totals?: {
        pluginDownloads?: number;
        runsExecuted?: number;
        runsSucceeded?: number;
        runsFailed?: number;
      };
    };
    const t = raw.totals;
    if (!t) return null;
    const out: Partial<SiteStats> = {};
    if (typeof t.pluginDownloads === 'number') out.downloads = t.pluginDownloads;
    if (typeof t.runsExecuted === 'number') out.runsExecuted = t.runsExecuted;
    if (typeof t.runsSucceeded === 'number') out.runsSucceeded = t.runsSucceeded;
    if (typeof t.runsFailed === 'number') out.runsFailed = t.runsFailed;
    return Object.keys(out).length ? out : null;
  } catch {
    return null;
  }
}

async function fetchCloudflareHits(
  token: string,
  zoneId: string,
): Promise<{ hits?: number; botHits?: number } | null> {
  const end = new Date();
  const start = new Date(end.getTime() - 30 * 86_400_000);
  const datetime_geq = start.toISOString().replace(/\.\d{3}Z$/, 'Z');
  const datetime_lt = end.toISOString().replace(/\.\d{3}Z$/, 'Z');

  const body = {
    query: `
      query($zoneTag: string) {
        viewer {
          zones(filter: { zoneTag: $zoneTag }) {
            all: httpRequestsAdaptiveGroups(
              limit: 1
              filter: {
                datetime_geq: "${datetime_geq}"
                datetime_lt: "${datetime_lt}"
                requestSource: "eyeball"
              }
            ) { count }
            bots: httpRequestsAdaptiveGroups(
              limit: 1
              filter: {
                datetime_geq: "${datetime_geq}"
                datetime_lt: "${datetime_lt}"
                requestSource: "eyeball"
                botScore_lt: 30
              }
            ) { count }
          }
        }
      }
    `,
    variables: { zoneTag: zoneId },
  };

  try {
    const res = await fetch('https://api.cloudflare.com/client/v4/graphql', {
      method: 'POST',
      headers: {
        Authorization: `Bearer ${token}`,
        'Content-Type': 'application/json',
      },
      body: JSON.stringify(body),
    });
    if (!res.ok) return null;
    const json = await res.json() as {
      data?: { viewer?: { zones?: { all?: { count: number }[]; bots?: { count: number }[] }[] } };
      errors?: unknown[];
    };
    if (json.errors?.length) return null;
    const zone = json.data?.viewer?.zones?.[0];
    if (!zone) return null;
    const out: { hits?: number; botHits?: number } = {};
    const hits = zone.all?.[0]?.count;
    const bots = zone.bots?.[0]?.count;
    if (typeof hits === 'number') out.hits = hits;
    if (typeof bots === 'number') out.botHits = bots;
    return Object.keys(out).length ? out : null;
  } catch {
    return null;
  }
}

export async function buildSiteStats(opts?: {
  outPath?: string;
  telemetryPath?: string;
  cfToken?: string;
  cfZoneId?: string;
}): Promise<SiteStats> {
  const generatedAt = new Date().toISOString();
  const provenance: string[] = [];
  const merged: SiteStats = { generatedAt };

  const telPath = opts?.telemetryPath ?? TELEMETRY_PATH;
  let tel: Partial<SiteStats> | null = null;
  try {
    if (fs.existsSync(telPath)) {
      const raw = JSON.parse(fs.readFileSync(telPath, 'utf8')) as {
        totals?: {
          pluginDownloads?: number;
          runsExecuted?: number;
          runsSucceeded?: number;
          runsFailed?: number;
        };
      };
      const t = raw.totals;
      if (t) {
        tel = {};
        if (typeof t.pluginDownloads === 'number') tel.downloads = t.pluginDownloads;
        if (typeof t.runsExecuted === 'number') tel.runsExecuted = t.runsExecuted;
        if (typeof t.runsSucceeded === 'number') tel.runsSucceeded = t.runsSucceeded;
        if (typeof t.runsFailed === 'number') tel.runsFailed = t.runsFailed;
        if (!Object.keys(tel).length) tel = null;
      }
    }
  } catch {
    tel = null;
  }
  // Fallback to helper when default path used
  if (!tel && telPath === TELEMETRY_PATH) tel = readTelemetry();

  if (tel) {
    Object.assign(merged, tel);
    provenance.push(`telemetry:${path.relative(ROOT, telPath)}`);
  }

  const token = opts?.cfToken ?? process.env.CF_API_TOKEN ?? '';
  const zoneId = opts?.cfZoneId ?? process.env.CF_ZONE_ID ?? '';
  if (token && zoneId) {
    const cf = await fetchCloudflareHits(token, zoneId);
    if (cf) {
      Object.assign(merged, cf);
      provenance.push('cloudflare:graphql-analytics');
    }
  }

  const facetKeys = ['hits', 'botHits', 'downloads', 'runsExecuted', 'runsSucceeded', 'runsFailed'] as const;
  const hasFacet = facetKeys.some(k => typeof merged[k] === 'number');
  if (!hasFacet) {
    const minimal: SiteStats = { unavailable: true, generatedAt };
    writeOut(opts?.outPath ?? DEFAULT_OUT, minimal);
    return minimal;
  }

  merged.provenance = provenance;
  writeOut(opts?.outPath ?? DEFAULT_OUT, merged);
  return merged;
}

function writeOut(outPath: string, data: SiteStats): void {
  fs.mkdirSync(path.dirname(outPath), { recursive: true });
  fs.writeFileSync(outPath, JSON.stringify(data, null, 2) + '\n');
}

const invokedDirectly =
  process.argv[1] && path.resolve(process.argv[1]) === fileURLToPath(import.meta.url);
if (invokedDirectly) {
  buildSiteStats().then(s => {
    const keys = Object.keys(s).filter(k => k !== 'generatedAt' && k !== 'provenance' && k !== 'unavailable');
    // eslint-disable-next-line no-console
    console.log(`site-stats → ${DEFAULT_OUT} (${s.unavailable ? 'unavailable' : keys.join(', ')})`);
  }).catch(err => {
    console.error(err);
    process.exit(1);
  });
}
