import { html, fixture, assert, oneEvent, nextFrame } from '@open-wc/testing'
import { spy } from 'sinon'
import {
  EventTypes,
  IOAuth2Authorization,
  IRequestAuthorization,
  RequestAuthorizationKind,
} from '@api-client/core/browser.js'
import type OAuth2Authorization from '../../../src/elements/authorization/ui/OAuth2Authorization.js'
import type Input from '../../../src/md/input/Input.js'
import type UiButton from '../../../src/md/button/internals/button.js'
import '../../../src/define/authorization/oauth2-authorization.js'

describe('Authorization', () => {
  describe('<oauth2-authorization>', () => {
    describe('urn:ietf:params:oauth:grant-type:jwt-bearer', () => {
      const baseConfig: IOAuth2Authorization = {
        grantType: 'urn:ietf:params:oauth:grant-type:jwt-bearer',
        redirectUri: 'https://redirect.com/',
        accessTokenUri: 'https://accounts.google.com/o/oauth2/v2/token',
        scopes: ['email', 'profile'],
        assertion: 'test-jwt',
      }

      async function basicFixture(opts: IOAuth2Authorization = {}): Promise<OAuth2Authorization> {
        const auth: IRequestAuthorization = {
          config: opts,
          enabled: true,
          kind: RequestAuthorizationKind,
          type: 'oauth 2',
          valid: true,
        }
        return fixture(html`<oauth2-authorization .authorization="${auth}"></oauth2-authorization>`)
      }

      async function baseUriFixture(baseUri: string, opts: IOAuth2Authorization): Promise<OAuth2Authorization> {
        const auth: IRequestAuthorization = {
          config: opts,
          enabled: true,
          kind: RequestAuthorizationKind,
          type: 'oauth 2',
          valid: true,
        }
        return fixture(
          html`<oauth2-authorization .authorization="${auth}" .baseUri="${baseUri}"></oauth2-authorization>`
        )
      }

      describe('DOM rendering', () => {
        let element: OAuth2Authorization
        let form: HTMLFormElement
        beforeEach(async () => {
          element = await basicFixture({ ...baseConfig })
          form = element.shadowRoot!.querySelector('form')!
        })

        it('has form in the DOM', async () => {
          assert.ok(form)
        })

        it(`has the accessTokenUri input`, async () => {
          const input = form.querySelector(`[name="accessTokenUri"]`) as Input
          assert.ok(input, 'has the input')
        })

        it(`has the assertion input`, async () => {
          const input = form.querySelector(`[name="assertion"]`) as Input
          assert.ok(input, 'has the input')
        })

        it('does not render redirect URI filed', async () => {
          const node = element.shadowRoot!.querySelector('[name="redirectUri"]')
          assert.notOk(node)
        })

        it('does not render token section when no token information', () => {
          const node = element.shadowRoot!.querySelector('.current-token')
          assert.notOk(node)
        })

        it('renders token section when token information is set', async () => {
          element.config.accessToken = 'test-token'
          element.requestUpdate()
          await element.updateComplete
          const node = element.shadowRoot!.querySelector('.current-token')!
          assert.ok(node)
          const label = node.querySelector('.token-value')!
          assert.equal(label.textContent!.trim(), 'test-token')
        })
      })

      describe('Data initialization', () => {
        let element: OAuth2Authorization
        beforeEach(async () => {
          element = await basicFixture({ ...baseConfig })
        })

        it(`has the accessTokenUri input`, async () => {
          const input = element.shadowRoot!.querySelector(`[name="accessTokenUri"]`) as Input
          assert.equal(input.value, baseConfig.accessTokenUri)
        })

        it(`has the assertion input`, async () => {
          const input = element.shadowRoot!.querySelector(`[name="assertion"]`) as Input
          assert.equal(input.value, baseConfig.assertion)
        })
      })

      describe('Change notification', () => {
        let element: OAuth2Authorization
        beforeEach(async () => {
          element = await basicFixture({ ...baseConfig })
        })

        it(`notifies when accessTokenUri changes`, async () => {
          setTimeout(() => {
            const input = element.shadowRoot!.querySelector(`[name="accessTokenUri"]`) as Input
            input.value = 'changed'
            input.dispatchEvent(new CustomEvent('change'))
          })
          const e = await oneEvent(element, 'change')
          assert.ok(e)
        })

        it(`notifies when assertion changes`, async () => {
          setTimeout(() => {
            const input = element.shadowRoot!.querySelector(`[name="assertion"]`) as Input
            input.value = 'changed'
            input.dispatchEvent(new CustomEvent('change'))
          })
          const e = await oneEvent(element, 'change')
          assert.ok(e)
        })
      })

      describe('Data serialization', () => {
        let element: OAuth2Authorization
        beforeEach(async () => {
          element = await basicFixture({ ...baseConfig })
        })

        it(`serializes the accessTokenUri`, async () => {
          const result = element.serialize()
          assert.equal(result.accessTokenUri, baseConfig.accessTokenUri)
        })

        it(`serializes the scopes`, async () => {
          const result = element.serialize()
          assert.deepEqual(result.scopes, baseConfig.scopes)
        })

        it(`serializes the assertion`, async () => {
          const result = element.serialize()
          assert.deepEqual(result.assertion, baseConfig.assertion)
        })

        it('deletes the client id', async () => {
          element.config.clientId = 'test'
          await nextFrame()
          const result = element.serialize()
          assert.isUndefined(result.clientId)
        })
      })

      describe('authorization event', () => {
        let element: OAuth2Authorization

        beforeEach(async () => {
          element = await basicFixture({ ...baseConfig })
        })

        it(`sets the serialized values onto the event`, async () => {
          const handler = spy()
          element.addEventListener(EventTypes.Authorization.OAuth2.authorize, handler)
          element.authorize()
          const { detail } = handler.args[0][0] as CustomEvent<IOAuth2Authorization & { result?: unknown }>
          delete detail.state
          delete detail.result
          assert.deepEqual(detail, element.serialize())
        })

        it('calls authorize() from button click', () => {
          const button = element.shadowRoot!.querySelector('.auth-button') as UiButton
          const handler = spy()
          element.addEventListener(EventTypes.Authorization.OAuth2.authorize, handler)
          button.click()
          assert.isTrue(handler.called)
        })
      })

      describe('#baseUri', () => {
        const baseUri = 'https://api.domain.com/auth'

        it('adds base URI to accessTokenUri', async () => {
          const params = { ...baseConfig }
          params.accessTokenUri = '/authorize'
          const element = await baseUriFixture(baseUri, params)
          await nextFrame()
          const result = element.serialize()
          assert.equal(result.accessTokenUri, 'https://api.domain.com/auth/authorize')
        })
      })

      describe('a11y', () => {
        let element: OAuth2Authorization

        beforeEach(async () => {
          element = await basicFixture({ ...baseConfig })
        })

        it('passes automated a11y tests', async () => {
          await assert.isAccessible(element)
        })
      })
    })
  })
})
