import fetch from 'node-fetch';
import fs from 'fs';
import path from 'path';
import { fileURLToPath } from 'url';
import dotenv from 'dotenv';
import readline from 'readline';

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

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

const CLIENT_ID = process.env.BLIZZARD_CLIENT_ID;
const CLIENT_SECRET = process.env.BLIZZARD_CLIENT_SECRET;
const WOW_ADDON_DIR = process.env.WOW_ADDON_DIR; // optional now; 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';
const MAX_ENTRIES_PER_BRACKET = (() => {
  const raw = process.env.MAX_ENTRIES_PER_BRACKET;
  if (raw === '0') return 0; // 0 = unlimited
  const n = parseInt(raw || '5000', 10);
  return Number.isFinite(n) ? n : 5000;
})();
const OFFLINE_SOURCE = process.env.OFFLINE_SOURCE; // file path or URL to JSON/CSV

// Determine mode: online (Blizzard API) vs offline (local/URL file)
const ONLINE_MODE = !!(CLIENT_ID && CLIENT_SECRET);

const REGION_CONFIG = {
  us: { host: 'https://us.api.blizzard.com', oauth: 'https://oauth.battle.net' },
  eu: { host: 'https://eu.api.blizzard.com', oauth: 'https://oauth.battle.net' },
  kr: { host: 'https://kr.api.blizzard.com', oauth: 'https://oauth.battle.net' },
  tw: { host: 'https://tw.api.blizzard.com', oauth: 'https://oauth.battle.net' },
};

async function getToken(region) {
  const url = `${REGION_CONFIG[region].oauth}/token`;
  const auth = Buffer.from(`${CLIENT_ID}:${CLIENT_SECRET}`).toString('base64');
  const res = await fetch(url, {
    method: 'POST',
    headers: { 'Authorization': `Basic ${auth}`, 'Content-Type': 'application/x-www-form-urlencoded' },
    body: 'grant_type=client_credentials'
  });
  if (!res.ok) throw new Error(`Token error ${res.status}`);
  return await res.json(); // { access_token, expires_in, ... }
}

async function getCurrentSeason(region, token) {
  // Get PvP season index, then current season ID
  const ns = `static-${region}`;
  const host = REGION_CONFIG[region].host;
  const res = await fetch(`${host}/data/wow/pvp-season/index?namespace=${ns}&locale=${LOCALE}&access_token=${token}`);
  if (!res.ok) throw new Error(`Season index error ${res.status}`);
  const data = await res.json();
  const current = data.current_season && data.current_season.id;
  if (!current) throw new Error('No current season id');
  return current;
}

// Map logical bracket to API slug candidates (try in order until one exists)
const BRACKET_MAP = {
  '2v2': ['2v2'],
  '3v3': ['3v3'],
  'shuffle': ['shuffle', 'solo-shuffle'],
  'rbg': ['rbg', 'rated-battlegrounds', 'battlegrounds-rated'],
  'blitz': ['battleground-blitz', 'blitz', 'bg-blitz'],
};

async function getLeaderboardsIndex(region, seasonId, token) {
  const host = REGION_CONFIG[region].host;
  const ns = `dynamic-${region}`;
  const url = `${host}/data/wow/pvp-season/${seasonId}/pvp-leaderboard/index?namespace=${ns}&locale=${LOCALE}&access_token=${token}`;
  const res = await fetch(url);
  if (!res.ok) throw new Error(`Leaderboard index error ${res.status}`);
  const data = await res.json();
  // Try to extract slugs from hrefs if needed
  const hrefs = [];
  const list = data.leaderboards || data.pvp_leaderboards || data._links || [];
  if (Array.isArray(data.leaderboards)) {
    for (const l of data.leaderboards) {
      if (l && l.href) hrefs.push(l.href);
      else if (l && l.key && l.key.href) hrefs.push(l.key.href);
    }
  } else if (Array.isArray(data.pvp_leaderboards)) {
    for (const l of data.pvp_leaderboards) {
      if (l && l.href) hrefs.push(l.href);
      else if (l && l.key && l.key.href) hrefs.push(l.key.href);
    }
  }
  const slugs = new Set();
  for (const h of hrefs) {
    try {
      const u = new URL(h);
      const parts = u.pathname.split('/').filter(Boolean);
      const idx = parts.lastIndexOf('pvp-leaderboard');
      if (idx >= 0 && parts[idx+1]) slugs.add(parts[idx+1]);
    } catch { /* ignore */ }
  }
  return slugs;
}

