const fs = require('fs');
const path = require('path');
const mkdirp = require('mkdirp');
const rimraf = require('rimraf');
const tempy = require('tempy');
const installFrom = require('../yorkie/src/install');
const uninstallFrom = require('../yorkie/src/uninstall');

function install(rootDir, dir) {
  installFrom(path.join(rootDir, dir));
}

function uninstall(rootDir, dir) {
  uninstallFrom(path.join(rootDir, dir));
}

function mkdir(rootDir, dir) {
  mkdirp.sync(path.join(rootDir, dir));
}

function writeFile(dir, filePath, data) {
  fs.writeFileSync(path.join(dir, filePath), data);
}

function readFile(dir, filePath) {
  return fs.readFileSync(path.join(dir, filePath), 'utf-8');
}

function exists(dir, filePath) {
  return fs.existsSync(path.join(dir, filePath));
}

describe('yorkie', () => {
  let dir;
  beforeEach(() => (dir = tempy.directory()));
  afterEach(() => rimraf.sync(dir));

  it('should support basic layout', () => {
    mkdir(dir, '.git/hooks');
    mkdir(dir, 'node_modules/@ne_fe/boot/yorkie');
    writeFile(dir, 'package.json', '{}');

    install(dir, '/node_modules/@ne_fe/boot/yorkie');
    const hook = readFile(dir, '.git/hooks/pre-commit');

    expect(hook).toMatch('#yorkie');
    expect(hook).toMatch('cd "."');
    expect(hook).toMatch('node "./node_modules/@ne_fe/boot/yorkie/src/runner.js" pre-commit');
    expect(hook).toMatch('--no-verify');

    const prepareCommitMsg = readFile(dir, '.git/hooks/prepare-commit-msg');
    expect(prepareCommitMsg).toMatch('cannot be bypassed');

    uninstall(dir, 'node_modules/@ne_fe/boot/yorkie');
    expect(exists(dir, '.git/hooks/pre-push')).toBeFalsy();
  });

  it('should not install git hooks when installed in sub directory', () => {
    mkdir(dir, '.git/hooks');
    mkdir(dir, 'A/B/node_modules/@ne_fe/boot/yorkie');
    writeFile(dir, 'A/B/package.json', '{}');

    install(dir, 'A/B/node_modules/@ne_fe/boot/yorkie');
    expect(exists(dir, '.git/hooks/pre-commit')).toBeFalsy();
  });

  it('should support git submodule', () => {
    mkdir(dir, '.git/modules/A/B');
    mkdir(dir, 'A/B/node_modules/@ne_fe/boot/yorkie');
    writeFile(dir, 'package.json', '{}');
    writeFile(dir, 'A/B/package.json', '{}');
    writeFile(dir, 'A/B/.git', 'git: ../../.git/modules/A/B');

    install(dir, 'A/B/node_modules/yorkie');
    const hook = readFile(dir, '.git/modules/A/B/hooks/pre-commit');

    expect(hook).toMatch('cd "."');

    uninstall(dir, 'A/B/node_modules/@ne_fe/boot/yorkie');
    expect(exists(dir, '.git/hooks/pre-push')).toBeFalsy();
  });

  it('should not install git hooks in submodule sub directory', () => {
    mkdir(dir, '.git/modules/A/B');
    mkdir(dir, 'A/B/C/node_modules/@ne_fe/boot/yorkie');
    writeFile(dir, 'package.json', '{}');
    writeFile(dir, 'A/B/C/package.json', '{}');
    writeFile(dir, 'A/B/.git', 'git: ../../.git/modules/A/B');

    install(dir, 'A/B/C/node_modules/@ne_fe/boot/yorkie');
    expect(exists(dir, '.git/modules/A/B/hooks/pre-commit')).toBeFalsy();
  });

  it('should support git worktrees', () => {
    mkdir(dir, '.git/worktrees/B');
    mkdir(dir, 'A/B/node_modules/@ne_fe/boot/yorkie');
    writeFile(dir, 'package.json', '{}');
    writeFile(dir, 'A/B/package.json', '{}');

    // Git path for worktrees is absolute
    const absolutePath = path.join(dir, '.git/worktrees/B');
    writeFile(dir, 'A/B/.git', `git: ${absolutePath}`);

    install(dir, 'A/B/node_modules/@ne_fe/boot/yorkie');
    const hook = readFile(dir, '.git/worktrees/B/hooks/pre-commit');

    expect(hook).toMatch('cd "."');

    uninstall(dir, 'A/B/node_modules/@ne_fe/boot/yorkie');
    expect(exists(dir, '.git/hooks/pre-commit')).toBeFalsy();
  });

  it('should not modify user hooks', () => {
    mkdir(dir, '.git/hooks');
    mkdir(dir, 'node_modules/@ne_fe/boot/yorkie');
    writeFile(dir, '.git/hooks/pre-push', 'foo');

    // Verify that it's not overwritten
    install(dir, 'node_modules/@ne_fe/boot/yorkie');
    const hook = readFile(dir, '.git/hooks/pre-push');
    expect(hook).toBe('foo');

    uninstall(dir, 'node_modules/@ne_fe/boot/yorkie');
    expect(exists(dir, '.git/hooks/pre-push')).toBeTruthy();
  });

  it('should not install from /node_modules/A/node_modules', () => {
    mkdir(dir, '.git/hooks');
    mkdir(dir, 'node_modules/A/node_modules/@ne_fe/boot/yorkie');

    install(dir, 'node_modules/A/node_modules/@ne_fe/boot/yorkie');
    expect(exists(dir, '.git/hooks/pre-push')).toBeFalsy();
  });

  it("should not crash if there's no .git directory", () => {
    mkdir(dir, 'node_modules/@ne_fe/boot/yorkie');

    expect(() => install(dir, 'node_modules/@ne_fe/boot/yorkie')).not.toThrow();
    expect(() => uninstall(dir, 'node_modules/@ne_fe/boot/yorkie')).not.toThrow();
  });

  it('should migrate existing scripts (ghooks)', () => {
    mkdir(dir, '.git/hooks');
    writeFile(dir, 'package.json', '{}');
    mkdir(dir, '/node_modules/@ne_fe/boot/yorkie');
    writeFile(
      dir,
      '.git/hooks/pre-commit',
      '// Generated by ghooks. Do not edit this file.'
    );

    install(dir, 'node_modules/@ne_fe/boot/yorkie');
    const hook = readFile(dir, '.git/hooks/pre-commit');
    expect(hook).toMatch('yorkie');
    expect(hook).not.toMatch('ghooks');
  });

  it('should migrate existing scripts (pre-commit)', () => {
    mkdir(dir, '.git/hooks');
    writeFile(dir, 'package.json', '{}');
    mkdir(dir, '/node_modules/@ne_fe/boot/yorkie');
    writeFile(dir, '.git/hooks/pre-commit', './node_modules/pre-commit/hook');

    install(dir, 'node_modules/@ne_fe/boot/yorkie');
    const hook = readFile(dir, '.git/hooks/pre-commit');
    expect(hook).toMatch('yorkie');
    expect(hook).not.toMatch('./node_modules/pre-commit/hook');
  });
});
