import { assert, aTimeout, fixture, html } from '@open-wc/testing'
import sinon from 'sinon'
import type CertificateAdd from '../../../src/elements/http/CertificateAdd.element.js'
import type Input from '../../../src/md/input/Input.js'
import { UiMock } from '../../helpers/UiMock.js'
import { EventTypes } from '../../../src/events/EventTypes.js'
import type {
  FileReadOptions,
  IOpenFileDialogInit,
  IOpenFileDialogResult,
} from '../../../src/bindings/base/FileBindings.js'
import '../../../src/define/http/certificate-add.js'
import { SnackNotifications } from '../../../src/md/notification/SnackNotifications.js'

describe.skip('elements', () => {
  describe('http', () => {
    describe('CertificateAdd', () => {
      async function basicFixture(): Promise<CertificateAdd> {
        return fixture(html`<certificate-add></certificate-add>`)
      }

      async function pfxFixture(): Promise<CertificateAdd> {
        return fixture(html`<certificate-add importType="p12"></certificate-add>`)
      }

      describe('PEM', () => {
        let element: CertificateAdd

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

        it('renders the certificate name input', async () => {
          const input = element.shadowRoot!.querySelector('ui-filled-text-field[name="name"]')! as Input
          assert.ok(input, 'has the input')
        })

        it('renders the certificate file filed', async () => {
          const input = element.shadowRoot!.querySelector('.cert-file[data-kind="certificate"]')! as HTMLElement
          assert.ok(input, 'has the input')
        })

        it('renders the key file filed', async () => {
          const input = element.shadowRoot!.querySelector('.cert-file[data-kind="key"]')! as HTMLElement
          assert.ok(input, 'has the input')
        })

        it('renders the key password filed', async () => {
          const input = element.shadowRoot!.querySelector('ui-filled-text-field[name="keyPassword"]') as Input
          assert.ok(input, 'has the input')
        })

        it('does not render the certificate password filed', async () => {
          const input = element.shadowRoot!.querySelector('ui-filled-text-field[name="certificatePassword"]')
          assert.notOk(input, 'has no input')
        })
      })

      describe('PFX/PKCS12', () => {
        let element: CertificateAdd

        beforeEach(async () => {
          element = await pfxFixture()
        })

        it('renders the certificate name input', async () => {
          const input = element.shadowRoot!.querySelector('ui-filled-text-field[name="name"]')! as Input
          assert.ok(input, 'has the input')
        })

        it('renders the certificate file filed', async () => {
          const input = element.shadowRoot!.querySelector('.cert-file[data-kind="certificate"]')! as HTMLElement
          assert.ok(input, 'has the input')
        })

        it('does not render the key file filed', async () => {
          const input = element.shadowRoot!.querySelector('.cert-file[data-kind="key"]')
          assert.notOk(input, 'has no input')
        })

        it('does not render the key password filed', async () => {
          const input = element.shadowRoot!.querySelector('ui-filled-text-field[name="keyPassword"]')
          assert.notOk(input, 'has no input')
        })

        it('renders the certificate password input', async () => {
          const input = element.shadowRoot!.querySelector('ui-filled-text-field[name="certificatePassword"]')! as Input
          assert.ok(input, 'has the input')
        })
      })

      describe('File drag and drop', () => {
        let element: CertificateAdd
        let certTarget: HTMLElement
        let keyTarget: HTMLElement

        beforeEach(async () => {
          element = await basicFixture()
          certTarget = element.shadowRoot!.querySelector('.cert-file[data-kind="certificate"]') as HTMLElement
          keyTarget = element.shadowRoot!.querySelector('.cert-file[data-kind="key"]') as HTMLElement
        })

        it('handles the drag enter event on the certificate input', async () => {
          const e = UiMock.getFileDragEvent('dragenter')
          certTarget.dispatchEvent(e)
          assert.isTrue(e.defaultPrevented, 'the event is prevented')
          assert.isTrue(certTarget.classList.contains('dragover'), 'adds the dragover class')
        })

        it('handles the drag enter event on the key input', async () => {
          const e = UiMock.getFileDragEvent('dragenter')
          keyTarget.dispatchEvent(e)
          assert.isTrue(e.defaultPrevented, 'the event is prevented')
          assert.isTrue(keyTarget.classList.contains('dragover'), 'adds the dragover class')
        })

        it('handles the drag leave event on the certificate input', async () => {
          certTarget.classList.add('dragover')
          const e = UiMock.getFileDragEvent('dragleave')
          certTarget.dispatchEvent(e)
          assert.isTrue(e.defaultPrevented, 'the event is prevented')
          assert.isFalse(certTarget.classList.contains('dragover'), 'adds the dragover class')
        })

        it('handles the drag leave event on the key input', async () => {
          keyTarget.classList.add('dragover')
          const e = UiMock.getFileDragEvent('dragleave')
          keyTarget.dispatchEvent(e)
          assert.isTrue(e.defaultPrevented, 'the event is prevented')
          assert.isFalse(keyTarget.classList.contains('dragover'), 'adds the dragover class')
        })

        it('handles the drag over event on the certificate input', async () => {
          const e = UiMock.getFileDragEvent('dragover')
          certTarget.dispatchEvent(e)
          assert.isTrue(e.defaultPrevented, 'the event is prevented')
          assert.isTrue(certTarget.classList.contains('dragover'), 'adds the dragover class')
        })

        it('handles the drag over event on the key input', async () => {
          const e = UiMock.getFileDragEvent('dragover')
          keyTarget.dispatchEvent(e)
          assert.isTrue(e.defaultPrevented, 'the event is prevented')
          assert.isTrue(keyTarget.classList.contains('dragover'), 'adds the dragover class')
        })

        it('handles the drop event on the certificate input', async () => {
          certTarget.classList.add('dragover')
          const spy = sinon.spy(element, 'processDroppedFile')
          const file = new File(['test'], 'test.txt', { type: 'text/plain' })
          const e = UiMock.getFileDragEvent('drop', { file })
          certTarget.dispatchEvent(e)
          assert.isTrue(e.defaultPrevented, 'the event is prevented')
          assert.isFalse(certTarget.classList.contains('dragover'), 'adds the dragover class')
          assert.isTrue(spy.called, 'calls the processDroppedFile method')
          assert.equal(spy.args[0][0], file, 'passes the file argument')
          assert.equal(spy.args[0][1], 'certificate', 'passes the kind argument')
        })

        it('handles the drop event on the key input', async () => {
          keyTarget.classList.add('dragover')
          const spy = sinon.spy(element, 'processDroppedFile')
          const file = new File(['test'], 'test.txt', { type: 'text/plain' })
          const e = UiMock.getFileDragEvent('drop', { file })
          keyTarget.dispatchEvent(e)
          assert.isTrue(e.defaultPrevented, 'the event is prevented')
          assert.isFalse(keyTarget.classList.contains('dragover'), 'adds the dragover class')
          assert.isTrue(spy.called, 'calls the processDroppedFile method')
          assert.equal(spy.args[0][0], file, 'passes the file argument')
          assert.equal(spy.args[0][1], 'key', 'passes the kind argument')
        })
      })

      describe('Manual file selecting for PEM', () => {
        let element: CertificateAdd
        let certTarget: HTMLElement
        let keyTarget: HTMLElement

        beforeEach(async () => {
          element = await basicFixture()
          certTarget = element.shadowRoot!.querySelector(
            '.cert-file[data-kind="certificate"] .select-button'
          ) as HTMLElement
          keyTarget = element.shadowRoot!.querySelector('.cert-file[data-kind="key"] .select-button') as HTMLElement
        })

        it('dispatches the Filesystem.requestOpenFile for the certificate', async () => {
          let openOptions: IOpenFileDialogInit | undefined
          let readOptions: FileReadOptions | undefined
          let eventPath: string | undefined

          element.addEventListener(EventTypes.Filesystem.requestOpenFile, (e: Event): void => {
            const event = e as CustomEvent<{ options: IOpenFileDialogInit; result: Promise<IOpenFileDialogResult> }>
            openOptions = event.detail.options
            event.detail.result = Promise.resolve({
              canceled: false,
              filePath: ['test/file.pem'],
            })
          })

          element.addEventListener(EventTypes.Filesystem.readFile, (e: Event): void => {
            const event = e as CustomEvent<{
              path: string
              options: FileReadOptions
              result: Promise<string | ArrayBuffer | Buffer>
            }>
            readOptions = event.detail.options
            eventPath = event.detail.path
            event.detail.result = Promise.resolve(new ArrayBuffer(100))
          })

          certTarget.click()
          // Promises are resolved in the save event loop at the end as microtasks so `1` timeout should be enough
          // as this will be executed with the next event loop.
          await aTimeout(1)

          assert.typeOf(openOptions, 'object', 'has the file open request options')
          assert.isFalse(openOptions!.multiple, 'the (open request) options.multiple is false')
          assert.equal(openOptions!.title, 'Select a certificate file', 'has the (open request) options.title')
          assert.lengthOf(openOptions!.filters!, 1, 'has the (open request) options.filters')
          const [openFilter] = openOptions!.filters!
          assert.equal(openFilter.description, 'Certificate files', 'open filter has the description')
          assert.deepEqual(
            openFilter.accept,
            { 'application/x-pem-file': ['.pem', '.crt', '.cer', '.key'] },
            'filter has the accept'
          )

          assert.typeOf(readOptions, 'object', 'has the file read request options')
          assert.equal(readOptions!.returnType, 'buffer', 'read file return type is set')
          assert.isTrue(readOptions!.dispose, 'read file dispose is set')

          assert.equal(eventPath, 'test/file.pem', 'read event has the correct path')
        })

        it('dispatches the Filesystem.requestOpenFile for the key', async () => {
          let openOptions: IOpenFileDialogInit | undefined
          let readOptions: FileReadOptions | undefined
          let eventPath: string | undefined

          element.addEventListener(EventTypes.Filesystem.requestOpenFile, (e: Event): void => {
            const event = e as CustomEvent<{ options: IOpenFileDialogInit; result: Promise<IOpenFileDialogResult> }>
            openOptions = event.detail.options
            event.detail.result = Promise.resolve({
              canceled: false,
              filePath: ['test/file.pem'],
            })
          })

          element.addEventListener(EventTypes.Filesystem.readFile, (e: Event): void => {
            const event = e as CustomEvent<{
              path: string
              options: FileReadOptions
              result: Promise<string | ArrayBuffer | Buffer>
            }>
            readOptions = event.detail.options
            eventPath = event.detail.path
            event.detail.result = Promise.resolve(new ArrayBuffer(100))
          })

          keyTarget.click()
          // Promises are resolved in the save event loop at the end as microtasks so `1` timeout should be enough
          // as this will be executed with the next event loop.
          await aTimeout(1)

          assert.typeOf(openOptions, 'object', 'has the file open request options')
          assert.isFalse(openOptions!.multiple, 'the (open request) options.multiple is false')
          assert.equal(openOptions!.title, 'Select a key file', 'has the (open request) options.title')
          assert.lengthOf(openOptions!.filters!, 1, 'has the (open request) options.filters')
          const [openFilter] = openOptions!.filters!
          assert.equal(openFilter.description, 'Certificate key files', 'open filter has the description')
          assert.deepEqual(
            openFilter.accept,
            { 'application/x-pem-file': ['.pem', '.crt', '.cer', '.key'] },
            'filter has the accept'
          )

          assert.typeOf(readOptions, 'object', 'has the file read request options')
          assert.equal(readOptions!.returnType, 'buffer', 'read file return type is set')
          assert.isTrue(readOptions!.dispose, 'read file dispose is set')

          assert.equal(eventPath, 'test/file.pem', 'read event has the correct path')
        })
      })

      describe('Manual file selecting for PFX/PKCS12', () => {
        let element: CertificateAdd
        let certTarget: HTMLElement

        beforeEach(async () => {
          element = await pfxFixture()
          certTarget = element.shadowRoot!.querySelector(
            '.cert-file[data-kind="certificate"] .select-button'
          ) as HTMLElement
        })

        it('dispatches the Filesystem.requestOpenFile for the certificate', async () => {
          let openOptions: IOpenFileDialogInit | undefined
          let readOptions: FileReadOptions | undefined
          let eventPath: string | undefined

          element.addEventListener(EventTypes.Filesystem.requestOpenFile, (e: Event): void => {
            const event = e as CustomEvent<{ options: IOpenFileDialogInit; result: Promise<IOpenFileDialogResult> }>
            openOptions = event.detail.options
            event.detail.result = Promise.resolve({
              canceled: false,
              filePath: ['test/file.pem'],
            })
          })

          element.addEventListener(EventTypes.Filesystem.readFile, (e: Event): void => {
            const event = e as CustomEvent<{
              path: string
              options: FileReadOptions
              result: Promise<string | ArrayBuffer | Buffer>
            }>
            readOptions = event.detail.options
            eventPath = event.detail.path
            event.detail.result = Promise.resolve(new ArrayBuffer(100))
          })

          certTarget.click()
          // Promises are resolved in the save event loop at the end as microtasks so `1` timeout should be enough
          // as this will be executed with the next event loop.
          await aTimeout(1)

          assert.typeOf(openOptions, 'object', 'has the file open request options')
          assert.isFalse(openOptions!.multiple, 'the (open request) options.multiple is false')
          assert.equal(openOptions!.title, 'Select a certificate file', 'has the (open request) options.title')
          assert.lengthOf(openOptions!.filters!, 1, 'has the (open request) options.filters')
          const [openFilter] = openOptions!.filters!
          assert.equal(openFilter.description, 'Certificate files', 'open filter has the description')
          assert.deepEqual(openFilter.accept, { 'application/x-pkcs12': ['.p12', '.pfx'] }, 'filter has the accept')

          assert.typeOf(readOptions, 'object', 'has the file read request options')
          assert.equal(readOptions!.returnType, 'buffer', 'read file return type is set')
          assert.isTrue(readOptions!.dispose, 'read file dispose is set')

          assert.equal(eventPath, 'test/file.pem', 'read event has the correct path')
        })
      })

      describe('processDroppedFile()', () => {
        let element: CertificateAdd

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

        it('sets the certificate file info', async () => {
          const file = new File(['test'], 'test.txt', { type: 'text/plain' })
          await element.processDroppedFile(file, 'certificate')
          const info = element.certificateFile!
          assert.equal(info.fileName, 'test.txt', 'sets the file name')
          assert.ok(info.contents, 'sets the file contents')
        })

        it('sets the key file info', async () => {
          const file = new File(['test'], 'test.txt', { type: 'text/plain' })
          await element.processDroppedFile(file, 'key')
          const info = element.keyFile!
          assert.equal(info.fileName, 'test.txt', 'sets the file name')
          assert.ok(info.contents, 'sets the file contents')
        })
      })

      describe('submit() on PEM', () => {
        let element: CertificateAdd

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

        afterEach(() => {
          SnackNotifications.clear()
        })

        it('reports validity when missing certificate name', async () => {
          const cert = new File(['test'], 'cert.pem', { type: 'text/plain' })
          const key = new File(['test'], 'key.key', { type: 'text/plain' })
          await element.processDroppedFile(cert, 'certificate')
          await element.processDroppedFile(key, 'key')
          const spy = sinon.spy()
          element.addEventListener(EventTypes.Store.File.create, spy)
          await element.submit()
          assert.isFalse(spy.called, 'does not dispatch store event')
          const input = element.shadowRoot!.querySelector('ui-filled-text-field[name="name"]')! as Input
          assert.isTrue(input.invalid, 'the input is invalid')
        })

        it('informs when the certificate is missing', async () => {
          const key = new File(['test'], 'key.key', { type: 'text/plain' })
          await element.processDroppedFile(key, 'key')
          const input = element.shadowRoot!.querySelector('ui-filled-text-field[name="name"]')! as Input
          input.value = 'test'
          input.notifyChange()
          await input.updateComplete

          const spy = sinon.spy()
          element.addEventListener(EventTypes.Store.File.create, spy)
          await element.submit()
          assert.isFalse(spy.called, 'does not dispatch store event')
        })

        it('informs when the key is missing', async () => {
          const cert = new File(['test'], 'key.key', { type: 'text/plain' })
          await element.processDroppedFile(cert, 'certificate')
          const input = element.shadowRoot!.querySelector('ui-filled-text-field[name="name"]')! as Input
          input.value = 'test'
          input.notifyChange()
          await input.updateComplete

          const spy = sinon.spy()
          element.addEventListener(EventTypes.Store.File.create, spy)
          await element.submit()
          assert.isFalse(spy.called, 'does not dispatch store event')
        })

        it('throws when no space', async () => {
          const cert = new File(['test'], 'cert.pem', { type: 'text/plain' })
          const key = new File(['test'], 'key.key', { type: 'text/plain' })
          await element.processDroppedFile(cert, 'certificate')
          await element.processDroppedFile(key, 'key')
          const input = element.shadowRoot!.querySelector('ui-filled-text-field[name="name"]')! as Input
          input.value = 'test'
          input.notifyChange()
          await input.updateComplete

          let error: Error | undefined
          try {
            await element.submit()
          } catch (e) {
            error = e as Error
          }

          assert.ok(error)
        })

        it('dispatches the store event', async () => {
          element.space = 'abc'
          const cert = new File(['test'], 'cert.pem', { type: 'text/plain' })
          const key = new File(['test'], 'key.key', { type: 'text/plain' })
          await element.processDroppedFile(cert, 'certificate')
          await element.processDroppedFile(key, 'key')
          const input = element.shadowRoot!.querySelector('ui-filled-text-field[name="name"]')! as Input
          input.value = 'test'
          input.notifyChange()
          await input.updateComplete
          const spy = sinon.spy()
          element.addEventListener(EventTypes.Store.File.create, spy)
          await element.submit()
          assert.isTrue(spy.calledOnce)
        })
      })
    })
  })
})
