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

describe('Authorization', () => {
  describe('<oauth2-authorization>', () => {
    describe('custom-credentials', () => {
      const baseConfig: IOAuth2Authorization = {
        grantType: 'custom-credentials',
        clientId: 'test-client-id',
        clientSecret: 'test-secret-client',
        accessTokenUri: 'https://accounts.google.com/o/oauth2/v2/token',
        scopes: ['email', 'profile'],
      }

      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 the clientId input`, async () => {
          const input = form.querySelector(`[name="clientId"]`) as Input
          assert.ok(input, 'has the input')
        })

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

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

        it('client secret is not required', async () => {
          const input = form.querySelector(`[name="clientSecret"]`) as Input
          assert.notOk(input.required)
        })

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

        it('does not render the PKCE checkbox', async () => {
          const node = element.shadowRoot!.querySelector('[name="pkce"]')
          assert.notOk(node)
        })
      })

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

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

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

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

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

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

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

        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)
        })
      })

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

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

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

        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)
        })
      })

      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())
        })
      })

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

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

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

        it('adds base URI to redirectUri', async () => {
          const params = { ...baseConfig }
          params.redirectUri = '/redirect'

          const element = await baseUriFixture(baseUri, params)
          const result = element.serialize()
          assert.equal(result.redirectUri, 'https://api.domain.com/auth/redirect')
        })

        it('ignores trailing slash', async () => {
          const uri = `${baseUri}/`
          const params = { ...baseConfig }
          params.redirectUri = '/redirect'

          const element = await baseUriFixture(uri, params)
          const result = element.serialize()
          assert.equal(result.redirectUri, 'https://api.domain.com/auth/redirect')
        })

        it('makes authorizationUri input text type', async () => {
          const element = await baseUriFixture(baseUri, { ...baseConfig })
          const node = element.shadowRoot!.querySelector('[name="authorizationUri"]') as Input
          assert.equal(node.type, 'string')
        })

        it('makes accessTokenUri input text type', async () => {
          const element = await baseUriFixture(baseUri, { ...baseConfig })
          const node = element.shadowRoot!.querySelector('[name="accessTokenUri"]') as Input
          assert.equal(node.type, 'string')
        })
      })

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

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

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