import { fixture, assert, html, nextFrame, aTimeout } from '@open-wc/testing';
import sinon from 'sinon';
import { AmfDefinitions, IProperty } from '@api-client/core/build/browser.js'
import { AmfLoader } from '../AmfLoader.js';
import { loadMonaco } from '../MonacoSetup.js';
import { EventTypes } from '../../../src/events/amf/EventTypes.js';
import { NavigationEvents } from '../../../src/events/amf/NavigationEvents.js';
import ApiRequestElement from '../../../src/elements/amf/ApiRequest.element.js';
import { DomEventsAmfStore } from '../../../src/index.js';
import '../../../src/define/amf/api-request.js';
import { ApiConsoleRequest, ApiConsoleResponse } from '../../../src/amf/types.js';

describe.skip('ApiRequestElement', () => {
  const store = new DomEventsAmfStore(window);
  store.listen();

  let loader: AmfLoader;
  before(async () => {
    await loadMonaco();
    loader = new AmfLoader();
  });


  async function basicFixture(domainId?: string): Promise<ApiRequestElement> {
    const element = (await fixture(html`<api-request .domainId="${domainId}"></api-request>`));
    await aTimeout(2);
    return element as ApiRequestElement;
  }

  async function proxyFixture(domainId?: string): Promise<ApiRequestElement> {
    const element = (await fixture(
      html`<api-request .domainId="${domainId}" proxy="https://proxy.domain.com/"></api-request>`
    ));
    await aTimeout(2);
    return element as ApiRequestElement;
  }

  async function proxyEncFixture(domainId?: string): Promise<ApiRequestElement> {
    const element = (await fixture(html`<api-request
    .domainId="${domainId}"
    proxy="https://proxy.domain.com/"
    proxyEncodeUrl></api-request>`));
    await aTimeout(2);
    return element as ApiRequestElement;
  }

  async function addHeadersFixture(domainId?: string): Promise<ApiRequestElement> {
    const headers: IProperty[] = [
      {
        "name": "x-test", 
        "value": "header-value",
        kind: 'Core#Property',
        type: 'string',
      }
    ];
    const element = (await fixture(html`<api-request .domainId="${domainId}" .appendHeaders="${headers}"></api-request>`));
    await aTimeout(2);
    return element as ApiRequestElement;
  }

  async function navigationFixture(domainId?: string): Promise<ApiRequestElement> {
    const element = (await fixture(
      html`<api-request .domainId="${domainId}" handleNavigationEvents></api-request>`
    ));
    await aTimeout(2);
    return element as ApiRequestElement;
  }

  function appendRequestData(element: ApiRequestElement, request?: ApiConsoleRequest): void {
    const editor = (element.shadowRoot!.querySelector('api-request-editor'))!;
    editor.url = request && request.url || 'https://domain.com';
  }

  describe('Initialization', () => {
    let model: AmfDefinitions.IAmfDocument;
    before(async () => {
      model = await loader.getGraph();
      store.amf = model;
    });

    it('can be constructed with document.createElement', () => {
      const panel = document.createElement('api-request');
      assert.ok(panel);
    });

    it('hasResponse is false', async () => {
      const element = await basicFixture();
      assert.isFalse(element.hasResponse);
    });

    it('api-request is dispatched', async () => {
      const method = loader.lookupOperation(model, '/people', 'get') as AmfDefinitions.IAmfOperation;
      const element = await basicFixture(method['@id']);
      appendRequestData(element);
      const spy = sinon.spy();
      element.addEventListener('api-request', spy);
      const editor = element.shadowRoot!.querySelector('api-request-editor')!;
      editor.execute();
      assert.isTrue(spy.called);
    });
  });

  describe('Proxy settings', () => {
    let model: AmfDefinitions.IAmfDocument;
    before(async () => {
      model = await loader.getGraph();
      store.amf = model;
    });

    it('changes URL in the api request event', async () => {
      const method = loader.lookupOperation(model, '/people', 'get') as AmfDefinitions.IAmfOperation;
      const element = await proxyFixture(method['@id']);
      const editor = element.shadowRoot!.querySelector('api-request-editor')!;

      const spy = sinon.spy();
      element.addEventListener(EventTypes.Request.apiRequest, spy);
      editor.execute();
      const { url } = element.shadowRoot!.querySelector('api-request-editor')!.serialize();
      assert.equal(
        spy.args[0][0].detail.url,
        `https://proxy.domain.com/${url}`
      );
    });

    it('encodes the original URL', async () => {
      const method = loader.lookupOperation(model, '/people', 'get') as AmfDefinitions.IAmfOperation;
      const element = await proxyEncFixture(method['@id']);
      appendRequestData(element);
      const spy = sinon.spy();
      element.addEventListener(EventTypes.Request.apiRequest, spy);
      const editor = element.shadowRoot!.querySelector('api-request-editor')!;
      editor.execute();
      const { url } = element.shadowRoot!.querySelector('api-request-editor')!.serialize();
      assert.equal(
        spy.args[0][0].detail.url,
        `https://proxy.domain.com/${encodeURIComponent(url)}`
      );
    });
  });

  describe('Headers settings', () => {
    let model: AmfDefinitions.IAmfDocument;
    before(async () => {
      model = await loader.getGraph();
      store.amf = model;
    });

    it('adds headers to the request', async () => {
      const method = loader.lookupOperation(model, '/people', 'get') as AmfDefinitions.IAmfOperation;
      const element = await addHeadersFixture(method['@id']);
      const spy = sinon.spy();
      element.addEventListener(EventTypes.Request.apiRequest, spy);
      const editor = element.shadowRoot!.querySelector('api-request-editor')!;
      editor.execute();
      assert.equal(spy.args[0][0].detail.headers, 'x-people-op-id: 9719fa6f-c666-48e0-a191-290890760b30\nx-rate-client-id: 5757gh76\nx-test: header-value');
    });

    it('replaces headers in the request', async () => {
      const method = loader.lookupOperation(model, '/people', 'get') as AmfDefinitions.IAmfOperation;
      const element = await addHeadersFixture(method['@id']);
      appendRequestData(element, {
        method: 'get',
        headers: 'x-test: other-value',
        url: '',
      });
      const spy = sinon.spy();
      element.addEventListener(EventTypes.Request.apiRequest, spy);
      const editor = element.shadowRoot!.querySelector('api-request-editor')!;
      editor.execute();
      assert.equal(spy.args[0][0].detail.headers, 'x-people-op-id: 9719fa6f-c666-48e0-a191-290890760b30\nx-rate-client-id: 5757gh76\nx-test: header-value');
    });
  });

  describe('Response handling', () => {
    function propagate(element: ApiRequestElement): void {
      const detail: ApiConsoleResponse = {
        id: '',
        request: {
          url: 'https://domain.com/',
          method: 'GET',
          headers: 'accept: text/plain',
        },
        response: {
          status: 200,
          statusText: 'OK',
          payload: 'Hello world',
          headers: 'content-type: text/plain',
        },
        loadingTime: 124.12345678,
        isError: false,
      };
      element.propagateResponse(detail);
    }
    let element: ApiRequestElement;
    beforeEach(async () => {
      element = await basicFixture();
    });

    it('request is set', () => {
      propagate(element);
      assert.typeOf(element.request, 'object');
    });

    it('response is set', () => {
      propagate(element);
      assert.typeOf(element.response, 'object');
    });

    it('Changing selection clears response', () => {
      propagate(element);
      element.domainId = 'test';
      assert.isUndefined(element.request);
      assert.isUndefined(element.response);
    });

    it('Calling clearResponse() clears response', () => {
      propagate(element);
      element.clearResponse();
      assert.isUndefined(element.request);
      assert.isUndefined(element.response);
    });
  });

  describe('Automated navigation', () => {
    let element: ApiRequestElement;

    beforeEach(async () => {
      element = await navigationFixture();
    });

    it('sets the "domainId" when the domainType is "operation"', () => {
      const id = '%2Ftest-parameters%2F%7Bfeature%7D/get';
      NavigationEvents.apiNavigate(document.body, id, 'operation');
      assert.equal(element.domainId, id);
    });

    it('"does not set the "domainId" when type is not "operation"', () => {
      const id = '%2Ftest-parameters%2F%7Bfeature%7D';
      NavigationEvents.apiNavigate(document.body, id, 'resource');
      assert.isUndefined(element.domainId);
    });
  });

  describe('[responseHandler]()', () => {
    let element: ApiRequestElement;
    const requestId = 'test-id';
    beforeEach(async () => {
      element = await basicFixture();
      element.lastRequestId = requestId;
    });

    const xhrResponse = {
      request: {
        url: 'https://domain.com/',
        method: 'GET',
        headers: 'accept: text/plain',
      },
      response: {
        status: 200,
        statusText: 'OK',
        payload: 'Hello world',
        headers: 'content-type: text/plain',
      },
      loadingTime: 124.12345678,
      isError: false,
      isXhr: true,
    };

    it('Does nothing when ID is different', () => {
      const spy = sinon.spy(element, 'propagateResponse');
      const e = new CustomEvent(EventTypes.Request.apiResponse, {
        detail: {
          id: 'otherId',
        },
      });
      // @ts-ignore
      element.responseHandler(e);
      assert.isFalse(spy.called);
    });

    it('Calls propagateResponse()', () => {
      const detail = { id: requestId, ...xhrResponse };
      const spy = sinon.spy(element, 'propagateResponse');
      const e = new CustomEvent(EventTypes.Request.apiResponse, {
        detail
      });
      // @ts-ignore
      element.responseHandler(e);
      assert.isTrue(spy.called);
      assert.deepEqual(spy.args[0][0], detail);
    });
  });

  describe('Change notification', () => {
    let model: AmfDefinitions.IAmfDocument;
    before(async () => {
      model = await loader.getGraph();
      store.amf = model;
    });

    it('dispatches the change event when the selected change', async () => {
      const method = loader.lookupOperation(model, '/people', 'get') as AmfDefinitions.IAmfOperation;
      const panel = await basicFixture(method['@id']);
      const spy = sinon.spy();
      panel.addEventListener('change', spy);
      const other = loader.lookupOperation(model, '/people', 'post') as AmfDefinitions.IAmfOperation;
      panel.domainId = other['@id'];
      await aTimeout(2);
      assert.isTrue(spy.called);
    });

    it('dispatches the change event when a value change', async () => {
      const method = loader.lookupOperation(model, '/people', 'get') as AmfDefinitions.IAmfOperation;
      const panel = await basicFixture(method['@id']);
      const spy = sinon.spy();
      panel.addEventListener('change', spy);
      const editor = panel.shadowRoot!.querySelector('api-request-editor')!;
      const input = (editor.shadowRoot!.querySelector('[name="x-people-op-id"]')) as HTMLInputElement;
      input.value = 'test';
      input.dispatchEvent(new Event('change'));
      await nextFrame();
      assert.isTrue(spy.called);
    });
  });

  describe('serialize()', () => {
    let model: AmfDefinitions.IAmfDocument;
    before(async () => {
      model = await loader.getGraph();
      store.amf = model;
    });

    it('serializes the current request', async () => {
      const method = loader.lookupOperation(model, '/people', 'get') as AmfDefinitions.IAmfOperation;
      const panel = await basicFixture(method['@id']);
      const result = panel.serialize();
      assert.include(result.url, '/people');
    });
  });
});