async function resolveBracketSlug(region, seasonId, bracket, token, cachedIndex) {
  const candidates = BRACKET_MAP[bracket] || [bracket];
  const slugs = cachedIndex || await getLeaderboardsIndex(region, seasonId, token);
  for (const c of candidates) {
    if (slugs.has(c)) return c;
  }
  // fallback: try HTTP directly in order, stop on first non-404 success
  const host = REGION_CONFIG[region].host;
  const ns = `dynamic-${region}`;
  for (const c of candidates) {
    const testUrl = `${host}/data/wow/pvp-season/${seasonId}/pvp-leaderboard/${c}?namespace=${ns}&locale=${LOCALE}&access_token=${token}`;
    const res = await fetch(testUrl, { method: 'HEAD' });
    if (res.ok) return c;
    if (res.status !== 404) break;
  }
  return null;
}

async function fetchLeaderboard(region, seasonId, slug, token) {
  const host = REGION_CONFIG[region].host;
  const ns = `dynamic-${region}`;
  const url = `${host}/data/wow/pvp-season/${seasonId}/pvp-leaderboard/${slug}?namespace=${ns}&locale=${LOCALE}&access_token=${token}`;
  const res = await fetch(url);
  if (!res.ok) throw new Error(`Leaderboard ${region} ${slug} error ${res.status}`);
  return await res.json();
}

function stripDiacritics(str) {
  try { return str.normalize('NFD').replace(/[\u0300-\u036f]/g, ''); } catch { return str; }
}

