const express = require('express');
const { google } = require('googleapis');
const NodeCache = require('node-cache');

const router = express.Router();
const cache = new NodeCache({ stdTTL: parseInt(process.env.CACHE_TTL) || 300 });

// ── Auth ──────────────────────────────────────────────────────
function getGSCAuth() {
  if (process.env.GOOGLE_SERVICE_ACCOUNT_EMAIL && process.env.GOOGLE_PRIVATE_KEY) {
    return new google.auth.JWT({
      email: process.env.GOOGLE_SERVICE_ACCOUNT_EMAIL,
      key: process.env.GOOGLE_PRIVATE_KEY.replace(/\\n/g, '\n'),
      scopes: ['https://www.googleapis.com/auth/webmasters.readonly'],
    });
  }
  const oauth2Client = new google.auth.OAuth2(
    process.env.GOOGLE_CLIENT_ID,
    process.env.GOOGLE_CLIENT_SECRET
  );
  oauth2Client.setCredentials({ refresh_token: process.env.GOOGLE_REFRESH_TOKEN });
  return oauth2Client;
}

function getDateRange(days = 28) {
  const end = new Date();
  end.setDate(end.getDate() - 3); // GSC has ~3 day lag
  const start = new Date(end);
  start.setDate(start.getDate() - days);
  return {
    startDate: start.toISOString().split('T')[0],
    endDate: end.toISOString().split('T')[0],
  };
}

function getPrevDateRange(days = 28) {
  const { startDate, endDate } = getDateRange(days);
  const end = new Date(startDate);
  end.setDate(end.getDate() - 1);
  const start = new Date(end);
  start.setDate(start.getDate() - days);
  return {
    startDate: start.toISOString().split('T')[0],
    endDate: end.toISOString().split('T')[0],
  };
}

// ── GET /api/gsc/sites ────────────────────────────────────────
// Returns the list of verified GSC properties
router.get('/sites', async (req, res, next) => {
  const cacheKey = 'gsc_sites';
  const cached = cache.get(cacheKey);
  if (cached) return res.json(cached);

  try {
    const auth = getGSCAuth();
    const sc = google.searchconsole({ version: 'v1', auth });
    const { data } = await sc.sites.list();
    const sites = (data.siteEntry || []).map(s => ({
      url: s.siteUrl,
      permissionLevel: s.permissionLevel,
    }));
    cache.set(cacheKey, sites);
    res.json(sites);
  } catch (err) {
    next(err);
  }
});

// ── GET /api/gsc/metrics?site=&days=28 ───────────────────────
// Returns clicks, impressions, CTR, avg position + deltas
router.get('/metrics', async (req, res, next) => {
  const { site, days = 28 } = req.query;
  if (!site) return res.status(400).json({ error: 'site param required' });

  const cacheKey = `gsc_metrics_${site}_${days}`;
  const cached = cache.get(cacheKey);
  if (cached) return res.json(cached);

  try {
    const auth = getGSCAuth();
    const sc = google.searchconsole({ version: 'v1', auth });

    const current = getDateRange(parseInt(days));
    const prev = getPrevDateRange(parseInt(days));

    const [curResp, prevResp] = await Promise.all([
      sc.searchanalytics.query({
        siteUrl: site,
        requestBody: { type: 'web', startDate: current.startDate, endDate: current.endDate, dimensions: [] },
      }),
      sc.searchanalytics.query({
        siteUrl: site,
        requestBody: { type: 'web', startDate: prev.startDate, endDate: prev.endDate, dimensions: [] },
      }),
    ]);

    const cur = curResp.data.rows?.[0] || { clicks: 0, impressions: 0, ctr: 0, position: 0 };
    const prv = prevResp.data.rows?.[0] || { clicks: 0, impressions: 0, ctr: 0, position: 0 };

    const pct = (a, b) => b === 0 ? null : ((a - b) / b * 100).toFixed(1);
    const diff = (a, b) => (a - b).toFixed(1);

    const result = {
      period: current,
      current: { clicks: cur.clicks, impressions: cur.impressions, ctr: (cur.ctr * 100).toFixed(2), position: cur.position?.toFixed(1) },
      previous: { clicks: prv.clicks, impressions: prv.impressions, ctr: (prv.ctr * 100).toFixed(2), position: prv.position?.toFixed(1) },
      deltas: {
        clicks: pct(cur.clicks, prv.clicks),
        impressions: pct(cur.impressions, prv.impressions),
        ctr: diff(cur.ctr * 100, prv.ctr * 100),
        position: diff(prv.position, cur.position), // inverted: lower pos # = better
      },
    };

    cache.set(cacheKey, result);
    res.json(result);
  } catch (err) {
    next(err);
  }
});

