import { describe, it, expect, beforeEach, afterEach } from 'vitest';
import { resolve, join } from 'path';
import { existsSync, mkdirSync, rmSync, writeFileSync, readFileSync } from 'fs';
import {
  atomicWriteJSON,
  readJSON,
  deleteFile,
  ensureDir,
  fileExists,
  listFiles,
  listDirectories,
} from './file-ops.js';

const TEST_DIR = resolve(process.cwd(), 'data', '.test-file-ops');

beforeEach(() => {
  mkdirSync(TEST_DIR, { recursive: true });
});

afterEach(() => {
  rmSync(TEST_DIR, { recursive: true, force: true });
});

describe('atomicWriteJSON', () => {
  it('should write JSON data to a file', async () => {
    const filePath = join(TEST_DIR, 'test.json');
    const data = { name: 'test', value: 42 };

    await atomicWriteJSON(filePath, data);

    expect(existsSync(filePath)).toBe(true);
    const content = JSON.parse(readFileSync(filePath, 'utf-8'));
    expect(content).toEqual(data);
  });

  it('should create parent directories if they do not exist', async () => {
    const filePath = join(TEST_DIR, 'nested', 'deep', 'test.json');
    await atomicWriteJSON(filePath, { ok: true });

    expect(existsSync(filePath)).toBe(true);
  });

  it('should overwrite existing file', async () => {
    const filePath = join(TEST_DIR, 'overwrite.json');
    await atomicWriteJSON(filePath, { v: 1 });
    await atomicWriteJSON(filePath, { v: 2 });

    const content = JSON.parse(readFileSync(filePath, 'utf-8'));
    expect(content).toEqual({ v: 2 });
  });

  it('should not leave temp files on success', async () => {
    const filePath = join(TEST_DIR, 'clean.json');
    await atomicWriteJSON(filePath, { clean: true });

    const files = require('fs').readdirSync(TEST_DIR) as string[];
    const tmpFiles = files.filter((f: string) => f.endsWith('.tmp'));
    expect(tmpFiles).toHaveLength(0);
  });
});


describe('readJSON', () => {
  it('should read and parse a valid JSON file', async () => {
    const filePath = join(TEST_DIR, 'read.json');
    writeFileSync(filePath, JSON.stringify({ hello: 'world' }), 'utf-8');

    const result = await readJSON<{ hello: string }>(filePath);
    expect(result).toEqual({ hello: 'world' });
  });

  it('should return null for non-existent file', async () => {
    const result = await readJSON(join(TEST_DIR, 'nope.json'));
    expect(result).toBeNull();
  });

  it('should return null for invalid JSON', async () => {
    const filePath = join(TEST_DIR, 'bad.json');
    writeFileSync(filePath, 'not valid json {{{', 'utf-8');

    const result = await readJSON(filePath);
    expect(result).toBeNull();
  });
});

describe('deleteFile', () => {
  it('should delete an existing file and return true', async () => {
    const filePath = join(TEST_DIR, 'delete-me.json');
    writeFileSync(filePath, '{}', 'utf-8');

    const result = await deleteFile(filePath);
    expect(result).toBe(true);
    expect(existsSync(filePath)).toBe(false);
  });

  it('should return false for non-existent file', async () => {
    const result = await deleteFile(join(TEST_DIR, 'ghost.json'));
    expect(result).toBe(false);
  });
});

describe('ensureDir', () => {
  it('should create a directory recursively', async () => {
    const dirPath = join(TEST_DIR, 'a', 'b', 'c');
    await ensureDir(dirPath);
    expect(existsSync(dirPath)).toBe(true);
  });

  it('should not throw if directory already exists', async () => {
    await ensureDir(TEST_DIR);
    await expect(ensureDir(TEST_DIR)).resolves.toBeUndefined();
  });
});

describe('fileExists', () => {
  it('should return true for existing file', () => {
    const filePath = join(TEST_DIR, 'exists.txt');
    writeFileSync(filePath, 'hi', 'utf-8');
    expect(fileExists(filePath)).toBe(true);
  });

  it('should return false for non-existent file', () => {
    expect(fileExists(join(TEST_DIR, 'nope.txt'))).toBe(false);
  });
});

describe('listFiles', () => {
  it('should list all files in a directory', async () => {
    writeFileSync(join(TEST_DIR, 'a.json'), '{}');
    writeFileSync(join(TEST_DIR, 'b.txt'), 'hi');

    const files = await listFiles(TEST_DIR);
    expect(files).toContain('a.json');
    expect(files).toContain('b.txt');
  });

  it('should filter by extension', async () => {
    const subDir = join(TEST_DIR, 'filter-test');
    mkdirSync(subDir, { recursive: true });
    writeFileSync(join(subDir, 'a.json'), '{}');
    writeFileSync(join(subDir, 'b.txt'), 'hi');

    const files = await listFiles(subDir, '.json');
    expect(files).toEqual(['a.json']);
  });

  it('should return empty array for non-existent directory', async () => {
    const files = await listFiles(join(TEST_DIR, 'nope'));
    expect(files).toEqual([]);
  });
});

describe('listDirectories', () => {
  it('should list only subdirectories', async () => {
    const subDir = join(TEST_DIR, 'dirs-test');
    mkdirSync(subDir, { recursive: true });
    mkdirSync(join(subDir, 'subdir1'), { recursive: true });
    mkdirSync(join(subDir, 'subdir2'), { recursive: true });
    writeFileSync(join(subDir, 'file.txt'), 'hi');

    const dirs = await listDirectories(subDir);
    expect(dirs).toContain('subdir1');
    expect(dirs).toContain('subdir2');
    expect(dirs).not.toContain('file.txt');
  });

  it('should return empty array for non-existent directory', async () => {
    const dirs = await listDirectories(join(TEST_DIR, 'nope'));
    expect(dirs).toEqual([]);
  });
});
