import { expect } from 'chai'
import * as path from 'path'

import { closeWindow } from './window-helpers'
import { emittedNTimes } from './events-helpers'

import { BrowserWindow, ipcMain, WebContents } from 'electron'

describe('chrome extension content scripts', () => {
  const fixtures = path.resolve(__dirname, '..', 'spec', 'fixtures')
  const extensionPath = path.resolve(fixtures, 'extensions')

  const addExtension = (name: string) => BrowserWindow.addExtension(path.resolve(extensionPath, name))
  const removeAllExtensions = () => {
    Object.keys(BrowserWindow.getExtensions()).map(extName => {
      BrowserWindow.removeExtension(extName)
    })
  }

  let responseIdCounter = 0
  const executeJavaScriptInFrame = (webContents: WebContents, frameRoutingId: number, code: string) => {
    return new Promise(resolve => {
      const responseId = responseIdCounter++
      ipcMain.once(`executeJavaScriptInFrame_${responseId}`, (event, result) => {
        resolve(result)
      })
      webContents.send('executeJavaScriptInFrame', frameRoutingId, code, responseId)
    })
  }

  const generateTests = (sandboxEnabled: boolean, contextIsolationEnabled: boolean) => {
    describe(`with sandbox ${sandboxEnabled ? 'enabled' : 'disabled'} and context isolation ${contextIsolationEnabled ? 'enabled' : 'disabled'}`, () => {
      let w: BrowserWindow

      describe('supports "run_at" option', () => {
        beforeEach(async () => {
          await closeWindow(w)
          w = new BrowserWindow({
            show: false,
            width: 400,
            height: 400,
            webPreferences: {
              contextIsolation: contextIsolationEnabled,
              sandbox: sandboxEnabled
            }
          })
        })

        afterEach(() => {
          removeAllExtensions()
          return closeWindow(w).then(() => { w = null as unknown as BrowserWindow })
        })

        it('should run content script at document_start', () => {
          addExtension('content-script-document-start')
          w.webContents.once('dom-ready', async () => {
            const result = await w.webContents.executeJavaScript('document.documentElement.style.backgroundColor')
            expect(result).to.equal('red')
          })
          w.loadURL('about:blank')
        })

        it('should run content script at document_idle', async () => {
          addExtension('content-script-document-idle')
          w.loadURL('about:blank')
          const result = await w.webContents.executeJavaScript('document.body.style.backgroundColor')
          expect(result).to.equal('red')
        })

        it('should run content script at document_end', () => {
          addExtension('content-script-document-end')
          w.webContents.once('did-finish-load', async () => {
            const result = await w.webContents.executeJavaScript('document.documentElement.style.backgroundColor')
            expect(result).to.equal('red')
          })
          w.loadURL('about:blank')
        })
      })

      describe('supports "all_frames" option', () => {
        const contentScript = path.resolve(fixtures, 'extensions/content-script')

        // Computed style values
        const COLOR_RED = `rgb(255, 0, 0)`
        const COLOR_BLUE = `rgb(0, 0, 255)`
        const COLOR_TRANSPARENT = `rgba(0, 0, 0, 0)`

        before(() => {
          BrowserWindow.addExtension(contentScript)
        })

        after(() => {
          BrowserWindow.removeExtension('content-script-test')
        })

        beforeEach(() => {
          w = new BrowserWindow({
            show: false,
            webPreferences: {
              // enable content script injection in subframes
              nodeIntegrationInSubFrames: true,
              preload: path.join(contentScript, 'all_frames-preload.js')
            }
          })
        })

        afterEach(() =>
          closeWindow(w).then(() => {
            w = null as unknown as BrowserWindow
          })
        )

        it('applies matching rules in subframes', async () => {
          const detailsPromise = emittedNTimes(w.webContents, 'did-frame-finish-load', 2)
          w.loadFile(path.join(contentScript, 'frame-with-frame.html'))
          const frameEvents = await detailsPromise
          await Promise.all(
            frameEvents.map(async frameEvent => {
              const [, isMainFrame, , frameRoutingId] = frameEvent
              const result: any = await executeJavaScriptInFrame(
                w.webContents,
                frameRoutingId,
                `(() => {
                  const a = document.getElementById('all_frames_enabled')
                  const b = document.getElementById('all_frames_disabled')
                  return {
                    enabledColor: getComputedStyle(a).backgroundColor,
                    disabledColor: getComputedStyle(b).backgroundColor
                  }
                })()`
              )
              expect(result.enabledColor).to.equal(COLOR_RED)
              if (isMainFrame) {
                expect(result.disabledColor).to.equal(COLOR_BLUE)
              } else {
                expect(result.disabledColor).to.equal(COLOR_TRANSPARENT) // null color
              }
            })
          )
        })
      })
    })
  }

  generateTests(false, false)
  generateTests(false, true)
  generateTests(true, false)
  generateTests(true, true)
})