// ── GET /api/gsc/keywords?site=&days=28&limit=100 ────────────
// Returns queries from GSC with clicks, impressions, CTR, position + movement
router.get('/keywords', async (req, res, next) => {
  const { site, days = 28, limit = 100 } = req.query;
  if (!site) return res.status(400).json({ error: 'site param required' });

  const cacheKey = `gsc_kw_${site}_${days}_${limit}`;
  const cached = cache.get(cacheKey);
  if (cached) return res.json(cached);

  try {
    const auth = getGSCAuth();
    const sc = google.searchconsole({ version: 'v1', auth });

    const current = getDateRange(parseInt(days));
    const prev = getPrevDateRange(parseInt(days));

    const sevenDayEnd = new Date();
    sevenDayEnd.setDate(sevenDayEnd.getDate() - 3);
    const sevenDayStart = new Date(sevenDayEnd);
    sevenDayStart.setDate(sevenDayStart.getDate() - 9);

    const [curResp, prevResp, weekResp] = await Promise.all([
      sc.searchanalytics.query({
        siteUrl: site,
        requestBody: {
          type: 'web', startDate: current.startDate,
          endDate: current.endDate,
          dimensions: ['query'],
          rowLimit: parseInt(limit),
          orderBy: [{ fieldName: 'clicks', sortOrder: 'DESCENDING' }],
        },
      }),
      sc.searchanalytics.query({
        siteUrl: site,
        requestBody: {
          type: 'web', startDate: prev.startDate,
          endDate: prev.endDate,
          dimensions: ['query'],
          rowLimit: parseInt(limit),
        },
      }),
      sc.searchanalytics.query({
        siteUrl: site,
        requestBody: {
          type: 'web', startDate: sevenDayStart.toISOString().split('T')[0],
          endDate: sevenDayEnd.toISOString().split('T')[0],
          dimensions: ['date', 'query'],
          rowLimit: 500,
          orderBy: [{ fieldName: 'clicks', sortOrder: 'DESCENDING' }],
        },
      }),
    ]);

    const prevMap = {};
    (prevResp.data.rows || []).forEach(r => { prevMap[r.keys[0]] = r; });

    const historyMap = {};
    (weekResp.data.rows || []).forEach(r => {
      const [date, query] = r.keys;
      if (!historyMap[query]) historyMap[query] = {};
      historyMap[query][date] = parseFloat(r.position?.toFixed(1));
    });

    const sparkDates = [...new Set(
      (weekResp.data.rows || []).map(r => r.keys[0])
    )].sort().slice(-7);

    const keywords = (curResp.data.rows || []).map(r => {
      const query = r.keys[0];
      const p = prevMap[query];
      const posDiff = p ? (p.position - r.position) : null;
      const positionHistory = sparkDates.map(d => historyMap[query]?.[d] ?? null);

      return {
        keyword: query,
        clicks: r.clicks,
        impressions: r.impressions,
        ctr: parseFloat((r.ctr * 100).toFixed(2)),
        position: parseFloat(r.position?.toFixed(1)),
        prevPosition: p ? parseFloat(p.position?.toFixed(1)) : null,
        positionDelta: posDiff !== null ? parseFloat(posDiff.toFixed(1)) : null,
        movement: posDiff === null ? 'new' : posDiff > 0.5 ? 'up' : posDiff < -0.5 ? 'down' : 'stable',
        positionHistory,
        searchVolume: null,
        url: null,
      };
    });

    cache.set(cacheKey, keywords);
    res.json(keywords);
  } catch (err) {
    next(err);
  }
});

