import fetch from 'node-fetch';
import { load as cheerioLoad } from 'cheerio';
import fs from 'fs';
import path from 'path';
import { fileURLToPath } from 'url';
import dotenv from 'dotenv';
import https from 'https';

const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);

dotenv.config({ path: path.join(__dirname, '.env') });

const WOW_ADDON_DIR = process.env.WOW_ADDON_DIR; // optional; fallback to repo root
const REGIONS = (process.env.REGIONS || 'eu').split(',').map(s => s.trim().toLowerCase()).filter(Boolean);
const BRACKETS = (process.env.BRACKETS || '2v2,3v3,shuffle,rbg,blitz').split(',').map(s => s.trim().toLowerCase());
const LOCALE = (process.env.LOCALE || 'en_US').toLowerCase(); // e.g. en_US, en_GB, de_DE

const LOCALE_TO_PATH = {
  'en_us': 'en-us',
  'en_gb': 'en-gb',
  'de_de': 'de-de',
  'fr_fr': 'fr-fr',
  'es_es': 'es-es',
  'es_mx': 'es-mx',
  'it_it': 'it-it',
  'pt_br': 'pt-br',
  'ru_ru': 'ru-ru',
  'ko_kr': 'ko-kr',
  'zh_tw': 'zh-tw',
  'zh_cn': 'zh-cn',
};

const REGION_TO_QS = {
  us: 'US',
  eu: 'EU',
  kr: 'KR',
  tw: 'TW',
};

const BRACKET_TO_PATH = {
  '2v2': '2v2',
  '3v3': '3v3',
  'shuffle': 'shuffle',
  'rbg': 'battlegrounds',
  'blitz': 'blitz',
};

function sleep(ms) { return new Promise(r => setTimeout(r, ms)); }

