import { describe, test, expect, beforeEach, afterEach } from 'vitest';
import { mkdtempSync, rmSync, mkdirSync, writeFileSync, existsSync, symlinkSync, lstatSync } from 'node:fs';
import { tmpdir } from 'node:os';
import { join, dirname } from 'node:path';
import { saveSession, loadSession, hasSession, clearSession, sessionHasAuth, profileDirFor, clearStaleSingletonLocks } from './session.js';

let dir: string;

beforeEach(() => {
  dir = mkdtempSync(join(tmpdir(), 'im-sess-'));
});
afterEach(() => {
  rmSync(dir, { recursive: true, force: true });
});

describe('session storage', () => {
  test('hasSession is false before anything is saved', () => {
    expect(hasSession(join(dir, 's.json'))).toBe(false);
  });

  test('save then load round-trips the storage state', () => {
    const path = join(dir, 's.json');
    const state = { cookies: [{ name: 'sessionid', value: 'abc' }], origins: [] };
    saveSession(path, state);
    expect(hasSession(path)).toBe(true);
    expect(loadSession(path)).toEqual(state);
  });

  test('save creates parent directories as needed', () => {
    const path = join(dir, 'nested', 'deep', 's.json');
    saveSession(path, { cookies: [], origins: [] });
    expect(hasSession(path)).toBe(true);
  });

  test('loadSession returns null when the file is missing', () => {
    expect(loadSession(join(dir, 'nope.json'))).toBeNull();
  });

  test('clearSession removes the file (and is a no-op when absent)', () => {
    const path = join(dir, 's.json');
    saveSession(path, { cookies: [], origins: [] });
    clearSession(path);
    expect(hasSession(path)).toBe(false);
    expect(() => clearSession(path)).not.toThrow();
  });

  test('sessionHasAuth: true only when a sessionid cookie is present', () => {
    const path = join(dir, 's.json');
    expect(sessionHasAuth(path)).toBe(false); // missing file
    // A real failed/abandoned login: pre-login cookies but no sessionid.
    saveSession(path, { cookies: [{ name: 'csrftoken', value: 'x' }, { name: 'mid', value: 'y' }], origins: [] });
    expect(sessionHasAuth(path)).toBe(false);
    saveSession(path, { cookies: [{ name: 'sessionid', value: 'abc123' }], origins: [] });
    expect(sessionHasAuth(path)).toBe(true);
    // sessionid present but empty doesn't count.
    saveSession(path, { cookies: [{ name: 'sessionid', value: '' }], origins: [] });
    expect(sessionHasAuth(path)).toBe(false);
  });
});

describe('profileDirFor (R3 persistent browser profile)', () => {
  test('is a chrome_profile dir next to the session file', () => {
    const p = profileDirFor('/data/@insta/insta.sqlite/../session.json');
    expect(p).toBe(join(dirname('/data/@insta/insta.sqlite/../session.json'), 'chrome_profile'));
  });
  // existsSync FOLLOWS symlinks (false for a dangling one) — use lstat to test the link itself.
  const linkExists = (p: string): boolean => {
    try { lstatSync(p); return true; } catch { return false; }
  };

  test('clearStaleSingletonLocks removes a DEAD-owner symlink lock, keeps profile identity', () => {
    const sessionPath = join(dir, 'session.json');
    const profile = profileDirFor(sessionPath);
    mkdirSync(profile, { recursive: true });
    // A force-killed run leaves SingletonLock as a dangling symlink "<host>-<pid>".
    symlinkSync('somehost-999999', join(profile, 'SingletonLock')); // pid 999999 = dead
    writeFileSync(join(profile, 'SingletonCookie'), '');
    writeFileSync(join(profile, 'Cookies'), 'warmed'); // real identity must survive
    clearStaleSingletonLocks(sessionPath);
    expect(linkExists(join(profile, 'SingletonLock'))).toBe(false); // dangling symlink unlinked
    expect(existsSync(join(profile, 'SingletonCookie'))).toBe(false);
    expect(existsSync(join(profile, 'Cookies'))).toBe(true); // identity preserved
    expect(() => clearStaleSingletonLocks(sessionPath)).not.toThrow(); // no-op when absent
  });

  test('clearStaleSingletonLocks LEAVES a live-owner lock (never clobbers a running browser)', () => {
    const sessionPath = join(dir, 'session.json');
    const profile = profileDirFor(sessionPath);
    mkdirSync(profile, { recursive: true });
    symlinkSync(`somehost-${process.pid}`, join(profile, 'SingletonLock')); // our own pid = alive
    clearStaleSingletonLocks(sessionPath);
    expect(linkExists(join(profile, 'SingletonLock'))).toBe(true); // live lock preserved
  });
  test('clearSession (logout) removes the persistent profile dir too', () => {
    const sessionPath = join(dir, 'session.json');
    saveSession(sessionPath, { cookies: [{ name: 'sessionid', value: 'x' }], origins: [] });
    const profile = profileDirFor(sessionPath);
    mkdirSync(profile, { recursive: true });
    writeFileSync(join(profile, 'Cookies'), 'blob'); // simulate a warmed profile
    expect(existsSync(sessionPath)).toBe(true);
    expect(existsSync(profile)).toBe(true);
    clearSession(sessionPath);
    expect(existsSync(sessionPath)).toBe(false); // storageState gone
    expect(existsSync(profile)).toBe(false); // persistent profile gone too
  });
});