// ── GET /api/gsc/pages?site=&days=28&limit=20 ────────────────
// Returns top pages by clicks
router.get('/pages', async (req, res, next) => {
  const { site, days = 28, limit = 10 } = req.query;
  if (!site) return res.status(400).json({ error: 'site param required' });

  const cacheKey = `gsc_pages_${site}_${days}_${limit}`;
  const cached = cache.get(cacheKey);
  if (cached) return res.json(cached);

  try {
    const auth = getGSCAuth();
    const sc = google.searchconsole({ version: 'v1', auth });
    const { startDate, endDate } = getDateRange(parseInt(days));

    const resp = await sc.searchanalytics.query({
      siteUrl: site,
      requestBody: {
        startDate, endDate,
        dimensions: ['page'],
        rowLimit: parseInt(limit),
        orderBy: [{ fieldName: 'clicks', sortOrder: 'DESCENDING' }],
      },
    });

    const pages = (resp.data.rows || []).map(r => ({
      page: r.keys[0].replace(site, '/'),
      clicks: r.clicks,
      impressions: r.impressions,
      ctr: (r.ctr * 100).toFixed(2),
      position: r.position?.toFixed(1),
    }));

    cache.set(cacheKey, pages);
    res.json(pages);
  } catch (err) {
    next(err);
  }
});

// ── GET /api/gsc/history?site=&days=90 ───────────────────────
// Returns daily clicks + impressions for sparkline charts
router.get('/history', async (req, res, next) => {
  const { site, days = 90 } = req.query;
  if (!site) return res.status(400).json({ error: 'site param required' });

  const cacheKey = `gsc_hist_${site}_${days}`;
  const cached = cache.get(cacheKey);
  if (cached) return res.json(cached);

  try {
    const auth = getGSCAuth();
    const sc = google.searchconsole({ version: 'v1', auth });
    const { startDate, endDate } = getDateRange(parseInt(days));

    const resp = await sc.searchanalytics.query({
      siteUrl: site,
      requestBody: {
        startDate, endDate,
        dimensions: ['date'],
        orderBy: [{ fieldName: 'date', sortOrder: 'ASCENDING' }],
      },
    });

    const history = (resp.data.rows || []).map(r => ({
      date: r.keys[0],
      clicks: r.clicks,
      impressions: r.impressions,
      ctr: parseFloat((r.ctr * 100).toFixed(2)),
      position: parseFloat(r.position?.toFixed(1)),
    }));

    cache.set(cacheKey, history);
    res.json(history);
  } catch (err) {
    next(err);
  }
});


