/**
 * PR merge poller for auto-destroying worlds whose PR has merged.
 *
 * State machine per world:
 *   open -> merged (on GitHub reports merged)
 *   merged -> merged_destroyed (after grace period, if auto_destroy_on_merge)
 */

const GH_API_BASE = 'https://api.github.com';

/**
 * Parse owner, repo, and PR number from a GitHub PR URL.
 * @param {string} prUrl e.g. https://github.com/org/repo/pull/123
 * @returns {{ owner: string, repo: string, number: number } | null}
 */
function parsePrUrl(prUrl) {
  const m = /github\.com\/([^/]+)\/([^/]+)\/pull\/(\d+)/.exec(prUrl);
  if (!m) return null;
  return { owner: m[1], repo: m[2], number: parseInt(m[3], 10) };
}

/**
 * @param {{
 *   prStateStore: import('./world-pr-state.mjs').ReturnType<typeof createWorldPrStateStore>,
 *   getGhToken: () => Promise<string|null>,
 *   destroyWorld: (worldId: string) => Promise<void>,
 *   pollIntervalMs?: number,
 *   gracePeriodMs?: number,
 * }} opts
 */
export function createPrMergePoller({
  prStateStore,
  getGhToken,
  destroyWorld,
  pollIntervalMs = 300_000,
  gracePeriodMs = 600_000,
}) {
  let intervalId = null;
  let disabled = false;
  let warnedOnce = false;
  // Track in-flight grace timers so stop() can clear them
  const graceTimers = new Map();

  async function destroyAndMark(worldId) {
    const entry = prStateStore.get(worldId);
    const prUrl = entry?.pr_url ?? '(unknown)';
    const mergedAt = entry?.pr_merged_at ?? '(unknown)';
    console.log(
      `[pr-merge-poller] auto-destroyed world ${worldId}: PR ${prUrl} merged at ${mergedAt}, destroyed at ${new Date().toISOString()}`,
    );
    try {
      await destroyWorld(worldId);
    } catch (err) {
      console.error(`[pr-merge-poller] destroyWorld failed for ${worldId}:`, err.message);
    }
    prStateStore.set(worldId, { pr_state: 'merged_destroyed' });
    graceTimers.delete(worldId);
  }

  function scheduleGrace(worldId, entry) {
    if (graceTimers.has(worldId)) return; // already scheduled
    const id = setTimeout(() => {
      destroyAndMark(worldId).catch((err) => {
        console.error(`[pr-merge-poller] destroyAndMark error for ${worldId}:`, err.message);
      });
    }, gracePeriodMs);
    graceTimers.set(worldId, id);
  }

  async function checkPr(worldId, entry, ghToken) {
    const parsed = parsePrUrl(entry.pr_url);
    if (!parsed) {
      console.warn(`[pr-merge-poller] cannot parse PR URL for ${worldId}: ${entry.pr_url}`);
      return;
    }
    const apiUrl = `${GH_API_BASE}/repos/${parsed.owner}/${parsed.repo}/pulls/${parsed.number}`;
    let data;
    try {
      const resp = await fetch(apiUrl, {
        headers: { Authorization: `token ${ghToken}`, Accept: 'application/vnd.github+json' },
      });
      if (!resp.ok) {
        console.warn(`[pr-merge-poller] GH API ${resp.status} for ${worldId}`);
        return;
      }
      data = await resp.json();
    } catch (err) {
      console.warn(`[pr-merge-poller] fetch failed for ${worldId}:`, err.message);
      return;
    }

    const isMerged = data.state === 'closed' && data.merged_at != null;
    if (!isMerged) return;

    prStateStore.set(worldId, {
      pr_state: 'merged',
      pr_merged_at: data.merged_at,
    });

    if (entry.auto_destroy_on_merge === false) return;

    scheduleGrace(worldId, prStateStore.get(worldId));
  }

  async function pollOnce() {
    const ghToken = await getGhToken();
    if (!ghToken) {
      if (!warnedOnce) {
        console.warn(
          'pr-merge-poller: no GH token found (GH_TOKEN/GITHUB_TOKEN env or /gh-config/hosts.yml); PR polling disabled',
        );
        warnedOnce = true;
      }
      disabled = true;
      stop();
      return;
    }

    const worlds = prStateStore.getWorldsToWatch();
    for (const entry of worlds) {
      const { worldId, ...rest } = entry;
      if (rest.pr_state === 'open') {
        await checkPr(worldId, rest, ghToken);
      } else if (rest.pr_state === 'merged') {
        // Resume grace timer for merged entries that survived a restart
        if (rest.auto_destroy_on_merge !== false) {
          scheduleGrace(worldId, rest);
        }
      }
    }
  }

  function start() {
    if (intervalId !== null || disabled) return;
    intervalId = setInterval(() => {
      pollOnce().catch((err) => {
        console.error('[pr-merge-poller] pollOnce error:', err.message);
      });
    }, pollIntervalMs);
  }

  function stop() {
    if (intervalId !== null) {
      clearInterval(intervalId);
      intervalId = null;
    }
    for (const id of graceTimers.values()) {
      clearTimeout(id);
    }
    graceTimers.clear();
  }

  return { start, stop };
}
