import { existsSync, mkdirSync, readFileSync, readlinkSync, rmSync, unlinkSync, writeFileSync } from 'node:fs';
import { dirname, join } from 'node:path';

/** process.kill(pid, 0) liveness probe. EPERM = the process exists but isn't ours (alive). */
function ownerAlive(pid: number): boolean {
  try {
    process.kill(pid, 0);
    return true;
  } catch (e) {
    return (e as NodeJS.ErrnoException).code === 'EPERM';
  }
}

/** Structural shape compatible with Playwright's BrowserContext.storageState(). */
export interface StorageState {
  cookies: unknown[];
  origins: unknown[];
}

export function hasSession(path: string): boolean {
  return existsSync(path);
}

export function saveSession(path: string, state: StorageState): void {
  mkdirSync(dirname(path), { recursive: true });
  writeFileSync(path, JSON.stringify(state), 'utf8');
}

export function loadSession(path: string): StorageState | null {
  if (!existsSync(path)) return null;
  try {
    return JSON.parse(readFileSync(path, 'utf8')) as StorageState;
  } catch {
    return null; // corrupt/partial session file → treat as no session (prompts re-login)
  }
}

/**
 * Persistent Chrome profile dir for R3 (launchPersistentContext) — a stable folder
 * next to the session file that keeps the warmed device identity (cookies, IndexedDB,
 * history) across runs so IG sees "the same trusted device", not a new login each time.
 */
export function profileDirFor(sessionPath: string): string {
  return join(dirname(sessionPath), 'chrome_profile');
}

/** Remove the persisted session (logout): the storageState file AND the persistent
 *  browser profile, so a logout truly forgets the account. No-op if absent. */
export function clearSession(path: string): void {
  rmSync(path, { force: true });
  rmSync(profileDirFor(path), { recursive: true, force: true });
}

/**
 * Clear Chromium's ProcessSingleton lock — but ONLY when it's genuinely stale — so real
 * Chrome (channel:'chrome') can launch instead of silently falling back to bundled Chromium
 * (a weaker, more-detectable fingerprint) after a force-killed prior run leaves the lock behind.
 *
 * Two subtleties the naive version got wrong (both verified empirically):
 *  - On POSIX `SingletonLock` is a SYMLINK whose target is "<hostname>-<pid>"; a crash leaves
 *    it DANGLING. `rmSync(path, {force:true})` is a NO-OP on a dangling symlink (it stats
 *    through the link, gets ENOENT, and `force` swallows it) — so we must `unlinkSync` the
 *    link itself.
 *  - If the owner pid is still ALIVE, a real browser (or an orphaned prior run) holds this
 *    profile RIGHT NOW. Deleting its lock would let a second Chrome open the same profile
 *    concurrently → cookie/identity corruption + two scrapes on one IG login. The manual
 *    collect paths do NOT hold the cross-process collect lock, so "no live browser" can't be
 *    assumed — we verify it from the lock's own pid and only clear a dead/absent owner.
 *
 * Best-effort and never throws: a lock-file hiccup must not break start() (launch just falls
 * back to bundled Chromium). ponytail: handles the symlink lock a crash leaves; a plain-file
 * or directory lock (which Chrome doesn't produce on crash) is left alone.
 */
export function clearStaleSingletonLocks(sessionPath: string): void {
  const dir = profileDirFor(sessionPath);
  let ownerPid = 0;
  try {
    const target = readlinkSync(join(dir, 'SingletonLock')); // throws if absent or not a symlink
    ownerPid = Number(target.slice(target.lastIndexOf('-') + 1));
  } catch {
    return; // no symlink lock present → nothing a crash left to clear
  }
  if (ownerPid && ownerAlive(ownerPid)) return; // a live browser owns this profile — never touch it
  for (const f of ['SingletonLock', 'SingletonCookie', 'SingletonSocket']) {
    try {
      unlinkSync(join(dir, f));
    } catch {
      /* absent or busy — best effort, must not break start() */
    }
  }
}

/**
 * Whether the saved session is an *authenticated* one — i.e. it carries the
 * `sessionid` auth cookie. A session file can exist with only pre-login cookies
 * (csrftoken/datr/mid…), which must NOT count as logged in.
 */
export function sessionHasAuth(path: string): boolean {
  const s = loadSession(path);
  if (!s || !Array.isArray(s.cookies)) return false;
  return s.cookies.some(
    (c): c is { name: string; value: string } =>
      typeof c === 'object' && c !== null && (c as { name?: unknown }).name === 'sessionid' && !!(c as { value?: unknown }).value,
  );
}
