import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
import {
  chmodSync,
  mkdirSync,
  mkdtempSync,
  renameSync,
  rmSync,
  symlinkSync,
  writeFileSync,
  type PathLike,
} from 'node:fs';
import type * as NodeFs from 'node:fs';
import type { Stats } from 'node:fs';
import { tmpdir } from 'node:os';
import { join, resolve } from 'node:path';

interface FilesystemRaceState {
  afterLstat?: (path: string) => void;
  afterOpen?: (path: string) => void;
  afterStat?: (path: string, stats: Stats) => Stats;
}

const filesystemRaceState = vi.hoisted<FilesystemRaceState>(() => ({}));

vi.mock('node:fs', async (importOriginal) => {
  const actual = await importOriginal<typeof NodeFs>();
  return {
    ...actual,
    lstatSync: (path: PathLike) => {
      const result = actual.lstatSync(path);
      filesystemRaceState.afterLstat?.(String(path));
      return result;
    },
    statSync: (path: PathLike) => {
      const result = actual.statSync(path);
      return filesystemRaceState.afterStat?.(String(path), result) ?? result;
    },
    openSync: (path: PathLike, flags: string | number, mode?: number) => {
      const fd = actual.openSync(path, flags, mode);
      filesystemRaceState.afterOpen?.(String(path));
      return fd;
    },
  };
});

import { assertCanonicalContainment, readRegularFileSecure } from './secure-file.js';

