'use strict';

const common = require('../common');
const tmpdir = require('../common/tmpdir');
const assert = require('node:assert');
const {
  open,
  openSync,
  readFile,
  renameSync,
} = require('node:fs');
const { Utf8Stream } = require('node:fs');
const { join } = require('node:path');
const { isMainThread } = require('node:worker_threads');

tmpdir.refresh();
let fileCounter = 0;

if (isMainThread) {
  process.umask(0o000);
}

function getTempFile() {
  return join(tmpdir.path, `fastutf8stream-${process.pid}-${Date.now()}-${fileCounter++}.log`);
}

runTests(false);
runTests(true);

function runTests(sync) {

  {
    const dest = getTempFile();
    const stream = new Utf8Stream({ dest, sync });

    assert.ok(stream.write('hello world\n'));
    assert.ok(stream.write('something else\n'));

    const after = dest + '-moved';

    stream.once('drain', common.mustCall(() => {
      renameSync(dest, after);
      stream.reopen();

      stream.once('ready', common.mustCall(() => {
        assert.ok(stream.write('after reopen\n'));

        stream.once('drain', common.mustCall(() => {
          readFile(after, 'utf8', common.mustSucceed((data) => {
            assert.strictEqual(data, 'hello world\nsomething else\n');
            readFile(dest, 'utf8', common.mustSucceed((data) => {
              assert.strictEqual(data, 'after reopen\n');
              stream.end();
            }));
          }));
        }));
      }));
    }));
  }

  {
    const dest = getTempFile();
    const stream = new Utf8Stream({ dest, sync });

    assert.ok(stream.write('hello world\n'));
    assert.ok(stream.write('something else\n'));

    stream.reopen();
    stream.end();

    stream.on('close', common.mustCall());
  }

  {
    const dest = getTempFile();
    const stream = new Utf8Stream({ dest, minLength: 0, sync });

    assert.ok(stream.write('hello world\n'));
    assert.ok(stream.write('something else\n'));

    const after = dest + '-new';

    stream.once('drain', common.mustCall(() => {
      stream.reopen(after);
      assert.strictEqual(stream.file, after);

      stream.once('ready', common.mustCall(() => {
        assert.ok(stream.write('after reopen\n'));

        stream.once('drain', common.mustCall(() => {
          readFile(dest, 'utf8', common.mustSucceed((data) => {
            assert.strictEqual(data, 'hello world\nsomething else\n');
            readFile(after, 'utf8', common.mustSucceed((data) => {
              assert.strictEqual(data, 'after reopen\n');
              stream.end();
            }));
          }));
        }));
      }));
    }));
  }

  {
    let throwOnNextOpen = false;
    const err = new Error('open error');
    const fsOverride = {};
    if (sync) {
      fsOverride.openSync = function(...args) {
        if (throwOnNextOpen) {
          throwOnNextOpen = false;
          throw err;
        }
        return openSync(...args);
      };
    } else {
      fsOverride.open = function(file, flags, mode, cb) {
        if (throwOnNextOpen) {
          throwOnNextOpen = false;
          process.nextTick(() => cb(err));
          return;
        }
        return open(file, flags, mode, cb);
      };
    }

    const dest = getTempFile();
    const stream = new Utf8Stream({
      dest,
      sync,
      fs: fsOverride,
    });

    assert.ok(stream.write('hello world\n'));
    assert.ok(stream.write('something else\n'));

    const after = dest + '-moved';
    stream.on('error', common.mustCall());

    stream.once('drain', common.mustCall(() => {
      renameSync(dest, after);
      throwOnNextOpen = true;
      if (sync) {
        assert.throws(() => stream.reopen(), err);
      } else {
        stream.reopen();
      }

      setTimeout(common.mustCall(() => {
        assert.ok(stream.write('after reopen\n'));

        stream.end();
        stream.on('finish', common.mustCall(() => {
          readFile(after, 'utf8', common.mustSucceed((data) => {
            assert.strictEqual(data, 'hello world\nsomething else\nafter reopen\n');
          }));
        }));
      }), 10);
    }));
  }

  {
    const dest = getTempFile();
    const stream = new Utf8Stream({ dest, sync });

    assert.ok(stream.write('hello world\n'));
    assert.ok(stream.write('something else\n'));

    const after = dest + '-moved';
    stream.once('drain', common.mustCall(() => {
      renameSync(dest, after);
      stream.reopen();

      stream.once('drain', common.mustCall(() => {
        assert.ok(stream.write('after reopen\n'));

        stream.once('drain', common.mustCall(() => {
          readFile(after, 'utf8', common.mustSucceed((data) => {
            assert.strictEqual(data, 'hello world\nsomething else\n');
            readFile(dest, 'utf8', common.mustSucceed((data) => {
              assert.strictEqual(data, 'after reopen\n');
              stream.end();
            }));
          }));
        }));
      }));
    }));
  }
}
