const assert = require("assert")
const firefox = require('selenium-webdriver/firefox')
const {By, until, Builder} = require("selenium-webdriver")
const ScriptManager = require('selenium-webdriver/bidi/scriptManager')
const {ResultOwnership} = require("selenium-webdriver/bidi/resultOwnership");
const {ArgumentValue} = require("selenium-webdriver/bidi/argumentValue");
const {LocalValue, RemoteReferenceType, ReferenceValue} = require("selenium-webdriver/bidi/protocolValue");
const {EvaluateResultType} = require("selenium-webdriver/bidi/evaluateResult");
const BrowsingContext = require("selenium-webdriver/bidi/browsingContext");
const {WebDriverError} = require("selenium-webdriver/lib/error");
const {RealmType} = require("selenium-webdriver/bidi/realmInfo");
const LogInspector = require("selenium-webdriver/bidi/logInspector");
describe('Script commands', function () {
  let driver
  beforeEach(async function () {
    driver = new Builder()
      .forBrowser('firefox')
      .setFirefoxOptions(new firefox.Options().enableBidi())
      .build()
  })
  afterEach(async function () {
    await driver.quit()
  })
  it('can call function', async function () {
    const id = await driver.getWindowHandle()
    const manager = await ScriptManager(id, driver)
    let argumentValues = []
    let value = new ArgumentValue(LocalValue.createNumberValue(22))
    argumentValues.push(value)
    let mapValue = {some_property: LocalValue.createNumberValue(42)}
    let thisParameter = new ArgumentValue(LocalValue.createObjectValue(mapValue)).asMap()
    const result = await manager.callFunctionInBrowsingContext(
      id,
      'function processWithPromise(argument) {' +
      'return new Promise((resolve, reject) => {' +
      'setTimeout(() => {' +
      'resolve(argument + this.some_property);' +
      '}, 1000)' +
      '})' +
      '}',
      true,
      argumentValues,
      thisParameter,
      ResultOwnership.ROOT)
    assert.equal(result.resultType, EvaluateResultType.SUCCESS)
    assert.equal(result.result.value, 64)
  })
  it('can call function with declaration', async function () {
    const id = await driver.getWindowHandle()
    const manager = await ScriptManager(id, driver)
    const result = await manager.callFunctionInBrowsingContext(id, '()=>{return 1+2;}', false)
    assert.equal(result.resultType, EvaluateResultType.SUCCESS)
    assert.equal(result.result.value, 3)
  })
  it('can call function to get element', async function () {
    await driver.get('https://www.selenium.dev/selenium/web/bidi/logEntryAdded')
    const id = await driver.getWindowHandle()
    const manager = await ScriptManager(id, driver)
    const result = await manager.callFunctionInBrowsingContext(
      id,
      '() => document.getElementById("consoleLog")',
      false,
    )
    assert.equal(result.resultType, EvaluateResultType.SUCCESS)
    assert.notEqual(result.realmId, null)
    assert.equal(result.result.type, 'node')
    assert.notEqual(result.result.value, null)
    assert.notEqual(result.result.value.nodeType, null)
  })
  it('can call function with arguments', async function () {
    const id = await driver.getWindowHandle()
    const manager = await ScriptManager(id, driver)
    let argumentValues = []
    let value1 = new ArgumentValue(LocalValue.createStringValue('ARGUMENT_STRING_VALUE'))
    let value2 = new ArgumentValue(LocalValue.createNumberValue(42))
    argumentValues.push(value1)
    argumentValues.push(value2)
    const result = await manager.callFunctionInBrowsingContext(
      id,
      '(...args)=>{return args}',
      false,
      argumentValues,
    )
    assert.equal(result.resultType, EvaluateResultType.SUCCESS)
    assert.equal(result.result.value.length, 2)
  })
  it('can call function with await promise', async function () {
    const id = await driver.getWindowHandle()
    const manager = await ScriptManager(id, driver)
    const result = await manager.callFunctionInBrowsingContext(
      id,
      'async function() {{\n' +
      '            await new Promise(r => setTimeout(() => r(), 0));\n' +
      '            return "SOME_DELAYED_RESULT";\n' +
      '          }}',
      true,
    )
    assert.equal(result.resultType, EvaluateResultType.SUCCESS)
    assert.equal(result.result.value, 'SOME_DELAYED_RESULT')
  })
  it('can call function with await promise false', async function () {
    const id = await driver.getWindowHandle()
    const manager = await ScriptManager(id, driver)
    const result = await manager.callFunctionInBrowsingContext(
      id,
      'async function() {{\n' +
      '            await new Promise(r => setTimeout(() => r(), 0));\n' +
      '            return "SOME_DELAYED_RESULT";\n' +
      '          }}',
      false,
    )
    assert.equal(result.resultType, EvaluateResultType.SUCCESS)
    assert.equal(result.result.type, 'promise')
  })
  it('can call function with this parameter', async function () {
    const id = await driver.getWindowHandle()
    const manager = await ScriptManager(id, driver)
    let mapValue = {some_property: LocalValue.createNumberValue(42)}
    let thisParameter = new ArgumentValue(LocalValue.createObjectValue(mapValue)).asMap()
    const result = await manager.callFunctionInBrowsingContext(
      id,
      'function(){return this.some_property}',
      false,
      null,
      thisParameter,
    )
    assert.equal(result.resultType, EvaluateResultType.SUCCESS)
    assert.equal(result.result.value, 42)
  })
  it('can call function with ownership root', async function () {
    const id = await driver.getWindowHandle()
    const manager = await ScriptManager(id, driver)
    const result = await manager.callFunctionInBrowsingContext(
      id,
      'async function(){return {a:1}}',
      true,
      null,
      null,
      ResultOwnership.ROOT,
    )
    assert.equal(result.resultType, EvaluateResultType.SUCCESS)
  })
  it('can call function with ownership none', async function () {
    const id = await driver.getWindowHandle()
    const manager = await ScriptManager(id, driver)
    const result = await manager.callFunctionInBrowsingContext(
      id,
      'async function(){return {a:1}}',
      true,
      null,
      null,
      ResultOwnership.NONE,
    )
    assert.equal(result.resultType, EvaluateResultType.SUCCESS)
    assert.notEqual(result.realmId, null)
    assert.equal(result.result.handle, undefined)
    assert.notEqual(result.result.value, null)
  })
  it('can call function that throws exception', async function () {
    const id = await driver.getWindowHandle()
    const manager = await ScriptManager(id, driver)
    const result = await manager.callFunctionInBrowsingContext(id, '))) !!@@## some invalid JS script (((', false)
    assert.equal(result.resultType, EvaluateResultType.EXCEPTION)
    assert.equal(result.exceptionDetails.exception.type, 'error')
    assert.equal(result.exceptionDetails.text, "SyntaxError: expected expression, got ')'")
    assert.equal(result.exceptionDetails.columnNumber, 39)
    assert.equal(result.exceptionDetails.stackTrace.callFrames.length, 0)
  })
  it('can call function in a sandbox', async function () {
    const id = await driver.getWindowHandle()
    const manager = await ScriptManager(id, driver)
    await manager.callFunctionInBrowsingContext(id, '() => { window.foo = 2; }', true, null, null, null, 'sandbox')
    const resultInSandbox = await manager.callFunctionInBrowsingContext(
      id,
      '() => window.foo',
      true,
      null,
      null,
      null,
      'sandbox',
    )
    assert.equal(resultInSandbox.resultType, EvaluateResultType.SUCCESS)
  })
  it('can call function in a realm', async function () {
    const firstTab = await driver.getWindowHandle()
    await driver.switchTo().newWindow('tab')
    const manager = await ScriptManager(firstTab, driver)
    const realms = await manager.getAllRealms()
    const realmId = realms[0].realmId
    await manager.callFunctionInRealm(realmId, '() => { window.foo = 3; }', true)
    const result = await manager.callFunctionInRealm(realmId, '() => window.foo', true)
    assert.equal(result.resultType, EvaluateResultType.SUCCESS)
    assert.equal(result.result.value, 3)
  })
  it('can evaluate script', async function () {
    const id = await driver.getWindowHandle()
    const manager = await ScriptManager(id, driver)
    const result = await manager.evaluateFunctionInBrowsingContext(id, '1 + 2', true)
    assert.equal(result.resultType, EvaluateResultType.SUCCESS)
    assert.equal(result.result.value, 3)
  })
  it('can evaluate script that throws exception', async function () {
    const id = await driver.getWindowHandle()
    const manager = await ScriptManager(id, driver)
    const result = await manager.evaluateFunctionInBrowsingContext(
      id,
      '))) !!@@## some invalid JS script (((',
      false,
    )
    assert.equal(result.resultType, EvaluateResultType.EXCEPTION)
    assert.equal(result.exceptionDetails.exception.type, 'error')
    assert.equal(result.exceptionDetails.text, "SyntaxError: expected expression, got ')'")
    assert.equal(result.exceptionDetails.columnNumber, 39)
    assert.equal(result.exceptionDetails.stackTrace.callFrames.length, 0)
  })
  it('can evaluate script with result ownership', async function () {
    const id = await driver.getWindowHandle()
    const manager = await ScriptManager(id, driver)
    const result = await manager.evaluateFunctionInBrowsingContext(
      id,
      'Promise.resolve({a:1})',
      true,
      ResultOwnership.ROOT,
    )
    assert.equal(result.resultType, EvaluateResultType.SUCCESS)
    assert.notEqual(result.result.handle, null)
  })
  it('can evaluate in a sandbox', async function () {
    const id = await driver.getWindowHandle()
    const manager = await ScriptManager(id, driver)
    await manager.evaluateFunctionInBrowsingContext(id, 'window.foo = 2', true, null, 'sandbox')
    const resultInSandbox = await manager.evaluateFunctionInBrowsingContext(id, 'window.foo', true, null, 'sandbox')
    assert.equal(resultInSandbox.resultType, EvaluateResultType.SUCCESS)
    assert.equal(resultInSandbox.result.value, 2)
  })
  it('can evaluate in a realm', async function () {
    const firstTab = await driver.getWindowHandle()
    await driver.switchTo().newWindow('tab')
    const manager = await ScriptManager(firstTab, driver)
    const realms = await manager.getAllRealms()
    const realmId = realms[0].realmId
    await manager.evaluateFunctionInRealm(realmId, 'window.foo = 3', true)
    const result = await manager.evaluateFunctionInRealm(realmId, 'window.foo', true)
    assert.equal(result.resultType, EvaluateResultType.SUCCESS)
    assert.equal(result.result.value, 3)
  })
  it('can disown handles', async function () {
    const id = await driver.getWindowHandle()
    const manager = await ScriptManager(id, driver)
    const browsingContext = await BrowsingContext(driver, {browsingContextId: id})
    const info = await browsingContext.navigate(
      'https://www.selenium.dev/selenium/web/dynamic.html',
      'complete'
    )
    await driver.findElement(By.id('adder')).click()
    await driver.wait(until.elementLocated(By.id('box0')), 10000)
    const evaluateResult = await manager.evaluateFunctionInBrowsingContext(
      id,
      "document.querySelector('.redbox');",
      false,
      ResultOwnership.ROOT,
    )
    assert.equal(evaluateResult.resultType, EvaluateResultType.SUCCESS)
    let boxId = evaluateResult.result.handle
    await manager.disownBrowsingContextScript(id, boxId)
    await manager.callFunctionInBrowsingContext(id, 'arg => arg.a', false).catch((error) => {
      assert(error instanceof WebDriverError)
    })
  })
  it('can disown handles in realm', async function () {
    const id = await driver.getWindowHandle()
    const manager = await ScriptManager(id, driver)
    const browsingContext = await BrowsingContext(driver, {browsingContextId: id})
    const info = await browsingContext.navigate(
      'https://www.selenium.dev/selenium/web/dynamic.html',
      'complete'
    )
    await driver.findElement(By.id('adder')).click()
    await driver.wait(until.elementLocated(By.id('box0')), 10000)
    const realms = await manager.getAllRealms()
    const realmId = realms[0].realmId
    const evaluateResult = await manager.evaluateFunctionInBrowsingContext(
      id,
      "document.querySelector('.redbox');",
      false,
      ResultOwnership.ROOT,
    )
    assert.equal(evaluateResult.resultType, EvaluateResultType.SUCCESS)
    let boxId = evaluateResult.result.handle
    let argumentValues = []
    let value1 = new ArgumentValue(new ReferenceValue(RemoteReferenceType.HANDLE, boxId))
    argumentValues.push(value1)
    let checkHandle = await manager.callFunctionInBrowsingContext(id, 'arg => arg.a', false, argumentValues)
    assert.equal(checkHandle.resultType, EvaluateResultType.SUCCESS)
    await manager.disownRealmScript(realmId, boxId)
    await manager.callFunctionInBrowsingContext(id, 'arg => arg.a', false).catch((error) => {
      assert(error instanceof WebDriverError)
    })
  })
  it('can get all realms', async function () {
    const firstWindow = await driver.getWindowHandle()
    await driver.switchTo().newWindow('window')
    const secondWindow = await driver.getWindowHandle()
    const manager = await ScriptManager(firstWindow, driver)
    const realms = await manager.getAllRealms()
    assert.equal(realms.length, 2)
  })
  it('can get realm by type', async function () {
    const firstWindow = await driver.getWindowHandle()
    await driver.switchTo().newWindow('window')
    const secondWindow = await driver.getWindowHandle()
    const manager = await ScriptManager(firstWindow, driver)
    const realms = await manager.getRealmsByType(RealmType.WINDOW)
    assert.equal(realms.length, 2)
  })
  it('can get realm in browsing context', async function () {
    const windowId = await driver.getWindowHandle()
    await driver.switchTo().newWindow('tab')
    const tabId = await driver.getWindowHandle()
    const manager = await ScriptManager(windowId, driver)
    const realms = await manager.getRealmsInBrowsingContext(tabId)
    const tabRealm = realms[0]
    assert.equal(tabRealm.realmType, RealmType.WINDOW)
  })
  it('can get realm in browsing context by type', async function () {
    const windowId = await driver.getWindowHandle()
    await driver.switchTo().newWindow('tab')
    const manager = await ScriptManager(windowId, driver)
    const realms = await manager.getRealmsInBrowsingContextByType(windowId, RealmType.WINDOW)
    const windowRealm = realms[0]
    assert.equal(windowRealm.realmType, RealmType.WINDOW)
  })
  it('can add preload script', async function () {
    const id = await driver.getWindowHandle()
    const manager = await ScriptManager(id, driver)
    const scriptId = await manager.addPreloadScript('() => {{ console.log(\'{preload_script_console_text}\') }}')
    let logEntry = null
    const inspector = await LogInspector(driver)
    await inspector.onConsoleEntry(function (log) {
      logEntry = log
    })
    await driver.get('https://www.selenium.dev/selenium/blank')
    assert.equal(logEntry.text, '{preload_script_console_text}')
  })
  it('can add preload script to sandbox', async function () {
    const id = await driver.getWindowHandle()
    const manager = await ScriptManager(id, driver)
    await manager.addPreloadScript('() => { window.bar = 2; }', undefined, 'sandbox')
    await driver.get('https://www.selenium.dev/selenium/blank')
    let result_in_sandbox = await manager.evaluateFunctionInBrowsingContext(
      id,
      'window.bar',
      true,
      null,
      'sandbox',
    )
    assert.equal(result_in_sandbox.result.type, 'number')
    assert.equal(result_in_sandbox.result.value, 2)
  })
  it('can remove preload script', async function () {
    const id = await driver.getWindowHandle()
    const manager = await ScriptManager(id, driver)
    const scriptId = await manager.addPreloadScript('() => {{ console.log(\'{preload_script_console_text}\') }}')
    let logEntry = null
    const inspector = await LogInspector(driver)
    await inspector.onConsoleEntry(function (log) {
      logEntry = log
    })
    await manager.removePreloadScript(scriptId)
    await driver.get('https://www.selenium.dev/selenium/blank')
    assert.equal(logEntry, null)
  })
})