/*
 * Copyright (C) 2019 StApps
 * This program is free software: you can redistribute it and/or modify it
 * under the terms of the GNU General Public License as published by the Free
 * Software Foundation, version 3.
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
 * more details.
 *
 * You should have received a copy of the GNU General Public License along with
 * this program. If not, see <https://www.gnu.org/licenses/>.
 */
import {Converter} from '@openstapps/core-tools';
import chai from 'chai';
import {expect} from 'chai';
import chaiSpies from 'chai-spies';
import {HttpClient} from '@openstapps/api';
import {TestPlugin} from './plugin-resources/test-plugin.js';
import path from 'path';
import {readFile} from 'fs/promises';
import {fileURLToPath} from 'url';

chai.use(chaiSpies);

process.on('unhandledRejection', error => {
  throw error;
});

const sandbox = chai.spy.sandbox();

const httpClient = new HttpClient();

const dirname = path.dirname(fileURLToPath(import.meta.url));

describe('Plugin', function () {
  this.timeout(20_000);

  let testPlugin: TestPlugin;
  let constructTestPlugin: TestPlugin | undefined;

  beforeEach(function () {
    testPlugin = new TestPlugin(
      4000,
      '',
      '',
      '',
      '',
      {
        getSchema: () => {
          /***/
        },
      } as never,
      '',
      '',
      '',
    );
  });

  afterEach(async function () {
    try {
      await testPlugin.close();
      await constructTestPlugin?.close();
    } catch {}
    constructTestPlugin = undefined;
    sandbox.restore();
  });

  it('should construct', async function () {
    const converter = new Converter(
      dirname,
      path.resolve(dirname, 'plugin-resources', 'test-plugin-response.ts'),
    );

    sandbox.on(converter, 'getSchema', schemaName => {
      return {$id: schemaName};
    });

    constructTestPlugin = new TestPlugin(
      4001,
      'A',
      'http://B',
      '/C', // This doesn't matter for our tests. It's only something that affects the backend
      'http://D',
      converter,
      'PluginTestRequest',
      'PluginTestResponse',
      JSON.parse(await readFile(path.resolve(dirname, '..', 'package.json'), 'utf8')).version,
    );
    expect(constructTestPlugin.port).to.be.equal(4001);
    expect(constructTestPlugin.name).to.be.equal('A');
    expect(constructTestPlugin.url).to.be.equal('http://B');
    expect(constructTestPlugin.route).to.be.equal('/C');
    // @ts-expect-error private property
    expect(constructTestPlugin.backendUrl).to.be.equal('http://D');
    // schemas are already covered, together with the directory and version
    // @ts-expect-error private property
    expect(constructTestPlugin.active).to.be.equal(false);
    expect(constructTestPlugin.requestSchema.$id).to.be.equal('PluginTestRequest');
    expect(constructTestPlugin.responseSchema.$id).to.be.equal('PluginTestResponse');

    sandbox.on(constructTestPlugin, 'onRouteInvoke');

    try {
      await httpClient.request({
        url: new URL('http://localhost:4001'),
      });
    } catch {}
    // onRouteInvoke is a protected method, but we need to access it from the outside to test it
    // @ts-expect-error protected method
    expect(constructTestPlugin.onRouteInvoke).not.to.have.been.called();
  });

  it('should have full url', async function () {
    constructTestPlugin = new TestPlugin(
      4001,
      '',
      'http://B',
      '',
      '',
      {
        getSchema: () => {
          /***/
        },
      } as never,
      '',
      '',
      '',
    );
    expect(constructTestPlugin.fullUrl).to.be.equal('http://B:4001');
    await constructTestPlugin.close();
  });

  it('should start', async function () {
    testPlugin.start();

    sandbox.on(testPlugin, 'onRouteInvoke');

    await httpClient.request({
      url: new URL('http://localhost:4000'),
    });

    // onRouteInvoke is a protected method, but we need to access it from the outside to test it
    // @ts-expect-error protected method
    expect(testPlugin.onRouteInvoke).to.have.been.called();
  });

  it('should stop', async function () {
    // simulate a normal use case by first starting the plugin and then stopping it
    testPlugin.start();
    testPlugin.stop();

    sandbox.on(testPlugin, 'onRouteInvoke');

    const response = await httpClient.request({
      url: new URL('http://localhost:4000'),
    });

    await expect(response.statusCode).to.be.equal(404);
    // onRouteInvoke is a protected method, but we need to access it from the outside to test it
    // @ts-expect-error protected method
    expect(testPlugin.onRouteInvoke).not.to.have.been.called();
  });
});
