// MediaWiki (РодоВики family wiki) tools for agap-mcp — kb#95.
// Target is family.alogins.net (МediaWiki), NOT Gitea. Follows the exact
// login->edit flow documented in the `rodowiki` skill: login token ->
// action=login -> session cookie -> CSRF token -> read/search/edit. Read
// operations also require login ($wgGroupPermissions['*']['read'] = false).
//
// Credentials: Vaultwarden org item "family.alogins.net" (username field
// holds the wiki account name "Claude", password field holds its password),
// fetched once at init like every other agap-mcp integration.
//
// Session handling: Node's fetch does not auto-manage cookies like curl's
// --cookie-jar, so a tiny module-level cookie jar (Map) is kept here and
// reused across calls -- this module is a singleton within the process, so
// one login session serves every MCP request for the process lifetime.
// withAuth() retries once on an auth-shaped failure (expired session) by
// forcing a fresh login, mirroring how a human re-running the skill's shell
// snippet would just log in again.

const BASE = () => (process.env.MEDIAWIKI_URL || 'http://localhost:8099').replace(/\/$/, '');

let _username = null;
let _password = null;
let _cookies = new Map();
let _loggedIn = false;

export function initMediaWiki(username, password) {
  _username = username;
  _password = password;
  console.log('MediaWiki: ready');
}

function cookieHeader() {
  return [..._cookies.entries()].map(([k, v]) => `${k}=${v}`).join('; ');
}

function storeCookies(res) {
  const setCookies =
    typeof res.headers.getSetCookie === 'function'
      ? res.headers.getSetCookie()
      : res.headers.get('set-cookie')
      ? [res.headers.get('set-cookie')]
      : [];
  for (const sc of setCookies) {
    const pair = sc.split(';')[0];
    const idx = pair.indexOf('=');
    if (idx > -1) _cookies.set(pair.slice(0, idx).trim(), pair.slice(idx + 1).trim());
  }
}

async function api(params, { method = 'GET' } = {}) {
  const url = new URL(`${BASE()}/api.php`);
  const headers = { Cookie: cookieHeader() };
  let body;
  if (method === 'GET') {
    for (const [k, v] of Object.entries(params)) url.searchParams.set(k, v);
  } else {
    body = new URLSearchParams(params);
    headers['Content-Type'] = 'application/x-www-form-urlencoded';
  }
  const res = await fetch(url, { method, headers, body });
  storeCookies(res);
  const data = await res.json();
  if (data.error) throw new Error(`MediaWiki API error: ${data.error.info || JSON.stringify(data.error)}`);
  return data;
}

async function login() {
  if (!_username || !_password) throw new Error('MediaWiki not initialized');
  const tokenData = await api({ action: 'query', meta: 'tokens', type: 'login', format: 'json' });
  const logintoken = tokenData.query.tokens.logintoken;
  const loginData = await api(
    { action: 'login', lgname: _username, lgpassword: _password, lgtoken: logintoken, format: 'json' },
    { method: 'POST' }
  );
  if (loginData.login?.result !== 'Success') {
    throw new Error(`MediaWiki login failed: ${loginData.login?.result || JSON.stringify(loginData)}`);
  }
}

async function withAuth(fn) {
  if (!_loggedIn) {
    await login();
    _loggedIn = true;
  }
  try {
    return await fn();
  } catch (e) {
    // Session likely expired mid-process: force a fresh login and retry once.
    if (/notloggedin|readapidenied|permissiondenied|mustbeloggedin/i.test(e.message)) {
      _loggedIn = false;
      await login();
      _loggedIn = true;
      return await fn();
    }
    throw e;
  }
}

function stripHtml(s) {
  return (s || '').replace(/<[^>]+>/g, '');
}

export async function wikiSearch(query, limit = 20) {
  if (!query || !query.trim()) throw new Error('query is required');
  return withAuth(async () => {
    const data = await api({
      action: 'query',
      list: 'search',
      srsearch: query,
      srlimit: String(limit),
      format: 'json',
    });
    return data.query.search.map((p) => ({
      title: p.title,
      snippet: stripHtml(p.snippet),
      wordcount: p.wordcount,
    }));
  });
}

export async function wikiRead(title) {
  if (!title || !title.trim()) throw new Error('title is required');
  return withAuth(async () => {
    const data = await api({
      action: 'query',
      titles: title,
      prop: 'revisions',
      rvprop: 'content',
      rvslots: 'main',
      format: 'json',
    });
    const page = Object.values(data.query.pages)[0];
    if (page.missing !== undefined) throw new Error(`Page not found: ${title}`);
    return page.revisions?.[0]?.slots?.main?.['*'] ?? '';
  });
}

export async function wikiEdit(title, text, summary) {
  if (!title || !title.trim()) throw new Error('title is required');
  if (text === undefined || text === null) throw new Error('text is required');
  return withAuth(async () => {
    const tokenData = await api({ action: 'query', meta: 'tokens', format: 'json' });
    const csrftoken = tokenData.query.tokens.csrftoken;
    const editData = await api(
      {
        action: 'edit',
        title,
        text,
        summary: summary || `Update ${title}`,
        token: csrftoken,
        format: 'json',
      },
      { method: 'POST' }
    );
    if (editData.edit?.result !== 'Success') {
      throw new Error(`MediaWiki edit failed: ${JSON.stringify(editData.edit || editData)}`);
    }
    return { title, result: editData.edit.result, newrevid: editData.edit.newrevid, oldrevid: editData.edit.oldrevid };
  });
}
