import { html, fixture, assert, nextFrame } from '@open-wc/testing'
import sinon from 'sinon'
import {
  AmfDefinitions,
  ApiDefinitions,
  IBasicAuthorization,
  IOAuth2Authorization,
  IPassThroughAuthorization,
  IRamlCustomAuthorization,
} from '@api-client/core/browser.js'
import { AmfLoader } from '../AmfLoader.js'
import ApiAuthorizationEditorElement, {
  SecurityMethods,
} from '../../../src/elements/amf/ApiAuthorizationEditor.element.js'
import '../../../src/define/amf/api-authorization-editor.js'

describe.skip('ApiAuthorizationEditorElement', () => {
  async function basicFixture(
    security?: ApiDefinitions.IApiSecurityRequirement
  ): Promise<ApiAuthorizationEditorElement> {
    const element = await fixture(
      html`<api-authorization-editor
        .security="${security}"
        oauth2RedirectUri="https://api.rdr.com"
      ></api-authorization-editor>`
    )
    return element as ApiAuthorizationEditorElement
  }

  let store: AmfLoader
  before(async () => {
    store = new AmfLoader()
  })

  function getSecurityRequirement(
    model: AmfDefinitions.IAmfDocument,
    path: string,
    method: string
  ): ApiDefinitions.IApiSecurityRequirement {
    const operation = store.getOperation(model, path, method)
    return operation.security[0]
  }

  describe('RAML tests', () => {
    let model: AmfDefinitions.IAmfDocument
    before(async () => {
      model = await store.getGraph('secured-api')
    })

    describe(`Basic method`, () => {
      const username = 'uname'
      let element: ApiAuthorizationEditorElement

      beforeEach(async () => {
        const security = getSecurityRequirement(model, '/basic', 'get')
        element = await basicFixture(security)
      })

      it('has "types" in the authorization object', () => {
        const methods = element.methods as SecurityMethods
        const { types } = methods
        assert.deepEqual(types, ['Basic Authentication'])
      })

      it('has "schemes" in the authorization object', () => {
        const methods = element.methods as SecurityMethods
        const { schemes } = methods
        assert.typeOf(schemes[0], 'object')
      })

      it('notifies changes when panel value change', () => {
        const form = element.shadowRoot!.querySelector('amf-basic-authorization')!
        const spy = sinon.spy()
        element.addEventListener('change', spy)
        form.config.username = username
        form.dispatchEvent(new CustomEvent('change'))
        assert.isTrue(spy.called)
      })

      it('element is not invalid before calling validation method', () => {
        assert.isUndefined(element.invalid)
      })

      it('element is invalid without username', () => {
        const result = element.validate()
        assert.isFalse(result, 'validation result is false')
        assert.isTrue(element.invalid, 'is invalid')
      })

      it('element is valid with username', async () => {
        const form = element.shadowRoot!.querySelector('amf-basic-authorization')!
        form.config.username = username
        form.dispatchEvent(new CustomEvent('change'))
        await nextFrame()
        const result = element.validate()
        assert.isTrue(result, 'validation result is true')
        assert.isFalse(element.invalid, 'is not invalid')
      })

      it('creates params with serialize()', async () => {
        const form = element.shadowRoot!.querySelector('amf-basic-authorization')!
        form.config.username = username
        form.dispatchEvent(new CustomEvent('change'))
        await nextFrame()
        const result = element.serialize()
        const cnf = result[0].config as IBasicAuthorization
        assert.equal(cnf.username, username, 'has username')
        assert.equal(cnf.password, '', 'has no password')
      })
    })

    describe(`Digest method`, () => {
      let element: ApiAuthorizationEditorElement

      beforeEach(async () => {
        const security = getSecurityRequirement(model, '/digest', 'get')
        element = await basicFixture(security)
      })

      it('has "types" in the authorization object', () => {
        const methods = element.methods as SecurityMethods
        const { types } = methods
        assert.deepEqual(types, ['Digest Authentication'])
      })

      it('has "schemes" in the authorization object', () => {
        const methods = element.methods as SecurityMethods
        const { schemes } = methods
        assert.typeOf(schemes[0], 'object')
      })

      it('renders no support label', () => {
        const form = element.shadowRoot!.querySelector('.no-support.digest')!
        assert.ok(form)
      })
    })

    describe(`Pass through method`, () => {
      let element: ApiAuthorizationEditorElement

      beforeEach(async () => {
        const security = getSecurityRequirement(model, '/passthrough', 'get')
        element = await basicFixture(security)
      })

      it('has "types" in the authorization object', () => {
        const methods = element.methods as SecurityMethods
        const { types } = methods
        assert.deepEqual(types, ['Pass Through'])
      })

      it('has "schemes" in the authorization object', () => {
        const methods = element.methods as SecurityMethods
        const { schemes } = methods
        assert.typeOf(schemes[0], 'object')
      })

      it('notifies changes when panel value change', () => {
        const form = element.shadowRoot!.querySelector('amf-passthrough-authorization')!
        const spy = sinon.spy()
        element.addEventListener('change', spy)
        form.updateHeader('api_key', 'test')
        form.dispatchEvent(new CustomEvent('change'))
        assert.isTrue(spy.called)
      })

      it('element is invalid without required values', () => {
        const result = element.validate()
        assert.isFalse(result, 'validation result is false')
        assert.isTrue(element.invalid, 'is invalid')
      })

      it('element is valid with required values', async () => {
        const form = element.shadowRoot!.querySelector('amf-passthrough-authorization')!
        form.updateHeader('api_key', 'test')
        form.updateQueryParameter('query', 'value')
        form.dispatchEvent(new CustomEvent('change'))
        await nextFrame()
        const result = element.validate()
        assert.isTrue(result, 'validation result is true')
        assert.isFalse(element.invalid, 'is not invalid')
      })

      it('produces authorization settings', async () => {
        const form = element.shadowRoot!.querySelector('amf-passthrough-authorization')!
        form.updateHeader('api_key', 'test')
        form.updateQueryParameter('query', 'value')
        form.dispatchEvent(new CustomEvent('change'))
        await nextFrame()
        const result = element.serialize()
        const [auth] = result
        const settings = auth.config as IPassThroughAuthorization

        assert.isTrue(auth.valid, 'valid is true')
        assert.isTrue(auth.enabled, 'enabled is true')
        assert.equal(auth.type, 'pass through', 'type is set')
        assert.typeOf(settings, 'object')

        assert.typeOf(settings.header, 'object', 'headers is set')
        assert.typeOf(settings.query, 'object', 'queryParameters is set')
      })
    })

    describe(`RAML Custom method`, () => {
      let element: ApiAuthorizationEditorElement

      beforeEach(async () => {
        const security = getSecurityRequirement(model, '/custom1', 'get')
        element = await basicFixture(security)
      })

      it('has "types" in the authorization object', () => {
        const methods = element.methods as SecurityMethods
        const { types } = methods
        assert.deepEqual(types, ['x-my-custom'])
      })

      it('has "schemes" in the authorization object', () => {
        const methods = element.methods as SecurityMethods
        const { schemes } = methods
        assert.typeOf(schemes[0], 'object')
      })

      it('notifies changes when panel value change', () => {
        const form = element.shadowRoot!.querySelector('amf-ramlcustom-authorization')!
        const spy = sinon.spy()
        element.addEventListener('change', spy)
        form.updateHeader('SpecialTokenHeader', 'test')
        form.dispatchEvent(new CustomEvent('change'))
        assert.isTrue(spy.called)
      })

      it('element is valid with required values', async () => {
        const form = element.shadowRoot!.querySelector('amf-ramlcustom-authorization')!
        form.updateHeader('SpecialTokenHeader', 'test')
        form.updateQueryParameter('debugTokenParam', 'Log')
        form.dispatchEvent(new CustomEvent('change'))
        await nextFrame()
        const result = element.validate()
        assert.isTrue(result, 'validation result is true')
        assert.isFalse(element.invalid, 'is not invalid')
      })

      it('produces authorization settings', async () => {
        const form = element.shadowRoot!.querySelector('amf-ramlcustom-authorization')!
        form.updateHeader('SpecialTokenHeader', 'test')
        form.updateQueryParameter('debugTokenParam', 'Log')
        form.dispatchEvent(new CustomEvent('change'))
        await nextFrame()
        const result = element.serialize()
        const [auth] = result
        const settings = auth.config as IRamlCustomAuthorization

        assert.isTrue(auth.valid, 'valid is true')
        assert.isTrue(auth.enabled, 'enabled is true')
        assert.equal(auth.type, 'custom', 'type is set')
        assert.typeOf(settings, 'object')
        assert.typeOf(settings.header, 'object', 'headers is set')
        assert.typeOf(settings.query, 'object', 'queryParameters is set')
      })
    })

    describe(`Oauth 2 method`, () => {
      describe('Basics', () => {
        let element: ApiAuthorizationEditorElement

        beforeEach(async () => {
          const security = getSecurityRequirement(model, '/oauth2', 'post')
          element = await basicFixture(security)
        })

        it('has "types" in the authorization object', () => {
          const methods = element.methods as SecurityMethods
          const { types } = methods
          assert.deepEqual(types, ['OAuth 2.0'])
        })

        it('has "schemes" in the authorization object', () => {
          const methods = element.methods as SecurityMethods
          const { schemes } = methods
          assert.typeOf(schemes[0], 'object')
        })

        it('notifies changes when panel value change', () => {
          const form = element.shadowRoot!.querySelector('amf-oauth2-authorization')!
          const spy = sinon.spy()
          element.addEventListener('change', spy)
          form.config.clientId = 'test'
          form.dispatchEvent(new CustomEvent('change'))
          assert.isTrue(spy.called)
        })

        it('element is invalid without required values', () => {
          const result = element.validate()
          assert.isFalse(result, 'validation result is false')
          assert.isTrue(element.invalid, 'is invalid')
        })

        it('element is valid with required values', async () => {
          const form = element.shadowRoot!.querySelector('amf-oauth2-authorization')!
          form.config.clientId = 'test-client-id'
          form.config.accessToken = 'test-token'
          form.dispatchEvent(new CustomEvent('change'))
          await nextFrame()
          const result = element.validate()
          assert.isTrue(result, 'validation result is true')
          assert.isFalse(element.invalid, 'is not invalid')
        })

        it('produces authorization settings', async () => {
          element.oauth2RedirectUri = 'https://rdr.com'
          const form = element.shadowRoot!.querySelector('amf-oauth2-authorization')!
          form.config.clientId = 'test-client-id'
          form.config.accessToken = 'test-token'
          form.dispatchEvent(new CustomEvent('change'))
          await nextFrame()
          const result = element.serialize()
          const [auth] = result
          const settings = auth.config as IOAuth2Authorization

          assert.isTrue(auth.valid, 'valid is true')
          assert.isTrue(auth.enabled, 'enabled is true')
          assert.equal(auth.type, 'oauth 2', 'type is set')
          assert.typeOf(settings, 'object')

          assert.equal(settings.grantType, 'implicit', 'grantType is set')
          assert.equal(settings.clientId, 'test-client-id', 'clientId is set')
          assert.equal(settings.accessToken, 'test-token', 'accessToken is set')
          assert.equal(settings.tokenType, 'Bearer', 'tokenType is set')
          assert.deepEqual(settings.scopes, ['profile', 'email'], 'scopes is set')
          assert.equal(settings.deliveryMethod, 'header', 'deliveryMethod is set')
          assert.equal(settings.deliveryName, 'Authorization', 'deliveryName is set')
          assert.equal(settings.authorizationUri, 'https://auth.com', 'authorizationUri is set')
          assert.equal(settings.redirectUri, element.oauth2RedirectUri, 'redirectUri is set')
        })
      })

      describe('serialize()', () => {
        const accessToken = 'test-token'
        const clientId = 'test-client-id'

        it('creates params', async () => {
          const security = getSecurityRequirement(model, '/oauth2', 'post')
          const element = await basicFixture(security)

          const form = element.shadowRoot!.querySelector('amf-oauth2-authorization')!
          form.config.clientId = clientId
          form.config.accessToken = accessToken
          form.dispatchEvent(new CustomEvent('change'))
          await nextFrame()
          const result = element.serialize()
          const [auth] = result
          const settings = auth.config as IOAuth2Authorization
          assert.equal(settings.accessToken, accessToken, 'has accessToken')
          assert.equal(settings.clientId, clientId, 'has clientId')
          assert.equal(settings.tokenType, 'Bearer', 'has tokenType')
          assert.equal(settings.grantType, 'implicit', 'has grantType')
          assert.deepEqual(settings.scopes, ['profile', 'email'], 'has scopes')
          assert.equal(settings.deliveryMethod, 'header', 'has deliveryMethod')
          assert.equal(settings.deliveryName, 'Authorization', 'has deliveryName')
          assert.equal(settings.authorizationUri, 'https://auth.com', 'has authorizationUri')
          assert.equal(settings.redirectUri, 'https://api.rdr.com', 'has redirectUri')
        })

        it('marks invalid when no token', async () => {
          const security = getSecurityRequirement(model, '/oauth2', 'post')
          const element = await basicFixture(security)

          const form = element.shadowRoot!.querySelector('amf-oauth2-authorization')!
          form.config.clientId = clientId
          form.dispatchEvent(new CustomEvent('change'))
          await nextFrame()
          const result = element.serialize()
          const [auth] = result
          const settings = auth.config as IOAuth2Authorization
          assert.isFalse(auth.valid, 'is not valid')
          assert.equal(settings.accessToken, '', 'has no accessToken')
        })

        it('respects delivery method (query)', async () => {
          const security = getSecurityRequirement(model, '/oauth2-query-delivery', 'get')
          const element = await basicFixture(security)

          const form = element.shadowRoot!.querySelector('amf-oauth2-authorization')!
          form.config.clientId = clientId
          form.config.clientSecret = 'test'
          form.config.accessToken = accessToken
          form.dispatchEvent(new CustomEvent('change'))
          await nextFrame()
          const result = element.serialize()
          const [auth] = result
          const settings = auth.config as IOAuth2Authorization

          assert.equal(settings.deliveryMethod, 'query', 'has deliveryMethod')
          assert.equal(settings.deliveryName, 'access_token', 'has deliveryName')
        })

        it('respects delivery method (header)', async () => {
          const security = getSecurityRequirement(model, '/oauth2-header-delivery', 'get')
          const element = await basicFixture(security)

          const form = element.shadowRoot!.querySelector('amf-oauth2-authorization')!
          form.config.clientId = clientId
          form.config.clientSecret = 'test'
          form.config.accessToken = accessToken
          form.dispatchEvent(new CustomEvent('change'))
          await nextFrame()
          const result = element.serialize()
          const [auth] = result
          const settings = auth.config as IOAuth2Authorization

          assert.equal(settings.deliveryMethod, 'header', 'has deliveryMethod')
          assert.equal(settings.deliveryName, 'token', 'has deliveryName')
        })

        it('uses default delivery', async () => {
          const security = getSecurityRequirement(model, '/oauth2-no-delivery', 'get')
          const element = await basicFixture(security)

          const form = element.shadowRoot!.querySelector('amf-oauth2-authorization')!
          form.config.clientId = clientId
          form.config.clientSecret = 'test'
          form.config.accessToken = accessToken
          form.dispatchEvent(new CustomEvent('change'))
          await nextFrame()
          const result = element.serialize()
          const [auth] = result
          const settings = auth.config as IOAuth2Authorization

          assert.equal(settings.deliveryMethod, 'header', 'has deliveryMethod')
          assert.equal(settings.deliveryName, 'authorization', 'has deliveryName')
        })
      })
    })

    // we decided not to support oauth1.
    describe.skip(`OAuth 1 method`, () => {
      let element: ApiAuthorizationEditorElement

      beforeEach(async () => {
        const security = getSecurityRequirement(model, '/oauth1', 'get')
        element = await basicFixture(security)
      })

      it('has "types" in the authorization object', () => {
        const methods = element.methods as SecurityMethods
        const { types } = methods
        assert.deepEqual(types, ['OAuth 1.0'])
      })

      it('has "schemes" in the authorization object', () => {
        const methods = element.methods as SecurityMethods
        const { schemes } = methods
        assert.typeOf(schemes[0], 'object')
      })

      it('renders no support label', () => {
        const form = element.shadowRoot!.querySelector('.no-support.oauth1')!
        assert.ok(form)
      })
    })

    describe(`RAML null method`, () => {
      let element: ApiAuthorizationEditorElement

      beforeEach(async () => {
        const security = getSecurityRequirement(model, '/nil-oauth2', 'get')
        element = await basicFixture(security)
      })

      it('does not render authorization method', () => {
        const node = element.shadowRoot!.querySelector('.auth-editor')
        assert.notOk(node)
      })
    })
  })
})