// ── POST /api/gsc/kpi-pages ─────────────────────────────────
// Enhanced: weighted avg position, top queries, position distribution,
//           mobile vs desktop split, SERP features, cannibalization
router.post('/kpi-pages', async (req, res, next) => {
  const { site, pages = [], days = 28 } = req.body;
  if (!site) return res.status(400).json({ error: 'site param required' });
  if (!pages.length) return res.json([]);

  const cacheKey = `gsc_kpi_v2_${site}_${days}_${pages.sort().join('|')}`;
  const cached = cache.get(cacheKey);
  if (cached) return res.json(cached);

  try {
    const auth = getGSCAuth();
    const sc = google.searchconsole({ version: 'v1', auth });
    const current = getDateRange(parseInt(days));
    const prev    = getPrevDateRange(parseInt(days));

    // Fetch all data in parallel
    const [
      curPageResp,   // current period: page level
      prevPageResp,  // prev period: page level
      curQryResp,    // current period: page + query (for weighted pos, top queries, distribution)
      prevQryResp,   // prev period: page + query (for per-query movement)
      mobileResp,    // current: mobile device
      desktopResp,   // current: desktop device
      serpResp,      // current: search appearance (SERP features)
    ] = await Promise.all([
      sc.searchanalytics.query({ siteUrl: site, requestBody: { type: 'web', startDate: current.startDate, endDate: current.endDate, dimensions: ['page'], rowLimit: 5000 } }),
      sc.searchanalytics.query({ siteUrl: site, requestBody: { type: 'web', startDate: prev.startDate, endDate: prev.endDate, dimensions: ['page'], rowLimit: 5000 } }),
      sc.searchanalytics.query({ siteUrl: site, requestBody: { type: 'web', startDate: current.startDate, endDate: current.endDate, dimensions: ['page','query'], rowLimit: 25000, orderBy: [{ fieldName: 'impressions', sortOrder: 'DESCENDING' }] } }),
      sc.searchanalytics.query({ siteUrl: site, requestBody: { type: 'web', startDate: prev.startDate, endDate: prev.endDate, dimensions: ['page','query'], rowLimit: 25000 } }),
      sc.searchanalytics.query({ siteUrl: site, requestBody: { type: 'web', startDate: current.startDate, endDate: current.endDate, dimensions: ['page'], dimensionFilterGroups: [{ filters: [{ dimension: 'device', operator: 'equals', expression: 'MOBILE' }] }], rowLimit: 5000 } }),
      sc.searchanalytics.query({ siteUrl: site, requestBody: { type: 'web', startDate: current.startDate, endDate: current.endDate, dimensions: ['page'], dimensionFilterGroups: [{ filters: [{ dimension: 'device', operator: 'equals', expression: 'DESKTOP' }] }], rowLimit: 5000 } }),
      sc.searchanalytics.query({ siteUrl: site, requestBody: { type: 'web', startDate: current.startDate, endDate: current.endDate, dimensions: ['page', 'searchAppearance'], rowLimit: 5000 } }).catch(() => ({ data: { rows: [] } })),
    ]);

    // Build lookups
    const curPageMap  = {}, prevPageMap = {}, mobileMap = {}, desktopMap = {};
    (curPageResp.data.rows  || []).forEach(r => { curPageMap[r.keys[0]]  = r; });
    (prevPageResp.data.rows || []).forEach(r => { prevPageMap[r.keys[0]] = r; });
    (mobileResp.data.rows   || []).forEach(r => { mobileMap[r.keys[0]]  = r; });
    (desktopResp.data.rows  || []).forEach(r => { desktopMap[r.keys[0]] = r; });

    // Build page → queries map (current + prev)
    const pageQryMap     = {}; // page -> [{ query, clicks, impressions, ctr, position }]
    const pageQryPrevMap = {}; // page -> { query -> { position, clicks } }
    (curQryResp.data.rows || []).forEach(r => {
      const [pg, q] = r.keys;
      if (!pageQryMap[pg]) pageQryMap[pg] = [];
      pageQryMap[pg].push({ query: q, clicks: r.clicks, impressions: r.impressions, ctr: parseFloat((r.ctr*100).toFixed(2)), position: parseFloat(r.position?.toFixed(1)) });
    });
    (prevQryResp.data.rows || []).forEach(r => {
      const [pg, q] = r.keys;
      if (!pageQryPrevMap[pg]) pageQryPrevMap[pg] = {};
      pageQryPrevMap[pg][q] = { position: parseFloat(r.position?.toFixed(1)), clicks: r.clicks };
    });

    // SERP features per page
    const serpMap = {}; // page -> Set of features
    (serpResp.data.rows || []).forEach(r => {
      const [pg, feature] = r.keys;
      if (!serpMap[pg]) serpMap[pg] = new Set();
      serpMap[pg].add(feature);
    });

    // Expected CTR by position (industry averages)
    const expectedCTR = [0, 0.28, 0.15, 0.11, 0.08, 0.07, 0.06, 0.05, 0.04, 0.03, 0.025];
    const getExpectedCTR = (pos) => {
      if (!pos) return null;
      const p = Math.round(pos);
      return p <= 10 ? (expectedCTR[p] || 0.02) : 0.01;
    };

    // URL variant builder
    const buildVariants = (pagePath, siteUrl) => {
      const set = new Set();
      const addBoth = url => { set.add(url); set.add(url.endsWith('/') ? url.slice(0,-1) : url+'/'); };
      if (pagePath.startsWith('http')) {
        addBoth(pagePath);
        addBoth(pagePath.replace(/^https:/, 'http:'));
        addBoth(pagePath.replace(/^http:/,  'https:'));
        return [...set];
      }
      const domain = siteUrl.replace(/^sc-domain:/, '').replace(/^https?:\/\//, '').replace(/\/$/, '').replace(/^www\./, '');
      const path = pagePath.startsWith('/') ? pagePath : '/' + pagePath;
      ['https','http'].forEach(proto => { [domain, 'www.'+domain].forEach(d => addBoth(`${proto}://${d}${path}`)); });
      return [...set];
    };

    const findInMap = (map, variants) => { for (const v of variants) { if (map[v]) return map[v]; } return null; };
    const findKey   = (map, variants) => { for (const v of variants) { if (map[v]) return v; } return null; };

    const results = pages.map(pagePath => {
      const variants   = buildVariants(pagePath, site);
      const cur        = findInMap(curPageMap,  variants);
      const prv        = findInMap(prevPageMap, variants);
      const mob        = findInMap(mobileMap,   variants);
      const dsk        = findInMap(desktopMap,  variants);
      const matchedKey = findKey(pageQryMap, variants);
      const queries    = matchedKey ? (pageQryMap[matchedKey] || []) : [];
      const prevQuries = matchedKey ? (pageQryPrevMap[matchedKey] || {}) : {};
      const serpFeats  = matchedKey ? [...(serpMap[matchedKey] || new Set())] : [];

      // ── Weighted average position (by impressions) ────────────
      const totalImpr = queries.reduce((s, q) => s + q.impressions, 0);
      const weightedPos = totalImpr > 0
        ? parseFloat((queries.reduce((s, q) => s + q.position * q.impressions, 0) / totalImpr).toFixed(1))
        : (cur ? parseFloat(cur.position?.toFixed(1)) : null);

      const prevTotalImpr = Object.values(prevQuries).reduce((s, q) => s + (q.clicks || 0), 0);

      // ── Position distribution ─────────────────────────────────
      const dist = { top3: 0, top10: 0, top20: 0, beyond20: 0 };
      queries.forEach(q => {
        const p = q.position;
        if (p <= 3)  dist.top3++;
        else if (p <= 10) dist.top10++;
        else if (p <= 20) dist.top20++;
        else dist.beyond20++;
      });

      // ── Top 20 queries with movement ─────────────────────────
      const topQueries = queries.slice(0, 20).map(q => {
        const prev = prevQuries[q.query];
        const delta = prev ? parseFloat((prev.position - q.position).toFixed(1)) : null;
        const expCTR = getExpectedCTR(q.position);
        const ctrVsExpected = expCTR ? parseFloat(((q.ctr / 100 - expCTR) / expCTR * 100).toFixed(0)) : null;
        return {
          query:          q.query,
          clicks:         q.clicks,
          impressions:    q.impressions,
          ctr:            q.ctr,
          position:       q.position,
          prevPosition:   prev ? prev.position : null,
          positionDelta:  delta,
          movement:       delta === null ? 'new' : delta > 0.5 ? 'up' : delta < -0.5 ? 'down' : 'stable',
          ctrVsExpected,  // % above/below expected CTR for this position
        };
      });

      // ── Visibility score (0-100) ──────────────────────────────
      // Based on: position, CTR vs expected, impressions share
      const pos = weightedPos;
      const actualCTR = cur ? cur.ctr : 0;
      const expCTR = getExpectedCTR(pos) || 0.01;
      const posScore  = pos ? Math.max(0, Math.min(100, (1 - (pos - 1) / 49) * 100)) : 0;
      const ctrScore  = Math.min(100, (actualCTR / expCTR) * 50);
      const visibilityScore = Math.round((posScore * 0.6 + ctrScore * 0.4));

      // ── Main position delta ───────────────────────────────────
      const prevWeightedPos = prv ? parseFloat(prv.position?.toFixed(1)) : null;
      const delta = (pos !== null && prevWeightedPos !== null) ? parseFloat((prevWeightedPos - pos).toFixed(1)) : null;

      // ── Mobile vs desktop ─────────────────────────────────────
      const mobilePos  = mob ? parseFloat(mob.position?.toFixed(1))  : null;
      const desktopPos = dsk ? parseFloat(dsk.position?.toFixed(1))  : null;
      const mobileDesktopGap = (mobilePos && desktopPos) ? parseFloat((mobilePos - desktopPos).toFixed(1)) : null;

      // ── CTR vs expected ───────────────────────────────────────
      const expCTRVal = getExpectedCTR(pos);
      const ctrActual = cur ? parseFloat((cur.ctr * 100).toFixed(2)) : null;
      const ctrVsExpected = (expCTRVal && ctrActual != null) ? parseFloat(((ctrActual / 100 - expCTRVal) / expCTRVal * 100).toFixed(0)) : null;

      if (!cur) console.log('[KPI] No match for:', pagePath, '| tried:', variants.slice(0,2));

      return {
        page:            pagePath,
        // Core metrics
        clicks:          cur ? cur.clicks       : null,
        impressions:     cur ? cur.impressions  : null,
        ctr:             ctrActual,
        prevClicks:      prv ? prv.clicks       : null,
        prevImpressions: prv ? prv.impressions  : null,
        // Position
        position:        weightedPos,                  // weighted avg (main)
        rawPosition:     cur ? parseFloat(cur.position?.toFixed(1)) : null, // GSC page-level
        prevPosition:    prevWeightedPos,
        positionDelta:   delta,
        movement:        delta === null ? 'new' : delta > 0.5 ? 'up' : delta < -0.5 ? 'down' : 'stable',
        // Visibility
        visibilityScore,
        // Distribution
        queryCount:      queries.length,
        distribution:    dist,
        // Top queries
        topQueries,
        // Mobile vs desktop
        mobilePosition:  mobilePos,
        desktopPosition: desktopPos,
        mobileDesktopGap,
        // CTR analysis
        ctrVsExpected,
        expectedCTR:     expCTRVal ? parseFloat((expCTRVal * 100).toFixed(1)) : null,
        // SERP features
        serpFeatures:    serpFeats,
        hasFeaturedSnippet: serpFeats.some(f => f.includes('FEATURED') || f.includes('SNIPPET')),
        hasPAA:          serpFeats.some(f => f.includes('PEOPLE_ALSO_ASK') || f.includes('PAA')),
      };
    });

    // ── Cannibalization detection ─────────────────────────────
    // Find queries appearing as top query for multiple KPI pages
    const queryToPages = {};
    results.forEach(r => {
      (r.topQueries || []).slice(0, 5).forEach(q => {
        if (!queryToPages[q.query]) queryToPages[q.query] = [];
        queryToPages[q.query].push(r.page);
      });
    });
    const cannibalizedQueries = Object.entries(queryToPages)
      .filter(([, pgs]) => pgs.length > 1)
      .map(([query, pgs]) => ({ query, pages: pgs }));

    // Attach cannibalization flags to each result
    results.forEach(r => {
      const conflicts = cannibalizedQueries.filter(c => c.pages.includes(r.page));
      r.cannibalization = conflicts.length > 0 ? conflicts : null;
    });

    const response = { pages: results, cannibalization: cannibalizedQueries };
    cache.set(cacheKey, response, 300);
    res.json(response);
  } catch (err) { next(err); }
});


module.exports = router;
