// サイト名で検索して来た人の数を、Search Console の API から毎日取る（2026-11-06）。
// 主に見る3つの数字の3つ目（worker/kpi.js の nameSearch）。
//
// - 鍵は Cloudflare の秘密の値 GSC_SA_KEY（Google Cloud のサービスアカウントの JSON の中身そのもの）。
//   そのサービスアカウントのメールを、Search Console のプロパティに「制限付き」のユーザーとして足してある
// - 見るプロパティは GSC_SITES（カンマ区切り）。なければ新ドメインと旧ドメインの2つ。権限のないプロパティは飛ばして記録に残す
// - 数は2〜3日遅れて確定するので、毎日、今週と先週の分を取り直して上書きする（前の日の3つの数字を作ったときに1回だけ）
// - 置き場所は R2（p2m-messages）の kpi-sc/{週の月曜}.json（MCP の record_name_search で手で書いたものと同じ形。byDay が付く）
// - Search Console は、人数の少ない検索語を伏せて返す（匿名化）。数は少なめに出る

import { KPI_FIRST_DAY, todayJst, addDays, weekOf } from './kpi.js';

const DEFAULT_SITES = ['sc-domain:pixelstomotors.com', 'https://adaptdesign.jp/'];
// サイト名の検索とみなす検索語（RE2 の正規表現。大文字小文字は区別しない）
const NAME_RE = '(?i)pixels?\\s*to\\s*motors?|pixelstomotors|ピクセルズ\\s*トゥ|ピクセル\\s*トゥ\\s*モーター';

const b64url = (bytes) => btoa(String.fromCharCode(...new Uint8Array(bytes))).replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '');
const b64urlText = (s) => b64url(new TextEncoder().encode(s));

async function accessToken(sa) {
  const now = Math.floor(Date.now() / 1000);
  const head = b64urlText(JSON.stringify({ alg: 'RS256', typ: 'JWT' }));
  const claim = b64urlText(JSON.stringify({ iss: sa.client_email, scope: 'https://www.googleapis.com/auth/webmasters.readonly', aud: 'https://oauth2.googleapis.com/token', iat: now, exp: now + 3600 }));
  const pem = sa.private_key.replace(/-----[^-]+-----/g, '').replace(/\s+/g, '');
  const der = Uint8Array.from(atob(pem), (c) => c.charCodeAt(0));
  const key = await crypto.subtle.importKey('pkcs8', der, { name: 'RSASSA-PKCS1-v1_5', hash: 'SHA-256' }, false, ['sign']);
  const sig = await crypto.subtle.sign('RSASSA-PKCS1-v1_5', key, new TextEncoder().encode(`${head}.${claim}`));
  const r = await fetch('https://oauth2.googleapis.com/token', {
    method: 'POST', headers: { 'content-type': 'application/x-www-form-urlencoded' },
    body: `grant_type=${encodeURIComponent('urn:ietf:params:oauth:grant-type:jwt-bearer')}&assertion=${head}.${claim}.${b64url(sig)}`,
  });
  const j = await r.json();
  if (!r.ok || !j.access_token) throw new Error(`Google の鍵の受け取りに失敗: HTTP ${r.status} ${j.error || ''}`);
  return j.access_token;
}

async function query(token, site, body) {
  const r = await fetch(`https://searchconsole.googleapis.com/webmasters/v3/sites/${encodeURIComponent(site)}/searchAnalytics/query`, {
    method: 'POST', headers: { authorization: `Bearer ${token}`, 'content-type': 'application/json' }, body: JSON.stringify(body),
  });
  const j = await r.json().catch(() => ({}));
  if (!r.ok) throw new Error(`HTTP ${r.status} ${(j.error && j.error.message) || ''}`.trim());
  return j.rows || [];
}

export const gscReady = (env) => !!env.GSC_SA_KEY;

// 1週分を取り直して残す
async function refreshWeek(env, token, sites, week) {
  const today = todayJst();
  const end = addDays(week, 6) < today ? addDays(week, 6) : addDays(today, -1);
  if (end < week) return null;
  const rec = { week, clicks: 0, impressions: 0, queries: [], byDay: {}, allClicks: 0, allImpressions: 0, sites: [], sitesFailed: [], source: 'Search Console API', note: '', recordedAt: new Date().toISOString() };
  const qmap = new Map();
  for (const site of sites) {
    try {
      const named = await query(token, site, {
        startDate: week, endDate: end, dimensions: ['date', 'query'], rowLimit: 5000,
        dimensionFilterGroups: [{ filters: [{ dimension: 'query', operator: 'includingRegex', expression: NAME_RE }] }],
      });
      for (const row of named) {
        const [day, q] = row.keys;
        rec.clicks += row.clicks; rec.impressions += row.impressions;
        const d = rec.byDay[day] || { clicks: 0, impressions: 0 }; d.clicks += row.clicks; d.impressions += row.impressions; rec.byDay[day] = d;
        const e = qmap.get(q) || { query: q, clicks: 0, impressions: 0 }; e.clicks += row.clicks; e.impressions += row.impressions; qmap.set(q, e);
      }
      // 参考：検索全体のクリックと表示（サイト名の検索がどれくらいの割合か）
      for (const row of await query(token, site, { startDate: week, endDate: end, dimensions: ['date'], rowLimit: 100 })) {
        rec.allClicks += row.clicks; rec.allImpressions += row.impressions;
      }
      rec.sites.push(site);
    } catch (e) {
      rec.sitesFailed.push(`${site}: ${e.message}`);
    }
  }
  if (!rec.sites.length) throw new Error(`どのプロパティも読めなかった（${rec.sitesFailed.join(' / ')}）`);
  rec.queries = [...qmap.values()].sort((a, b) => b.impressions - a.impressions).slice(0, 50);
  rec.note = `${week} 〜 ${end} の分（数は2〜3日遅れて確定する。人数の少ない検索語は Search Console が伏せる）`;
  await env.MESSAGES.put(`kpi-sc/${week}.json`, JSON.stringify(rec), { httpMetadata: { contentType: 'application/json' } });
  return rec;
}

// since の週から今週までを取り直す（毎日は先週と今週だけ）
export async function gscRefresh(env, { since } = {}) {
  if (!gscReady(env)) return { ok: false, reason: 'GSC_SA_KEY がない' };
  const sa = JSON.parse(env.GSC_SA_KEY);
  const sites = (env.GSC_SITES ? env.GSC_SITES.split(',') : DEFAULT_SITES).map((s) => s.trim()).filter(Boolean);
  const token = await accessToken(sa);
  const today = todayJst();
  let w = weekOf(since && since >= KPI_FIRST_DAY ? since : addDays(today, -7));
  const done = [];
  for (; w <= weekOf(today); w = addDays(w, 7)) {
    const r = await refreshWeek(env, token, sites, w);
    if (r) done.push({ week: w, clicks: r.clicks, impressions: r.impressions, sitesFailed: r.sitesFailed });
  }
  return { ok: true, account: sa.client_email, weeks: done };
}