function normalizeRealmString(realm) {
  let s = '' + (realm || '');
  try { s = s.normalize('NFD').replace(/[\u0300-\u036f]/g, ''); } catch {}
  s = s.toLowerCase();
  const parts = s.split(/[\s\-']/g).filter(Boolean);
  const titled = parts.map(p => p.charAt(0).toUpperCase() + p.slice(1)).join('');
  return titled;
}

function buildLua(dataByRegion, season = 0) {
  const now = Math.floor(Date.now()/1000);
  const lines = [];
  lines.push('-- Generated by PvPScout scraper');
  lines.push('PvPScoutData = PvPScoutData or {}');
  lines.push(`PvPScoutData.season = ${Number(season || 0)}`);
  lines.push(`PvPScoutData.updatedAt = ${now}`);
  lines.push('PvPScoutData.data = PvPScoutData.data or {}');
  for (const region of Object.keys(dataByRegion)) {
    lines.push(`PvPScoutData.data["${region}"] = PvPScoutData.data["${region}"] or {}`);
    const reg = dataByRegion[region];
    for (const bracket of Object.keys(reg)) {
      lines.push(`PvPScoutData.data["${region}"]["${bracket}"] = {}`);
      const map = reg[bracket];
      const entries = Object.entries(map).slice(0, 5000);
      for (const [key, val] of entries) {
        const rating = Number(val.rating || 0);
        const rank = Number(val.rank || 0);
        lines.push(`PvPScoutData.data["${region}"]["${bracket}"]["${key}"] = { rating = ${rating}, rank = ${rank} }`);
      }
    }
  }
  lines.push('');
  return lines.join('\n');
}

function makeUrl(localePath, bracketPath, regionQs) {
  return `https://worldofwarcraft.blizzard.com/${localePath}/game/pvp/leaderboards/${bracketPath}?region=${regionQs}`;
}

async function fetchHTML(url) {
  // Optional TLS controls for local environments with SSL inspection
  // - CA_CERT_PATH / NODE_EXTRA_CA_CERTS: path to PEM with corporate root/intermediate
  // - INSECURE_TLS=1: disable verification (NOT recommended; for local testing only)
  const insecure = (process.env.INSECURE_TLS || '').toLowerCase() === '1' || (process.env.INSECURE_TLS || '').toLowerCase() === 'true';
  const caPath = process.env.CA_CERT_PATH || process.env.NODE_EXTRA_CA_CERTS;
  let agent;
  if (insecure) {
    agent = new https.Agent({ rejectUnauthorized: false });
  } else if (caPath && fs.existsSync(caPath)) {
    try {
      const ca = fs.readFileSync(caPath, 'utf8');
      agent = new https.Agent({ ca });
    } catch {
      // fall through without agent
    }
  }
  const res = await fetch(url, {
    headers: {
      'User-Agent': 'PvPScout/0.1 (+https://github.com/dominikreckziegel/PvPScout) polite-scraper',
      'Accept-Language': LOCALE.replace('_', '-'),
      'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
      'Cache-Control': 'no-cache',
    },
    agent,
  });
  if (!res.ok) throw new Error(`HTTP ${res.status}`);
  return await res.text();
}

function parseLeaderboard(html) {
  const $ = cheerioLoad(html);
  // Try to find rows in the leaderboard table; structure may vary, so be defensive.
  // Look for elements containing Rank, Player, Rating columns.
  const data = [];

  // Approach 1: table rows with headings
  const table = $('table, div[data-testid="leaderboard-table"], div.Leaderboard, div.pvp-leaderboard').first();
  let rows = [];
  if (table.length) {
    rows = table.find('tbody tr');
  } else {
    rows = $('tr');
  }

  rows.each((_, tr) => {
    const tds = $(tr).find('td');
    if (tds.length < 3) return;
    let rankText = $(tds[0]).text().trim();
    let playerText = $(tds[1]).text().trim();
    let ratingText = $(tds[2]).text().trim();

    // Fallbacks: sometimes rating may be in a later column
    if (!/\d/.test(ratingText)) {
      for (let i = 2; i < tds.length; i++) {
        const txt = $(tds[i]).text().trim();
        if (/^\d{3,4}$/.test(txt)) { ratingText = txt; break; }
      }
    }

    const rank = parseInt(rankText.replace(/[^0-9]/g, ''), 10);
    const rating = parseInt(ratingText.replace(/[^0-9]/g, ''), 10);

    // Extract player name and realm from links if available
    let name = '';
    let realm = '';
    const link = $(tds).find('a[href*="/character/"]').first();
    if (link.length) {
      try {
        const href = link.attr('href');
        // href example: /character/us/area-52/playername/
        const parts = href.split('/').filter(Boolean);
        // [..., region, realmSlug, name, ...]
        name = decodeURIComponent(parts[parts.length - 1]);
        const realmSlug = parts[parts.length - 2] || '';
        realm = realmSlug.replace(/-/g, ' ');
      } catch {}
    }
    if (!name) {
      // fallback to playerText like "Name-Realm" or "Name Realm"
      const m = playerText.match(/([A-Za-zÀ-ÖØ-öø-ÿ\-']+)[\s\-–—]+([A-Za-zÀ-ÖØ-öø-ÿ\-']+)/);
      if (m) { name = m[1]; realm = m[2]; }
      else { name = playerText; }
    }

    if (Number.isFinite(rank) && Number.isFinite(rating) && name) {
      data.push({ rank, rating, name, realm });
    }
  });

  // If nothing found, attempt to parse Next.js __NEXT_DATA__ or other embedded JSON
  if (data.length === 0) {
    // Next.js payload often lives in <script id="__NEXT_DATA__" type="application/json">{...}</script>
    const nextData = $('#__NEXT_DATA__').first();
    if (nextData && nextData.length) {
      try {
        const obj = JSON.parse(nextData.text());
        const acc = [];
        const visit = (o) => {
          if (!o) return;
          if (Array.isArray(o)) {
            for (const it of o) visit(it);
            return;
          }
          if (typeof o === 'object') {
            // Heuristic: arrays named entries or objects containing rank & rating
            if (Array.isArray(o.entries)) {
              for (const e of o.entries) acc.push(e);
            }
            for (const k of Object.keys(o)) visit(o[k]);
          }
        };
        visit(obj);
        for (const e of acc) {
          const rank = e && (e.rank || e.rating_position);
          const rating = e && (e.rating || e.pvp_rating);
          const cname = e && e.character && (e.character.name || e.character.character && e.character.character.name);
          const crealm = e && e.character && e.character.realm && (e.character.realm.name || e.character.realm.slug);
          if (rank && rating && cname) {
            data.push({ rank: Number(rank), rating: Number(rating), name: String(cname), realm: String(crealm || '') });
          }
        }
      } catch {
        // ignore JSON parse errors
      }
    }
  }

  // Legacy: any other scripts with JSON blobs
  if (data.length === 0) {
    $('script').each((_, s) => {
      const txt = $(s).html() || '';
      if (/leaderboard/i.test(txt) && /rank/i.test(txt) && /rating/i.test(txt)) {
        try {
          const jMatches = txt.match(/\{[\s\S]*\}/g) || [];
          for (const jm of jMatches) {
            const o = JSON.parse(jm);
            if (Array.isArray(o.entries)) {
              for (const e of o.entries) {
                if (e && e.rank && e.rating && e.character && e.character.name && e.character.realm) {
                  data.push({ rank: e.rank, rating: e.rating, name: e.character.name, realm: e.character.realm.name || e.character.realm.slug || '' });
                }
              }
            }
          }
        } catch {
          // ignore parse errors
        }
      }
    });
  }

  return data;
}

async function scrape() {
  const localePath = LOCALE_TO_PATH[LOCALE] || 'en-us';
  const results = {}; // { region: { bracket: { "Name-Realm": {rating, rank} } } }

  for (const region of REGIONS) {
    results[region] = {};
    for (const bracket of BRACKETS) {
      const bracketPath = BRACKET_TO_PATH[bracket];
      if (!bracketPath) { continue; }
      const url = makeUrl(localePath, bracketPath, REGION_TO_QS[region] || 'EU');
      try {
        const html = await fetchHTML(url);
        const rows = parseLeaderboard(html);
        const map = {};
        for (const e of rows) {
          const key = `${e.name}-${normalizeRealmString(e.realm)}`;
          if (!map[key] || e.rank < map[key].rank) {
            map[key] = { rating: e.rating, rank: e.rank };
          }
        }
        results[region][bracket] = map;
        const count = Object.keys(map).length;
        console.log(`Parsed ${count} rows for ${region} ${bracket}`);
        // polite delay between requests
        await sleep(1000);
      } catch (err) {
        console.error(`Scrape failed for ${region} ${bracket}: ${err.message}`);
      }
    }
  }

  // Fail if we parsed nothing, to avoid shipping empty Data.lua
  let total = 0;
  for (const r of Object.keys(results)) {
    for (const b of Object.keys(results[r])) total += Object.keys(results[r][b] || {}).length;
  }
  if (total === 0) {
    throw new Error('No leaderboard rows parsed; aborting to avoid empty Data.lua');
  }

  const lua = buildLua(results, 0);
  let outPath;
  if (WOW_ADDON_DIR) {
    try { fs.mkdirSync(WOW_ADDON_DIR, { recursive: true }); } catch {}
    outPath = path.join(WOW_ADDON_DIR, 'Data.lua');
  } else {
    const root = path.resolve(path.join(__dirname, '..'));
    outPath = path.join(root, 'Data.lua');
  }
  fs.writeFileSync(outPath, lua, 'utf8');
  console.log('Wrote', outPath);
}

scrape().catch(err => { console.error(err); process.exit(1); });
