import {
  access,
  chmod,
  mkdtemp,
  readFile,
  rm,
  stat,
  writeFile,
} from 'node:fs/promises';
import os from 'node:os';
import path from 'node:path';
import { afterEach, describe, expect, test } from 'vitest';

import {
  removeMaterializedEnv,
  writeMaterializedEnv,
} from '../../src/materialize-env';

const tempDirs: string[] = [];

const mode = async (filePath: string) => (await stat(filePath)).mode & 0o777;

const exists = async (filePath: string) =>
  access(filePath).then(
    () => true,
    () => false,
  );

describe('materialize env', () => {
  afterEach(async () => {
    await Promise.all(
      tempDirs.map((dir) => rm(dir, { force: true, recursive: true })),
    );
    tempDirs.length = 0;
  });

  test('writes the env file 0600 at the returned path with quoted values', async () => {
    const repoDir = await mkdtemp(path.join(os.tmpdir(), 'spoon-env-'));
    tempDirs.push(repoDir);

    const envPath = await writeMaterializedEnv(repoDir, '.env.local', [
      { name: 'A', value: 'x' },
    ]);

    await expect(exists(envPath)).resolves.toBe(true);
    await expect(mode(envPath)).resolves.toBe(0o600);
    await expect(readFile(envPath, 'utf8')).resolves.toBe('A="x"\n');
  });

  test('tightens perms to 0600 and overwrites content when the env file pre-exists 0644', async () => {
    const repoDir = await mkdtemp(path.join(os.tmpdir(), 'spoon-env-'));
    tempDirs.push(repoDir);

    const preExisting = path.join(repoDir, '.env.local');
    // Pre-create the target with loose perms. umask may mask the writeFile
    // mode, so chmod explicitly and assert the precondition really is 0644.
    await writeFile(preExisting, 'OLD', { mode: 0o644 });
    await chmod(preExisting, 0o644);
    await expect(mode(preExisting)).resolves.toBe(0o644);

    const envPath = await writeMaterializedEnv(repoDir, '.env.local', [
      { name: 'A', value: 'x' },
    ]);

    // Overwrote the plaintext content AND tightened perms unconditionally.
    await expect(mode(envPath)).resolves.toBe(0o600);
    await expect(readFile(envPath, 'utf8')).resolves.toBe('A="x"\n');
  });

  test('removeMaterializedEnv deletes an existing file and no-ops when absent', async () => {
    const repoDir = await mkdtemp(path.join(os.tmpdir(), 'spoon-env-'));
    tempDirs.push(repoDir);

    const envPath = await writeMaterializedEnv(repoDir, '.env.local', [
      { name: 'A', value: 'x' },
    ]);
    await expect(exists(envPath)).resolves.toBe(true);

    await removeMaterializedEnv(envPath);
    await expect(exists(envPath)).resolves.toBe(false);

    // No-ops when the file is already gone or the path is undefined.
    await expect(removeMaterializedEnv(envPath)).resolves.toBeUndefined();
    await expect(removeMaterializedEnv(undefined)).resolves.toBeUndefined();
  });
});