function normalizeRealmString(realm) {
  let s = '' + (realm || '');
  s = stripDiacritics(s).toLowerCase();
  // Split by hyphen/space/apostrophe and TitleCase each segment
  const parts = s.split(/[\s\-']/g).filter(Boolean);
  const titled = parts.map(p => p.charAt(0).toUpperCase() + p.slice(1)).join('');
  return titled;
}

function slugifySpecName(name) {
  if (!name) return null;
  let s = String(name).trim().toLowerCase();
  s = s.normalize('NFD').replace(/[\u0300-\u036f]/g, '');
  s = s.replace(/[^a-z0-9\s\-]/g, '');
  s = s.replace(/\s+/g, '-');
  return s || null;
}

function normalizeNameRealm(entry) {
  const name = entry.character && entry.character.name ? entry.character.name : (entry.name || '');
  const realm = (entry.realm && (entry.realm.name || entry.realm.slug || entry.realm.id)) || (entry.character && entry.character.realm && (entry.character.realm.name || entry.character.realm.slug)) || '';
  const realmStr = normalizeRealmString(realm);
  return `${name}-${realmStr}`;
}

function buildLua(dataByRegion) {
  const now = Math.floor(Date.now()/1000);
  const obj = {
    season: dataByRegion.season || 0,
    updatedAt: now,
    data: dataByRegion.data,
  };
  // Serialize to Lua table
  const lines = [];
  lines.push('-- Generated by PvPScout fetcher');
  lines.push('PvPScoutData = PvPScoutData or {}');
  lines.push(`PvPScoutData.season = ${obj.season}`);
  lines.push(`PvPScoutData.updatedAt = ${obj.updatedAt}`);
  lines.push('PvPScoutData.data = PvPScoutData.data or {}');
  for (const region of Object.keys(obj.data)) {
    lines.push(`PvPScoutData.data["${region}"] = PvPScoutData.data["${region}"] or {}`);
    const reg = obj.data[region];
    for (const bracket of Object.keys(reg)) {
      lines.push(`PvPScoutData.data["${region}"]["${bracket}"] = {}`);
      const map = reg[bracket];
  const entries = Object.entries(map);
  // Optional cap to keep Data.lua compact; 0 disables the cap
  const slice = (MAX_ENTRIES_PER_BRACKET > 0) ? entries.slice(0, MAX_ENTRIES_PER_BRACKET) : entries;
      for (const [key, val] of slice) {
        const rating = Number(val.rating || 0);
        const rank = Number(val.rank || 0);
        const bySpec = (val && typeof val.bySpec === 'object') ? val.bySpec : null;
        if (bySpec && Object.keys(bySpec).length > 0) {
          const specEntries = Object.entries(bySpec)
            .map(([s, v]) => ` ["${s}"] = { rating = ${Number((v && v.rating) || 0)}, rank = ${Number((v && v.rank) || 0)} }`)
            .join(',');
          lines.push(`PvPScoutData.data["${region}"]["${bracket}"]["${key}"] = { rating = ${rating}, rank = ${rank}, bySpec = {${specEntries}} }`);
        } else {
          lines.push(`PvPScoutData.data["${region}"]["${bracket}"]["${key}"] = { rating = ${rating}, rank = ${rank} }`);
        }
      }
    }
  }
  lines.push('');
  return lines.join('\n');
}

async function importOfflineFromJSON(obj) {
  // Expected structure:
  // { season: <number>, eu: { "2v2": {"Name-Realm": {rating, rank}}, ... }, us: {...} }
  const dataByRegion = { season: Number(obj.season || 0), data: {} };
  for (const region of REGIONS) {
    const regData = obj[region];
    if (!regData || typeof regData !== 'object') continue;
    dataByRegion.data[region] = {};
    for (const bracket of BRACKETS) {
      const b = regData[bracket];
      if (!b || typeof b !== 'object') continue;
      dataByRegion.data[region][bracket] = {};
      for (const [key, val] of Object.entries(b)) {
        const rating = Number((val && val.rating) || 0);
        const rank = Number((val && val.rank) || 0);
        dataByRegion.data[region][bracket][key] = { rating, rank };
      }
    }
  }
  return dataByRegion;
}

async function importOfflineFromCSV(text) {
  // CSV columns: region,bracket,name,realm,rating,rank
  const dataByRegion = { season: 0, data: {} };
  const rl = readline.createInterface({ input: fs.createReadStream(text, { encoding: 'utf8' }), crlfDelay: Infinity });
  // However, if 'text' is a path we streamed already; here we detect whether 'text' is content or path
  // Simpler: if text contains newlines, parse as content; else read file
  let content = text.includes('\n') ? text : fs.readFileSync(text, 'utf8');
  const lines = content.split(/\r?\n/).filter(Boolean);
  for (let i = 0; i < lines.length; i++) {
    const line = lines[i];
    if (i === 0 && /region\s*,\s*bracket/i.test(line)) continue; // skip header
    const parts = line.split(',').map(s => s.trim());
    if (parts.length < 6) continue;
    const [region, bracket, name, realm, ratingStr, rankStr] = parts;
    const reg = (region || '').toLowerCase();
    if (!REGION_CONFIG[reg]) continue;
    if (!dataByRegion.data[reg]) dataByRegion.data[reg] = {};
    if (!BRACKETS.includes(bracket)) continue;
    if (!dataByRegion.data[reg][bracket]) dataByRegion.data[reg][bracket] = {};
    const key = `${name}-${normalizeRealmString(realm)}`;
    dataByRegion.data[reg][bracket][key] = { rating: Number(ratingStr || 0), rank: Number(rankStr || 0) };
  }
  return dataByRegion;
}

async function loadOfflineSource() {
  // If OFFLINE_SOURCE is a URL, fetch it; else treat as file path; else fallback to default locations
  let src = OFFLINE_SOURCE;
  let content = null;
  if (!src) {
    const candidates = [
      path.join(__dirname, 'offline-data.json'),
      path.join(path.resolve(path.join(__dirname, '..')), 'offline-data.json'),
      path.join(__dirname, 'offline-data.csv'),
      path.join(path.resolve(path.join(__dirname, '..')), 'offline-data.csv'),
    ];
    for (const p of candidates) { if (fs.existsSync(p)) { src = p; break; } }
    if (!src) throw new Error('No OFFLINE_SOURCE provided and no offline-data.json/csv found.');
  }
  if (/^https?:\/\//i.test(src)) {
    const res = await fetch(src);
    if (!res.ok) throw new Error(`Failed to fetch offline source ${res.status}`);
    content = await res.text();
  } else {
    content = fs.readFileSync(src, 'utf8');
  }

  // Detect JSON vs CSV by first non-whitespace char / extension
  const isJSON = src.toLowerCase().endsWith('.json') || content.trim().startsWith('{') || content.trim().startsWith('[');
  if (isJSON) {
    const obj = JSON.parse(content);
    return await importOfflineFromJSON(obj);
  } else {
    return await importOfflineFromCSV(content);
  }
}

async function main() {
  const dataByRegion = { season: 0, data: {} };
  if (!ONLINE_MODE) {
    console.log('Running in OFFLINE mode. Loading data from file/URL...');
    const offline = await loadOfflineSource();
    dataByRegion.season = offline.season || 0;
    dataByRegion.data = offline.data || {};
  } else {
    for (const region of REGIONS) {
    if (!REGION_CONFIG[region]) {
      console.warn(`Unknown region '${region}', skipping.`);
      continue;
    }
    const { access_token } = await getToken(region);
    const seasonId = await getCurrentSeason(region, access_token);
    if (!dataByRegion.season) dataByRegion.season = seasonId;
    dataByRegion.data[region] = {};
    let indexCache = null;
    try {
      indexCache = await getLeaderboardsIndex(region, seasonId, access_token);
    } catch (e) {
      console.warn(`Could not load leaderboard index for ${region}: ${e.message}. Falling back to probes.`);
    }
    for (const bracket of BRACKETS) {
      try {
        const slug = await resolveBracketSlug(region, seasonId, bracket, access_token, indexCache);
        if (!slug) {
          console.warn(`Bracket '${bracket}' not available in ${region} for season ${seasonId}, skipping.`);
          continue;
        }
        const lb = await fetchLeaderboard(region, seasonId, slug, access_token);
        const map = {};
        if (Array.isArray(lb.entries)) {
          for (const e of lb.entries) {
            const key = normalizeNameRealm(e);
            const rating = e.rating || e.pvp_rating || 0;
            const rank = e.rank || e.rating_position || 0;
            // Initialize record
            const existing = map[key] || { rating: 0, rank: 0 };
            // Best overall (keep for backward compatibility and for non-spec brackets)
            if (!existing.rank || (rank && rank < existing.rank)) {
              existing.rating = rating;
              existing.rank = rank;
            }
            // Per-spec aggregation for Shuffle/Blitz only when specialization is provided
            if (bracket === 'shuffle' || bracket === 'blitz') {
              let specName = null;
              if (e.specialization && (e.specialization.name || e.specialization.display_string)) {
                specName = e.specialization.name || e.specialization.display_string;
              } else if (e.character && e.character.specialization && (e.character.specialization.name || e.character.specialization.display_string)) {
                specName = e.character.specialization.name || e.character.specialization.display_string;
              }
              const slug = slugifySpecName(specName);
              if (slug) {
                if (!existing.bySpec) existing.bySpec = {};
                const cur = existing.bySpec[slug];
                if (!cur || (rank && rank < (cur.rank || 0))) {
                  existing.bySpec[slug] = { rating, rank };
                }
              }
            }
            map[key] = existing;
          }
        }
        dataByRegion.data[region][bracket] = map;
      } catch (err) {
        console.error(`Failed ${region} ${bracket}:`, err.message);
      }
    }
  }
  }

  const lua = buildLua(dataByRegion);
  // Determine output path: prefer WOW_ADDON_DIR if set, else repo root
  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);
}

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