'use strict';
const common = require('../common');
const fs = require('fs');
const assert = require('assert');
const tmpdir = require('../common/tmpdir');
const file = tmpdir.resolve('read_stream_filehandle_test.txt');
const input = 'hello world';

tmpdir.refresh();
fs.writeFileSync(file, input);

fs.promises.open(file, 'r').then((handle) => {
  handle.on('close', common.mustCall());
  const stream = fs.createReadStream(null, { fd: handle });

  let output = '';
  stream.on('data', common.mustCallAtLeast((data) => {
    output += data;
  }));

  stream.on('end', common.mustCall(() => {
    assert.strictEqual(output, input);
  }));

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

fs.promises.open(file, 'r').then((handle) => {
  handle.on('close', common.mustCall());
  const stream = fs.createReadStream(null, { fd: handle });
  stream.on('data', common.mustNotCall());
  stream.on('close', common.mustCall());

  return handle.close();
}).then(common.mustCall());

fs.promises.open(file, 'r').then((handle) => {
  handle.on('close', common.mustCall());
  const stream = fs.createReadStream(null, { fd: handle });
  stream.on('close', common.mustCall());

  stream.on('data', common.mustCall(() => {
    handle.close();
  }));
}).then(common.mustCall());

fs.promises.open(file, 'r').then((handle) => {
  handle.on('close', common.mustCall());
  const stream = fs.createReadStream(null, { fd: handle });
  stream.on('close', common.mustCall());

  stream.close();
}).then(common.mustCall());

fs.promises.open(file, 'r').then((handle) => {
  assert.throws(() => {
    fs.createReadStream(null, { fd: handle, fs });
  }, {
    code: 'ERR_METHOD_NOT_IMPLEMENTED',
    name: 'Error',
    // message: 'The FileHandle with fs method is not implemented'
  });
  return handle.close();
}).then(common.mustCall());

fs.promises.open(file, 'r').then((handle) => {
  const { read: originalReadFunction } = handle;
  handle.read = common.mustCallAtLeast(function read() {
    return Reflect.apply(originalReadFunction, this, arguments);
  });

  const stream = fs.createReadStream(null, { fd: handle });

  let output = '';
  stream.on('data', common.mustCallAtLeast((data) => {
    output += data;
  }));

  stream.on('end', common.mustCall(() => {
    assert.strictEqual(output, input);
  }));
}).then(common.mustCall());

// AbortSignal option test
fs.promises.open(file, 'r').then((handle) => {
  const controller = new AbortController();
  const { signal } = controller;
  const stream = handle.createReadStream({ signal });

  stream.on('data', common.mustNotCall());
  stream.on('end', common.mustNotCall());

  stream.on('error', common.mustCall((err) => {
    assert.strictEqual(err.name, 'AbortError');
  }));

  stream.on('close', common.mustCall(() => {
    handle.close();
  }));

  controller.abort();
}).then(common.mustCall());

// Already-aborted signal test
fs.promises.open(file, 'r').then((handle) => {
  const signal = AbortSignal.abort();
  const stream = handle.createReadStream({ signal });

  stream.on('data', common.mustNotCall());
  stream.on('end', common.mustNotCall());

  stream.on('error', common.mustCall((err) => {
    assert.strictEqual(err.name, 'AbortError');
  }));

  stream.on('close', common.mustCall(() => {
    handle.close();
  }));
}).then(common.mustCall());

// Invalid signal type test
fs.promises.open(file, 'r').then((handle) => {
  for (const signal of [1, {}, [], '', null, NaN, 1n, () => {}, Symbol(), false, true]) {
    assert.throws(() => {
      handle.createReadStream({ signal });
    }, {
      code: 'ERR_INVALID_ARG_TYPE',
      name: 'TypeError',
    });
  }
  return handle.close();
}).then(common.mustCall()).catch(console.error);

// Custom abort reason test
fs.promises.open(file, 'r').then((handle) => {
  const controller = new AbortController();
  const { signal } = controller;
  const reason = new Error('some silly abort reason');
  const stream = handle.createReadStream({ signal });

  stream.on('data', common.mustNotCall());
  stream.on('end', common.mustNotCall());

  stream.on('error', common.mustCall((err) => {
    assert.strictEqual(err.name, 'AbortError');
    assert.strictEqual(err.cause, reason);
  }));

  stream.on('close', common.mustCall(() => {
    handle.close();
  }));

  controller.abort(reason);
}).then(common.mustCall());
