import fs from "node:fs";
import sinon from "sinon";
import { JSONReporter } from "../../lib/reporters/json.js";
import utils from "../../lib/utils.cjs";
import { Mocha, Suite, Runner, Test } from "../../index.js";

describe("JSON reporter", function () {
  var mocha;
  var suite;
  var runner;
  var testTitle = "json test 1";
  var testFile = "someTest.spec.js";
  var noop = function () {};

  beforeEach(function () {
    mocha = new Mocha({
      reporter: "json",
    });
    suite = new Suite("JSON suite", "root");
    runner = new Runner(suite);
  });

  afterEach(function () {
    sinon.restore();
  });

  describe("test results", function () {
    beforeEach(function () {
      var options = {};
      /* eslint-disable-next-line no-unused-vars */
      var mochaReporter = new mocha._reporter(runner, options);
    });

    beforeEach(function () {
      sinon.stub(process.stdout, "write").callsFake(noop);
    });

    it("should have 1 test failure", function (done) {
      var error = { message: "oh shit" };

      var test = new Test(testTitle, function (done) {
        done(new Error(error.message));
      });

      test.file = testFile;
      suite.addTest(test);

      runner.run(function (failureCount) {
        sinon.restore();
        expect(runner, "to satisfy", {
          testResults: {
            failures: [
              {
                title: testTitle,
                file: testFile,
                err: {
                  message: error.message,
                },
              },
            ],
          },
        });
        expect(failureCount, "to be", 1);
        done();
      });
    });

    it("should have 1 test pending", function (done) {
      var test = new Test(testTitle);
      test.file = testFile;
      suite.addTest(test);

      runner.run(function (failureCount) {
        sinon.restore();
        expect(runner, "to satisfy", {
          testResults: {
            pending: [
              {
                title: testTitle,
                file: testFile,
              },
            ],
          },
        });
        expect(failureCount, "to be", 0);
        done();
      });
    });

    it("should have 1 test pass", function (done) {
      const test = new Test(testTitle, () => {});

      test.file = testFile;
      suite.addTest(test);

      runner.run(function (failureCount) {
        sinon.restore();
        expect(runner, "to satisfy", {
          testResults: {
            passes: [
              {
                title: testTitle,
                file: testFile,
                speed: /(slow|medium|fast)/,
              },
            ],
          },
        });
        expect(failureCount, "to be", 0);
        done();
      });
    });

    it("should handle circular objects in errors", function (done) {
      var testTitle = "json test 1";
      function CircleError() {
        this.message = "oh shit";
        this.circular = this;
      }
      var error = new CircleError();

      var test = new Test(testTitle, function () {
        throw error;
      });

      test.file = testFile;
      suite.addTest(test);

      runner.run(function (failureCount) {
        sinon.restore();
        expect(runner, "to satisfy", {
          testResults: {
            failures: [
              {
                title: testTitle,
                file: testFile,
                err: {
                  message: error.message,
                },
              },
            ],
          },
        });
        expect(failureCount, "to be", 1);
        done();
      });
    });
  });

  describe('when "reporterOption.output" is provided', function () {
    var expectedDirName = "reports";
    var expectedFileName = "reports/test-results.json";
    var options = {
      reporterOption: {
        output: expectedFileName,
      },
    };

    beforeEach(function () {
      /* eslint-disable-next-line no-unused-vars */
      var mochaReporter = new mocha._reporter(runner, options);
    });

    beforeEach(function () {
      // Add one test to suite to avoid assertions against empty test results
      var test = new Test(testTitle, () => {});
      test.file = testFile;
      suite.addTest(test);
    });

    it("should write test results to file", function (done) {
      const fsMkdirSync = sinon.stub(fs, "mkdirSync");
      const fsWriteFileSync = sinon.stub(fs, "writeFileSync");

      fsWriteFileSync.callsFake(function (filename, content) {
        const expectedJson = JSON.stringify(runner.testResults, null, 2);
        expect(expectedFileName, "to be", filename);
        expect(content, "to be", expectedJson);
      });

      runner.run(function () {
        expect(
          fsMkdirSync.calledWith(expectedDirName, { recursive: true }),
          "to be true",
        );
        expect(fsWriteFileSync.calledOnce, "to be true");
        done();
      });
    });

    it("should warn and write test results to console", function (done) {
      const fsMkdirSync = sinon.stub(fs, "mkdirSync");
      const fsWriteFileSync = sinon.stub(fs, "writeFileSync");

      fsWriteFileSync.throws("unable to write file");

      const outLog = [];
      const fake = (chunk) => outLog.push(chunk);
      sinon.stub(process.stderr, "write").callsFake(fake);
      sinon.stub(process.stdout, "write").callsFake(fake);

      runner.run(function () {
        sinon.restore();
        expect(
          fsMkdirSync.calledWith(expectedDirName, { recursive: true }),
          "to be true",
        );
        expect(fsWriteFileSync.calledOnce, "to be true");
        expect(
          outLog[0],
          "to contain",
          `[mocha] writing output to "${expectedFileName}" failed:`,
        );
        expect(outLog[1], "to match", /"fullTitle": "JSON suite json test 1"/);
        done();
      });
    });

    it('should throw "unsupported error" in browser', function () {
      sinon.stub(utils, "isBrowser").callsFake(() => true);
      expect(
        () => new JSONReporter(runner, options),
        "to throw",
        "file output not supported in browser",
      );
    });
  });
});
