/* eslint-disable no-console */
const fs = require('fs');
const path = require('path');

const { loadConfig } = require('../src/generator.js');
const { createLogger, isVerbose, startTimer } = require('../src/generator/utils/logger');

const log = createLogger('sync:projects');

const DEFAULT_SETTINGS = {
  enabled: true,
  cacheDir: 'dev',
  fetch: {
    timeoutMs: 10_000,
    concurrency: 4,
    userAgent: 'MeNavProjectsSync/1.0',
  },
  colors: {
    url: 'https://raw.githubusercontent.com/ozh/github-colors/master/colors.json',
    maxAgeMs: 7 * 24 * 60 * 60 * 1000,
  },
};

function parseBooleanEnv(value, fallback) {
  if (value === undefined || value === null || value === '') return fallback;
  const v = String(value).trim().toLowerCase();
  if (v === '1' || v === 'true' || v === 'yes' || v === 'y') return true;
  if (v === '0' || v === 'false' || v === 'no' || v === 'n') return false;
  return fallback;
}

function parseIntegerEnv(value, fallback) {
  if (value === undefined || value === null || value === '') return fallback;
  const n = Number.parseInt(String(value), 10);
  return Number.isFinite(n) ? n : fallback;
}

function getSettings(config) {
  const fromConfig =
    config && config.site && config.site.github && typeof config.site.github === 'object'
      ? config.site.github
      : {};

  const merged = {
    ...DEFAULT_SETTINGS,
    ...fromConfig,
    fetch: {
      ...DEFAULT_SETTINGS.fetch,
      ...(fromConfig.fetch || {}),
    },
    colors: {
      ...DEFAULT_SETTINGS.colors,
      ...(fromConfig.colors || {}),
    },
  };

  merged.enabled = parseBooleanEnv(process.env.PROJECTS_ENABLED, merged.enabled);
  merged.cacheDir = process.env.PROJECTS_CACHE_DIR
    ? String(process.env.PROJECTS_CACHE_DIR)
    : merged.cacheDir;
  merged.fetch.timeoutMs = parseIntegerEnv(
    process.env.PROJECTS_FETCH_TIMEOUT,
    merged.fetch.timeoutMs
  );
  merged.fetch.concurrency = parseIntegerEnv(
    process.env.PROJECTS_FETCH_CONCURRENCY,
    merged.fetch.concurrency
  );

  merged.fetch.timeoutMs = Math.max(1_000, merged.fetch.timeoutMs);
  merged.fetch.concurrency = Math.max(1, Math.min(10, merged.fetch.concurrency));

  return merged;
}

function ensureDir(dirPath) {
  fs.mkdirSync(dirPath, { recursive: true });
}

function isGithubRepoUrl(url) {
  if (!url) return null;
  try {
    const u = new URL(String(url));
    if (u.protocol !== 'https:' && u.protocol !== 'http:') return null;
    if (u.hostname.toLowerCase() !== 'github.com') return null;
    const parts = u.pathname.split('/').filter(Boolean);
    if (parts.length < 2) return null;
    const owner = parts[0];
    const repo = parts[1].replace(/\.git$/i, '');
    if (!owner || !repo) return null;
    return { owner, repo, canonicalUrl: `https://github.com/${owner}/${repo}` };
  } catch {
    return null;
  }
}

function collectSitesRecursively(node, output) {
  if (!node || typeof node !== 'object') return;
  if (Array.isArray(node.subcategories))
    node.subcategories.forEach((child) => collectSitesRecursively(child, output));
  if (Array.isArray(node.groups))
    node.groups.forEach((child) => collectSitesRecursively(child, output));
  if (Array.isArray(node.subgroups))
    node.subgroups.forEach((child) => collectSitesRecursively(child, output));
  if (Array.isArray(node.sites)) node.sites.forEach((site) => output.push(site));
}

function findProjectsPages(config) {
  const pages = [];
  const nav = Array.isArray(config.navigation) ? config.navigation : [];
  nav.forEach((item) => {
    const pageId = item && item.id ? String(item.id) : '';
    if (!pageId || !config[pageId]) return;
    const page = config[pageId];
    const templateName = page && page.template ? String(page.template) : pageId;
    if (templateName !== 'projects') return;
    pages.push({ pageId, page });
  });
  return pages;
}

async function fetchJsonWithTimeout(url, { timeoutMs, headers }) {
  const controller = new AbortController();
  const timer = setTimeout(() => controller.abort(), timeoutMs);
  try {
    const response = await fetch(url, { method: 'GET', headers, signal: controller.signal });
    if (!response.ok) throw new Error(`HTTP ${response.status}`);
    return await response.json();
  } finally {
    clearTimeout(timer);
  }
}

