/* eslint-disable max-len */
import { fixture, assert, aTimeout, nextFrame, html } from '@open-wc/testing'
import {
  AmfDefinitions,
  ApiDefinitions,
  IOAuth2Authorization,
  IOAuth2AuthorizationRequestCustomData,
  IOAuth2CustomData,
  IOAuth2CustomParameter,
  IOAuth2TokenRequestCustomData,
} from '@api-client/core/browser.js'
import { AmfLoader } from '../AmfLoader.js'
import SwitchElement from '../../../src/md/switch/internals/SwitchElement.js'
import AmfOauth2 from '../../../src/elements/amf/authorization/AmfOauth2.js'
import Input from '../../../src/md/input/Input.js'
import { oauth2GrantTypes } from '../../../src/elements/authorization/ui/OAuth2Authorization.js'
import '../../../src/define/amf/authorization/amf-oauth2-authorization.js'
import type { UiFilledButtonElement } from '../../../src/md/button/ui-filled-button.js'

describe.skip('OAuth 2', () => {
  async function methodFixture(security?: ApiDefinitions.IApiParametrizedSecurityScheme): Promise<AmfOauth2> {
    return fixture(html`<amf-oauth2-authorization .security="${security}"></amf-oauth2-authorization>`)
  }

  let store: AmfLoader
  let model: AmfDefinitions.IAmfDocument
  before(async () => {
    store = new AmfLoader()
    model = await store.getGraph('secured-api')
  })

  function getApiParametrizedSecurityScheme(
    path: string,
    method: string
  ): ApiDefinitions.IApiParametrizedSecurityScheme {
    const operation = store.getOperation(model, path, method)
    return operation.security[0].schemes[0]
  }

  describe('initialization', () => {
    it('can be initialized with document.createElement', () => {
      const element = document.createElement('amf-authorization-method')
      assert.ok(element)
    })

    it('can be initialized in a template with model', async () => {
      const operation = store.getOperation(model, '/oauth2', 'post')
      const element = await methodFixture(operation.security[0].schemes[0])
      await aTimeout(0)
      assert.ok(element)
    })
  })

  describe('setting API data', () => {
    it('sets default authorization grants', async () => {
      const scheme = getApiParametrizedSecurityScheme('/oauth2', 'post')
      const element = await methodFixture(scheme)
      assert.deepEqual(element.grantTypes, oauth2GrantTypes)
    })

    it('sets API defined grant types', async () => {
      const scheme = getApiParametrizedSecurityScheme('/oauth2-with-grant-list', 'get')
      const element = await methodFixture(scheme)
      assert.deepEqual(element.grantTypes, [
        {
          type: 'authorization_code',
          label: 'Authorization code (server flow)',
        },
      ])
    })

    it('changes grant types list when endpoint changes', async () => {
      const scheme = getApiParametrizedSecurityScheme('/oauth2-with-grant-list', 'get')
      const element = await methodFixture(scheme)
      const operation = store.getOperation(model, '/oauth2', 'post')
      const scheme2 = operation.security[0].schemes[0]
      element.security = scheme2
      await nextFrame()
      assert.deepEqual(element.grantTypes, oauth2GrantTypes)
    })

    it('selects first available grant type', async () => {
      const scheme = getApiParametrizedSecurityScheme('/oauth2-with-grant-list', 'get')
      const element = await methodFixture(scheme)
      assert.equal(element.config.grantType, 'authorization_code')
    })

    it('sets authorizationUri', async () => {
      const scheme = getApiParametrizedSecurityScheme('/oauth2', 'post')
      const element = await methodFixture(scheme)
      assert.equal(element.config.authorizationUri, 'https://auth.com')
    })

    it('sets accessTokenUri', async () => {
      const scheme = getApiParametrizedSecurityScheme('/oauth2', 'post')
      const element = await methodFixture(scheme)
      assert.equal(element.config.accessTokenUri, 'https://token.com')
    })

    it('sets scopes', async () => {
      const scheme = getApiParametrizedSecurityScheme('/oauth2', 'post')
      const element = await methodFixture(scheme)
      assert.deepEqual(element.config.scopes, ['profile', 'email'])
    })

    it('automatically hides advanced properties when filled', async () => {
      const scheme = getApiParametrizedSecurityScheme('/oauth2', 'post')
      const element = await methodFixture(scheme)
      const node = element.shadowRoot!.querySelector('.advanced-section') as HTMLElement
      assert.isTrue(node.hasAttribute('hidden'))
    })

    it('should not show pkce checkbox if selected grant type is not authorization_code', async () => {
      const scheme = getApiParametrizedSecurityScheme('/oauth2', 'post')
      const element = await methodFixture(scheme)
      /* Default grant type selected is Access Token for this endpoint */
      /* Check it just in case */
      assert.equal(element.config.grantType, 'implicit')
      const node = element.shadowRoot!.querySelector('.adv-toggle ui-switch') as SwitchElement
      node.click()
      await nextFrame()
      const pkceCheckbox = (element.shadowRoot!.querySelector('.advanced-section') as HTMLElement).querySelector(
        'ui-checkbox'
      )
      assert.notExists(pkceCheckbox)
    })

    it('should show pkce checkbox unchecked by default in advanced settings with authorization_code selected', async () => {
      const scheme = getApiParametrizedSecurityScheme('/oauth2', 'post')
      const element = await methodFixture(scheme)
      element.config.grantType = 'authorization_code'
      const node = element.shadowRoot!.querySelector('.adv-toggle ui-switch') as SwitchElement
      node.click()
      await nextFrame()
      const pkceCheckbox = (element.shadowRoot!.querySelector('.advanced-section') as HTMLElement).querySelector(
        'ui-checkbox'
      )!
      assert.isFalse(pkceCheckbox.checked)
    })

    const customSelector =
      '[data-binding="authQuery"],[data-binding="tokenQuery"],[data-binding="tokenHeader"],[data-binding="tokenBody"]'

    it('does not render annotation inputs when are not defined', async () => {
      const scheme = getApiParametrizedSecurityScheme('/oauth2', 'post')
      const element = await methodFixture(scheme)
      const node = element.shadowRoot!.querySelector(customSelector)
      assert.notOk(node)
    })

    it('renders annotation inputs when defined', async () => {
      const scheme = getApiParametrizedSecurityScheme('/oauth2-with-annotations', 'get')
      const element = await methodFixture(scheme)
      const nodes = element.shadowRoot!.querySelectorAll(customSelector)
      assert.lengthOf(nodes, 8)
    })

    it('sets config.deliveryMethod to header when available', async () => {
      const scheme = getApiParametrizedSecurityScheme('/oauth2-header-delivery', 'get')
      const element = await methodFixture(scheme)
      assert.equal(element.config.deliveryMethod, 'header')
      assert.equal(element.config.deliveryName, 'authorization')
    })

    it('sets config.deliveryMethod to query when available', async () => {
      const scheme = getApiParametrizedSecurityScheme('/oauth2-query-delivery', 'get')
      const element = await methodFixture(scheme)

      assert.equal(element.config.deliveryMethod, 'query')
      assert.equal(element.config.deliveryName, 'access_token')
    })

    it('sets default config.deliveryMethod', async () => {
      const scheme = getApiParametrizedSecurityScheme('/oauth2-no-delivery', 'get')
      const element = await methodFixture(scheme)

      assert.equal(element.config.deliveryMethod, 'header')
      assert.equal(element.config.deliveryName, 'authorization')
    })

    it('resets state when incompatible settings', async () => {
      const scheme = getApiParametrizedSecurityScheme('/basic', 'get')
      const element = await methodFixture(scheme)
      assert.deepEqual(element.grantTypes, oauth2GrantTypes, 'grant types are set')
      assert.equal(element.config.deliveryMethod, 'header', 'config.deliveryMethod is set')
      assert.equal(element.config.deliveryName, 'authorization', 'config.deliveryName is set')
    })
  })

  describe('annotation data changing', () => {
    let element: AmfOauth2

    beforeEach(async () => {
      const scheme = getApiParametrizedSecurityScheme('/oauth2-with-annotations', 'get')
      element = await methodFixture(scheme)
      element.config.grantType = 'authorization_code'
      await nextFrame()
    })

    it('updates query parameter on authorization request', async () => {
      const input = element.shadowRoot!.querySelector('.form-input[name="numericParam"]') as Input
      input.value = `10`
      input.dispatchEvent(new Event('change'))
      const info = element.serialize() as IOAuth2Authorization
      const cd = info.customData as IOAuth2CustomData
      const auth = cd.auth as IOAuth2AuthorizationRequestCustomData
      const param = (auth.parameters as IOAuth2CustomParameter[]).find(
        (i) => i.name === 'numericParam'
      ) as IOAuth2CustomParameter
      assert.equal(param.value, '10')
    })

    it('updates array value', async () => {
      const button = element.shadowRoot!.querySelector(
        '[data-param-label="repeatableParam1"] .add-item-button'
      ) as UiFilledButtonElement
      button.click()
      await nextFrame()
      const input = element.shadowRoot!.querySelector('.form-input[name="repeatableParam1"]') as Input
      // @ts-expect-error for testing
      input.value = ['test']
      input.dispatchEvent(new Event('change'))
      await nextFrame()
      const info = element.serialize() as IOAuth2Authorization
      const cd = info.customData as IOAuth2CustomData
      const auth = cd.auth as IOAuth2AuthorizationRequestCustomData
      const param = (auth.parameters as IOAuth2CustomParameter[]).find(
        (i) => i.name === 'repeatableParam1'
      ) as IOAuth2CustomParameter
      // @ts-expect-error for testing
      assert.deepEqual(param.value, ['test'])
    })

    it('updates query parameter on token request', async () => {
      const input = element.shadowRoot!.querySelector('.form-input[name="queryTokenResource"]') as Input
      input.value = 'test'
      input.notifyChange()
      const info = element.serialize() as IOAuth2Authorization
      const cd = info.customData as IOAuth2CustomData
      const token = cd.token as IOAuth2TokenRequestCustomData
      assert.equal((token.parameters as IOAuth2CustomParameter[])[0].value, 'test')
    })

    it('updates header on token request', async () => {
      const input = element.shadowRoot!.querySelector('.form-input[name="x-token-resource"]') as Input
      input.value = '123'
      input.dispatchEvent(new Event('change'))
      const info = element.serialize() as IOAuth2Authorization
      const cd = info.customData as IOAuth2CustomData
      const token = cd.token as IOAuth2TokenRequestCustomData
      assert.equal((token.headers as IOAuth2CustomParameter[])[0].value, '123')
    })

    it('updates header on token request (other?)', async () => {
      const input = element.shadowRoot!.querySelector('.form-input[name="bodyTokenResource"]') as Input
      input.value = 'test'
      input.dispatchEvent(new Event('change'))
      const info = element.serialize() as IOAuth2Authorization
      const cd = info.customData as IOAuth2CustomData
      const token = cd.token as IOAuth2TokenRequestCustomData
      assert.equal((token.body as IOAuth2CustomParameter[])[0].value, 'test')
    })
  })

  describe('serialize()', () => {
    let element: AmfOauth2

    beforeEach(async () => {
      const scheme = getApiParametrizedSecurityScheme('/oauth2-with-annotations', 'get')
      element = await methodFixture(scheme)
    })

    it('serializes implicit data', async () => {
      element.config.grantType = 'implicit'
      await nextFrame()
      const info = element.serialize() as IOAuth2Authorization
      const cd = info.customData as IOAuth2CustomData
      const auth = cd.auth as IOAuth2AuthorizationRequestCustomData
      assert.typeOf(auth.parameters, 'array')
      assert.lengthOf(auth.parameters as IOAuth2CustomParameter[], 1)
      const [param] = auth.parameters as IOAuth2CustomParameter[]
      assert.equal(param.name, 'resource')
      assert.equal(param.value, 'default')
    })

    it('has no token properties for implicit data', async () => {
      element.config.grantType = 'implicit'
      await nextFrame()
      const info = element.serialize() as IOAuth2Authorization
      const cd = info.customData as IOAuth2CustomData
      const token = cd.token as IOAuth2TokenRequestCustomData
      assert.isUndefined(token.parameters)
      assert.isUndefined(token.headers)
      assert.isUndefined(token.body)
    })

    it('serializes authorization code data', async () => {
      element.config.grantType = 'authorization_code'
      await nextFrame()
      const input = element.shadowRoot!.querySelector('.form-input[name="queryTokenResource"]') as Input
      input.value = 'test'
      input.dispatchEvent(new Event('change'))
      await nextFrame()

      const info = element.serialize() as IOAuth2Authorization
      const cd = info.customData as IOAuth2CustomData
      const token = cd.token as IOAuth2TokenRequestCustomData
      const auth = cd.auth as IOAuth2AuthorizationRequestCustomData

      assert.lengthOf(auth.parameters as IOAuth2CustomParameter[], 1, 'has auth parameter')
      assert.lengthOf(token.parameters as IOAuth2CustomParameter[], 1, 'has token parameter')
      const [authParam] = auth.parameters as IOAuth2CustomParameter[]
      assert.equal(authParam.name, 'resource')
      assert.equal(authParam.value, 'default')
      const [tokenParam] = token.parameters as IOAuth2CustomParameter[]
      assert.equal(tokenParam.name, 'queryTokenResource')
      assert.equal(tokenParam.value, 'test')
      assert.isFalse(element.config.pkce, 'pkce is false')
    })

    it('serializes client credentials data', async () => {
      element.config.grantType = 'client_credentials'
      await nextFrame()
      const input = element.shadowRoot!.querySelector('.form-input[name="queryTokenResource"]') as Input
      input.value = 'test'
      input.dispatchEvent(new Event('change'))
      const info = element.serialize() as IOAuth2Authorization
      const cd = info.customData as IOAuth2CustomData
      const token = cd.token as IOAuth2TokenRequestCustomData
      assert.typeOf(token.parameters, 'array')
      assert.lengthOf(token.parameters as IOAuth2CustomParameter[], 1)
      const [param] = token.parameters as IOAuth2CustomParameter[]
      assert.equal(param.name, 'queryTokenResource')
      assert.equal(param.value, 'test')
    })

    it('serializes password data', async () => {
      element.config.grantType = 'password'
      await nextFrame()
      const input = element.shadowRoot!.querySelector('.form-input[name="queryTokenResource"]') as Input
      input.value = 'test'
      input.dispatchEvent(new Event('change'))
      const info = element.serialize() as IOAuth2Authorization
      const cd = info.customData as IOAuth2CustomData
      const token = cd.token as IOAuth2TokenRequestCustomData
      assert.typeOf(token.parameters, 'array')
      assert.lengthOf(token.parameters as IOAuth2CustomParameter[], 1)
      const [param] = token.parameters as IOAuth2CustomParameter[]
      assert.equal(param.name, 'queryTokenResource')
      assert.equal(param.value, 'test')
    })

    it('serializes custom grant data', async () => {
      element.config.grantType = 'annotated_custom_grant'
      await nextFrame()
      const input = element.shadowRoot!.querySelector('.form-input[name="queryTokenResource"]') as Input
      input.value = 'test'
      input.dispatchEvent(new Event('change'))
      const info = element.serialize() as IOAuth2Authorization
      const cd = info.customData as IOAuth2CustomData
      const token = cd.token as IOAuth2TokenRequestCustomData
      assert.typeOf(token.parameters, 'array')
      assert.lengthOf(token.parameters as IOAuth2CustomParameter[], 1)
      const [param] = token.parameters as IOAuth2CustomParameter[]
      assert.equal(param.name, 'queryTokenResource')
      assert.equal(param.value, 'test')
    })
  })
})