describe('secure file reads', () => {
  let root: string;

  beforeEach(() => {
    root = mkdtempSync(join(tmpdir(), 'mosaic-secure-file-'));
    filesystemRaceState.afterLstat = undefined;
    filesystemRaceState.afterOpen = undefined;
    filesystemRaceState.afterStat = undefined;
  });

  afterEach(() => {
    filesystemRaceState.afterLstat = undefined;
    filesystemRaceState.afterOpen = undefined;
    filesystemRaceState.afterStat = undefined;
    rmSync(root, { recursive: true, force: true });
  });

  it('rejects canonical path escape', () => {
    expect(() => assertCanonicalContainment(root, join(root, '..', 'outside'))).toThrow(
      'path escapes managed root',
    );
  });

  // stack#1380: the guard resolves symlinks and validates the resolved target
  // instead of refusing any symlink component.
  it('permits a symlink ancestor whose resolved target is inside the root', () => {
    const external = join(root, 'external');
    mkdirSync(external);
    writeFileSync(join(external, 'file'), 'external\n');
    symlinkSync(external, join(root, 'linked'));

    const snapshot = readRegularFileSecure(join(root, 'linked', 'file'), { root });
    expect(snapshot.content.toString('utf8')).toBe('external\n');
  });

  it('permits a symlinked file whose resolved target is inside the root', () => {
    const external = join(root, 'external-file');
    writeFileSync(external, 'external\n');
    symlinkSync(external, join(root, 'linked-file'));

    const snapshot = readRegularFileSecure(join(root, 'linked-file'), { root });
    expect(snapshot.content.toString('utf8')).toBe('external\n');
  });

  it('permits a symlink resolving into an additional sanctioned root (split-home roster shape)', () => {
    const brain = `${root}-brain`;
    mkdirSync(join(brain, 'fleet'), { recursive: true });
    writeFileSync(join(brain, 'fleet', 'roster.yaml'), 'roster\n', { mode: 0o600 });
    mkdirSync(join(root, 'fleet'));
    symlinkSync(join(brain, 'fleet', 'roster.yaml'), join(root, 'fleet', 'roster.yaml'));

    const snapshot = readRegularFileSecure(join(root, 'fleet', 'roster.yaml'), {
      root,
      symlinkTargetRoots: [brain],
    });
    expect(snapshot.content.toString('utf8')).toBe('roster\n');
  });

  it('refuses a symlink whose resolved target escapes every sanctioned root', () => {
    const outside = mkdtempSync(join(tmpdir(), 'mosaic-secure-outside-'));
    try {
      mkdirSync(join(root, 'fleet'), { recursive: true });
      writeFileSync(join(outside, 'roster.yaml'), 'escaped\n', { mode: 0o600 });
      symlinkSync(join(outside, 'roster.yaml'), join(root, 'fleet', 'roster.yaml'));

      expect(() => readRegularFileSecure(join(root, 'fleet', 'roster.yaml'), { root })).toThrow(
        /symlink target escapes managed roots/,
      );
    } finally {
      rmSync(outside, { recursive: true, force: true });
    }
  });

  it('refuses a group-writable symlink target', () => {
    const loose = join(root, 'loose');
    mkdirSync(loose);
    chmodSync(loose, 0o770); // group-writable bit survives umask via explicit chmod
    writeFileSync(join(loose, 'file'), 'loose\n');
    symlinkSync(loose, join(root, 'linked-loose'));

    expect(() => readRegularFileSecure(join(root, 'linked-loose', 'file'), { root })).toThrow(
      /group- or world-writable/,
    );
  });

  it('refuses a symlink target owned by another user', () => {
    const external = join(root, 'foreign');
    mkdirSync(external);
    writeFileSync(join(external, 'file'), 'foreign\n');
    symlinkSync(external, join(root, 'linked-foreign'));

    filesystemRaceState.afterStat = (path, stats): Stats => {
      if (resolve(path) === resolve(external)) {
        return { ...stats, uid: stats.uid + 4242 } as Stats;
      }
      return stats;
    };
    try {
      expect(() => readRegularFileSecure(join(root, 'linked-foreign', 'file'), { root })).toThrow(
        /not owned by the current user/,
      );
    } finally {
      filesystemRaceState.afterStat = undefined;
    }
  });

  it('keeps ancestor traversal bound when an opened directory is substituted', () => {
    const tools = join(root, 'tools');
    const displacedTools = join(root, 'tools.displaced');
    const external = join(root, 'external');
    const helper = join(tools, 'helper.sh');
    mkdirSync(tools);
    mkdirSync(external);
    writeFileSync(helper, 'trusted\n', { mode: 0o755 });
    writeFileSync(join(external, 'helper.sh'), 'external marker\n', { mode: 0o755 });

    let substituted = false;
    filesystemRaceState.afterOpen = (openedPath: string): void => {
      if (substituted || !openedPath.startsWith('/proc/self/fd/')) return;
      if (openedPath.split('/').at(-1) !== 'tools') return;
      substituted = true;
      renameSync(tools, displacedTools);
      symlinkSync(external, tools);
    };

    const result = readRegularFileSecure(helper, { root, executable: true });
    expect(substituted).toBe(true);
    expect(result.content.toString('utf8')).toBe('trusted\n');
  });

  it('keeps root selection bound when the opened root is substituted', () => {
    const displacedRoot = `${root}.displaced`;
    const externalRoot = `${root}.external`;
    const helper = join(root, 'helper.sh');
    mkdirSync(externalRoot);
    writeFileSync(helper, 'trusted root\n', { mode: 0o755 });
    writeFileSync(join(externalRoot, 'helper.sh'), 'external root marker\n', { mode: 0o755 });

    let substituted = false;
    filesystemRaceState.afterOpen = (openedPath: string): void => {
      if (substituted || !openedPath.startsWith('/proc/self/fd/')) return;
      const match = openedPath.match(/\/([^/]+)$/);
      if (match?.[1] !== root.split('/').filter(Boolean).at(-1)) return;
      substituted = true;
      renameSync(root, displacedRoot);
      symlinkSync(externalRoot, root);
    };

    const result = readRegularFileSecure(helper, { root, executable: true });
    expect(substituted).toBe(true);
    expect(result.content.toString('utf8')).toBe('trusted root\n');
    filesystemRaceState.afterOpen = undefined;
    rmSync(root);
    renameSync(displacedRoot, root);
  });

  it('keeps target read and execute validation bound to the opened file', () => {
    const file = join(root, 'helper.sh');
    const displaced = join(root, 'helper.displaced.sh');
    const external = join(root, 'external-helper.sh');
    writeFileSync(file, 'trusted target\n', { mode: 0o755 });
    writeFileSync(external, 'external target marker\n', { mode: 0o755 });

    let substituted = false;
    filesystemRaceState.afterOpen = (openedPath: string): void => {
      if (substituted || !openedPath.startsWith('/proc/self/fd/')) return;
      if (openedPath.split('/').at(-1) !== 'helper.sh') return;
      substituted = true;
      renameSync(file, displaced);
      symlinkSync(external, file);
    };

    const result = readRegularFileSecure(file, { root, executable: true });
    expect(substituted).toBe(true);
    expect(result.content.toString('utf8')).toBe('trusted target\n');
  });

  it('uses a stable redacted executable error while retaining the error code', () => {
    const file = join(root, 'helper.sh');
    writeFileSync(file, '#!/bin/sh\n', { mode: 0o644 });

    try {
      readRegularFileSecure(file, { root, executable: true });
      throw new Error('expected executable validation to fail');
    } catch (error) {
      expect(error).toMatchObject({ message: 'managed file is not executable', code: 'EACCES' });
      expect(String(error)).not.toContain('/proc/self/fd/');
      expect(String(error)).not.toContain(root);
    }
  });

  it('uses effective-identity execute access after regular-file validation', () => {
    const file = join(root, 'helper.sh');
    writeFileSync(file, '#!/bin/sh\n', { mode: 0o644 });
    expect(() => readRegularFileSecure(file, { root, executable: true })).toThrow();

    chmodSync(file, 0o755);
    expect(readRegularFileSecure(file, { root, executable: true }).content.toString()).toBe(
      '#!/bin/sh\n',
    );
  });
});
