// Dated asset-download tracking — the fix for "top image downloads THIS YEAR".
//
// The platform's per-asset viewed/downloaded columns are LIFETIME counters
// (no dates), and GA4's file_download tag often misses image downloads
// (basket/zip downloads, right-click saves, download endpoints). Neither can
// answer "downloads this year, per asset" — retroactively, nothing can.
//
// So we build the dated history OURSELVES: once a day, snapshot every
// asset's lifetime counters into data/media-snapshots/<site>.json. The DELTA
// between any two snapshots IS the dated activity — for every visitor,
// server-side, no consent gap. From the day this ships, every date-bounded
// asset question becomes answerable, and the answer gets better every day.

import fs from 'node:fs';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
import { loadSites, resolveRange } from './ga4.js';
import { dbKind, dbIsDemo, q } from './db.js';

const __dirname = path.dirname(fileURLToPath(import.meta.url));
const DIR = path.join(__dirname, '..', 'data', 'media-snapshots');
const MT = { 1: 'image', 2: 'video', 3: 'document', 0: 'misc' };

const fileFor = (siteId) => path.join(DIR, String(siteId).replace(/[^\w-]/g, '_') + '.json');
function load(siteId) {
  try { return JSON.parse(fs.readFileSync(fileFor(siteId), 'utf8')); } catch { return { meta: {}, days: {} }; }
}
function save(siteId, obj) {
  fs.mkdirSync(DIR, { recursive: true });
  fs.writeFileSync(fileFor(siteId), JSON.stringify(obj));
}

// One snapshot per site per day; cheap no-op the rest of the day.
export async function captureMediaSnapshot(siteId) {
  const site = loadSites().find((s) => s.id === siteId);
  if (!site || !dbKind(site) || dbIsDemo(site)) return false;
  const today = new Date().toISOString().slice(0, 10);
  const store = load(siteId);
  if (store.days[today]) return false;
  // The /* snap */ comment varies per day, side-stepping the query cache so
  // each day's capture always reads FRESH counters.
  const rows = await q(siteId,
    `SELECT m.id, m.name, m.type, m.viewed, m.downloaded FROM media m /* snap ${today} */
      ORDER BY GREATEST(COALESCE(m.downloaded, 0), COALESCE(m.viewed, 0)) DESC LIMIT 5000`).catch(() => null);
  if (!rows || !rows.length) return false;
  const assets = {};
  for (const r of rows) assets[r.id] = [Number(r.viewed) || 0, Number(r.downloaded) || 0];
  for (const r of rows) store.meta[r.id] = { name: r.name, type: MT[r.type] || 'misc' };
  store.days[today] = assets;
  const keys = Object.keys(store.days).sort();
  while (keys.length > 420) delete store.days[keys.shift()]; // ~14 months of history
  save(siteId, store);
  return true;
}

export async function captureAllMediaSnapshots() {
  for (const s of loadSites()) {
    try { await captureMediaSnapshot(s.id); } catch (e) { console.error('media snapshot', s.id, ':', e.message); }
  }
}

// Dated per-asset activity between two dates, from the snapshot deltas.
// Returns { available, trackedSince, effectiveFrom, effectiveTo, rows } —
// rows: { id, name, type, downloads, views } for the window, ranked.
export function datedAssetActivity(siteId, rangeStr, type = null, limit = 25) {
  const { startDate, endDate } = resolveRange(rangeStr || '28d');
  const site = loadSites().find((s) => s.id === siteId);
  if (site && (dbIsDemo(site) || !dbKind(site))) {
    // demo: deterministic sample so the feature demonstrates
    const seed = [...String(siteId)].reduce((a, c) => (a * 31 + c.charCodeAt(0)) >>> 0, 7);
    const names = type === 'video'
      ? ['Launch film B-roll', 'Dynamic on-road footage', 'Interior walkthrough']
      : type === 'document'
        ? ['2026 Specs & Features', 'Pricing guide', 'Press kit PDF']
        : ['EX0112', 'INT0112', 'EX0212s', 'INT0312', 'EX050712'];
    return {
      available: true, demo: true, trackedSince: startDate, effectiveFrom: startDate, effectiveTo: endDate,
      rows: names.map((name, i) => ({ id: 9200 + i, name, type: type || 'image',
        downloads: Math.max(4, 320 - i * 60 + (seed % 40)), views: Math.max(9, 700 - i * 120 + (seed % 80)) })),
    };
  }
  const store = load(siteId);
  const days = Object.keys(store.days).sort();
  if (days.length < 2) {
    return { available: false, trackedSince: days[0] || null, trackedDays: days.length, rows: [] };
  }
  const startKey = [...days].filter((d) => d <= startDate).pop() || days[0];
  // Ranges typically end "yesterday" while today's snapshot is the freshest
  // counter state — allow one day of slack so the newest snapshot closes the
  // window instead of being excluded by a boundary technicality.
  const endSlack = new Date(new Date(endDate + 'T00:00:00Z').getTime() + 86400000).toISOString().slice(0, 10);
  const endKey = [...days].filter((d) => d <= endSlack).pop() || days[days.length - 1];
  if (startKey >= endKey) {
    return { available: false, trackedSince: days[0], trackedDays: days.length, rows: [],
             note: 'The requested window predates or is shorter than the tracked history.' };
  }
  const a = store.days[startKey] || {};
  const b = store.days[endKey] || {};
  const rows = [];
  for (const id of Object.keys(b)) {
    const [v2, d2] = b[id];
    const [v1, d1] = a[id] || [0, 0]; // asset new since the window opened → all its activity counts
    const downloads = Math.max(0, d2 - d1);
    const views = Math.max(0, v2 - v1);
    if (!downloads && !views) continue;
    const meta = store.meta[id] || {};
    if (type && meta.type !== type) continue;
    rows.push({ id, name: meta.name || String(id), type: meta.type || 'misc', downloads, views });
  }
  rows.sort((x, y) => y.downloads - x.downloads || y.views - x.views);
  return {
    available: true,
    trackedSince: days[0],
    effectiveFrom: startKey,
    effectiveTo: endKey,
    partial: startKey > startDate ? `Counter tracking began ${days[0]} — this window is covered from ${startKey}, not ${startDate}.` : null,
    rows: rows.slice(0, Math.max(1, Math.min(100, limit))),
  };
}