async function loadLanguageColors(settings, cacheBaseDir) {
  const cachePath = path.join(cacheBaseDir, 'github-colors.json');

  try {
    const stat = fs.existsSync(cachePath) ? fs.statSync(cachePath) : null;
    if (stat && stat.mtimeMs && Date.now() - stat.mtimeMs < settings.colors.maxAgeMs) {
      const raw = fs.readFileSync(cachePath, 'utf8');
      const parsed = JSON.parse(raw);
      if (parsed && typeof parsed === 'object') return parsed;
    }
  } catch {
    // 继续联网抓取
  }

  try {
    const headers = { 'user-agent': settings.fetch.userAgent, accept: 'application/json' };
    const colors = await fetchJsonWithTimeout(settings.colors.url, {
      timeoutMs: settings.fetch.timeoutMs,
      headers,
    });
    if (colors && typeof colors === 'object') {
      fs.writeFileSync(cachePath, JSON.stringify(colors, null, 2), 'utf8');
      return colors;
    }
  } catch (error) {
    log.warn('获取语言颜色表失败（将不输出 languageColor）', {
      message: String(error && error.message ? error.message : error),
    });
  }

  return {};
}

async function fetchRepoMeta(repo, settings, colors) {
  const headers = {
    'user-agent': settings.fetch.userAgent,
    accept: 'application/vnd.github+json',
  };

  const apiUrl = `https://api.github.com/repos/${repo.owner}/${repo.repo}`;
  const data = await fetchJsonWithTimeout(apiUrl, { timeoutMs: settings.fetch.timeoutMs, headers });

  const language = data && data.language ? String(data.language) : '';
  const stars = data && Number.isFinite(data.stargazers_count) ? data.stargazers_count : null;
  const forks = data && Number.isFinite(data.forks_count) ? data.forks_count : null;

  let languageColor = '';
  if (language && colors && colors[language] && colors[language].color) {
    languageColor = String(colors[language].color);
  }

  return {
    url: repo.canonicalUrl,
    fullName: data && data.full_name ? String(data.full_name) : `${repo.owner}/${repo.repo}`,
    language,
    languageColor,
    stars,
    forks,
  };
}

async function runPool(items, concurrency, worker) {
  const results = [];
  let index = 0;

  async function runOne() {
    while (index < items.length) {
      const current = items[index];
      index += 1;
      // eslint-disable-next-line no-await-in-loop
      const result = await worker(current);
      if (result) results.push(result);
    }
  }

  const runners = Array.from({ length: Math.min(concurrency, items.length) }, () => runOne());
  await Promise.all(runners);
  return results;
}

async function main() {
  const elapsedMs = startTimer();
  const config = loadConfig();
  const settings = getSettings(config);

  log.info('开始');

  if (!settings.enabled) {
    log.ok('projects 仓库同步已禁用，跳过', { env: 'PROJECTS_ENABLED=false' });
    return;
  }

  const cacheBaseDir = path.isAbsolute(settings.cacheDir)
    ? settings.cacheDir
    : path.join(process.cwd(), settings.cacheDir);
  ensureDir(cacheBaseDir);

  const colors = await loadLanguageColors(settings, cacheBaseDir);
  const pages = findProjectsPages(config);

  if (!pages.length) {
    log.ok('未找到 template=projects 的页面，跳过同步');
    return;
  }

  log.info('准备同步 projects 页面缓存', { pages: pages.length });

  let pageSuccess = 0;
  let pageFailed = 0;

  for (const { pageId, page } of pages) {
    const categories = Array.isArray(page.categories) ? page.categories : [];
    const sites = [];
    categories.forEach((category) => collectSitesRecursively(category, sites));

    const repos = sites
      .map((site) => (site && site.url ? isGithubRepoUrl(site.url) : null))
      .filter(Boolean);

    const unique = new Map();
    repos.forEach((r) => unique.set(r.canonicalUrl, r));
    const repoList = Array.from(unique.values());

    if (!repoList.length) {
      log.ok('页面未发现 GitHub 仓库链接，跳过', { page: pageId });
      continue;
    }

    let success = 0;
    let failed = 0;

    const results = await runPool(repoList, settings.fetch.concurrency, async (repo) => {
      try {
        const meta = await fetchRepoMeta(repo, settings, colors);
        success += 1;
        return meta;
      } catch (error) {
        failed += 1;
        log.warn('拉取仓库元信息失败（best-effort）', {
          repo: repo.canonicalUrl,
          message: String(error && error.message ? error.message : error),
        });
        return null;
      }
    });

    const payload = {
      version: '1.0',
      pageId,
      generatedAt: new Date().toISOString(),
      repos: results,
      stats: {
        totalRepos: repoList.length,
        success,
        failed,
      },
    };

    const cachePath = path.join(cacheBaseDir, `${pageId}.repo-cache.json`);
    fs.writeFileSync(cachePath, JSON.stringify(payload, null, 2), 'utf8');

    if (failed === 0) pageSuccess += 1;
    else pageFailed += 1;

    log.ok('页面同步完成', {
      page: pageId,
      success,
      failed,
      cache: cachePath,
    });
  }

  log.ok('完成', { ms: elapsedMs(), pages: pages.length, pageSuccess, pageFailed });
}

main().catch((error) => {
  log.error('执行异常（best-effort，不阻断后续 build）', {
    message: error && error.message ? error.message : String(error),
  });
  if (isVerbose() && error && error.stack) console.error(error.stack);
  process.exitCode = 0; // best-effort：不阻断后续 build
});
