require('./log')
const {
  app,
  BrowserWindow,
  Menu,
  MenuItem,
  Tray,
  screen: electronScreen,
  shell,
  dialog,
} = require('electron')
const isDev = require('electron-is-dev')
const path = require('path')
const startIpfs = require('./startIpfs')
const { URL } = require('node:url')

let startIpfsError
startIpfs().catch((e) => {
  startIpfsError = e
  console.error(e)
})

// use common user agent instead of electron so img, video, audio, iframe elements don't get blocked
// https://www.whatismybrowser.com/guides/the-latest-version/chrome
// https://www.whatismybrowser.com/guides/the-latest-user-agent/chrome
// NOTE: eventually should probably fake sec-ch-ua header as well
let fakeUserAgent =  'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36'
if (process.platform === 'darwin')
  fakeUserAgent = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 13_5_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36'
if (process.platform === 'linux')
  fakeUserAgent = 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36'
const realUserAgent = `plebchan/${require('../package.json').version}`

// add right click menu
const contextMenu = require('electron-context-menu')
contextMenu({
  // prepend custom buttons to top
  prepend: (defaultActions, parameters, browserWindow) => [
    {
      label: 'Back',
      visible: parameters.mediaType === 'none',
      enabled: browserWindow?.webContents?.canGoBack(),
      click: () => browserWindow?.webContents?.goBack(),
    },
    {
      label: 'Forward',
      visible: parameters.mediaType === 'none',
      enabled: browserWindow?.webContents?.canGoForward(),
      click: () => browserWindow?.webContents?.goForward(),
    },
    {
      label: 'Reload',
      visible: parameters.mediaType === 'none',
      click: () => browserWindow?.webContents?.reload(),
    },
  ],
  showLookUpSelection: false,
  showCopyImage: true,
  showCopyImageAddress: true,
  showSaveImageAs: true,
  showSaveLinkAs: true,
  showInspectElement: true,
  showServices: false,
  showSearchWithGoogle: false,
})

const createMainWindow = () => {
  let mainWindow = new BrowserWindow({
    width: 1000,
    height: 600,
    show: false,
    backgroundColor: 'white',
    webPreferences: {
      webSecurity: true, // must be true or iframe embeds like youtube can do remote code execution
      nodeIntegration: false,
      contextIsolation: true,
      devTools: true, // TODO: change to isDev when no bugs left
      preload: path.join(__dirname, 'preload.js'),
    },
  })

  // set fake user agent
  mainWindow.webContents.userAgent = fakeUserAgent

  // set custom user agent and other headers for window.fetch requests to prevent origin errors
  mainWindow.webContents.session.webRequest.onBeforeSendHeaders({urls: ['*://*/*']}, (details, callback) => {
    const isIframe = details.frame.parent !== null
    // if not a fetch request (or fetch request is from within iframe), do nothing, filtering webRequest by types doesn't seem to work
    if (details.resourceType !== 'xhr' || isIframe) {
      return callback({requestHeaders: details.requestHeaders})
    }
    // add privacy
    details.requestHeaders['User-Agent'] = realUserAgent
    details.requestHeaders['sec-ch-ua'] = undefined
    details.requestHeaders['sec-ch-ua-platform'] = undefined
    details.requestHeaders['sec-ch-ua-mobile'] = undefined
    details.requestHeaders['Sec-Fetch-Dest'] = undefined
    details.requestHeaders['Sec-Fetch-Mode'] = undefined
    details.requestHeaders['Sec-Fetch-Site'] = undefined
    // prevent origin errors
    details.requestHeaders['Origin'] = undefined
    callback({requestHeaders: details.requestHeaders})
  })

  // fix cors errors for window.fetch. must not be enabled for iframe or can cause remote code execution
  mainWindow.webContents.session.webRequest.onHeadersReceived({urls: ['*://*/*']}, (details, callback) => {
    const isIframe = details.frame.parent !== null
    // if not a fetch request (or fetch request is from within iframe), do nothing, filtering webRequest by types doesn't seem to work
    if (details.resourceType !== 'xhr' || isIframe) {
      return callback({responseHeaders: details.responseHeaders})
    }
    // must delete lower case headers or both '*, *' could get added
    delete details.responseHeaders['access-control-allow-origin']
    delete details.responseHeaders['access-control-allow-headers']
    delete details.responseHeaders['access-control-allow-methods']
    delete details.responseHeaders['access-control-expose-headers']
    details.responseHeaders['Access-Control-Allow-Origin'] = '*'
    details.responseHeaders['Access-Control-Allow-Headers'] = '*'
    details.responseHeaders['Access-Control-Allow-Methods'] = '*'
    details.responseHeaders['Access-Control-Expose-Headers'] = '*'
    callback({responseHeaders: details.responseHeaders})
  })

  const startURL = isDev
    ? 'http://localhost:3000'
    : `file://${path.join(__dirname, '../build/index.html')}`

  mainWindow.loadURL(startURL)

  mainWindow.once('ready-to-show', async () => {
    // make sure back button is disabled on launch
    mainWindow.webContents.clearHistory()

    mainWindow.show()

    if (isDev) {
      mainWindow.openDevTools()
    }

    if (startIpfsError) {
      dialog.showErrorBox('IPFS error', startIpfsError.message)
    }
  })

  mainWindow.on('closed', () => {
    mainWindow = null
  })

  // don't open new windows
  mainWindow.webContents.on('new-window', (event, url) => {
    event.preventDefault()
    mainWindow.loadURL(url)
  })

  // open links in external browser
  // do not open links in plebchan or will lead to remote execution
  mainWindow.webContents.on('will-navigate', (e, originalUrl) => {
    if (originalUrl != mainWindow.webContents.getURL()) {
      e.preventDefault()
      try {
        // do not let the user open any url with shell.openExternal
        // or it will lead to remote execution https://benjamin-altpeter.de/shell-openexternal-dangers/

        // only open valid https urls to prevent remote execution
        // will throw if url isn't valid
        const validatedUrl = new URL(originalUrl)
        let serializedUrl = ''

        // make an exception for ipfs stats
        if (validatedUrl.toString() === 'http://localhost:5001/webui/') {
          serializedUrl = validatedUrl.toString()
        } else if (validatedUrl.protocol === 'https:') {
          // open serialized url to prevent remote execution
          serializedUrl = validatedUrl.toString()
        } else {
          throw Error(`can't open url '${originalUrl}', it's not https and not the allowed http exception`)
        }

        shell.openExternal(serializedUrl)
      } catch (e) {
        console.warn(e)
      }
    }
  })

  // open links (with target="_blank") in external browser
  // do not open links in plebchan or will lead to remote execution
  mainWindow.webContents.setWindowOpenHandler(({url}) => {
    const originalUrl = url
    try {
      // do not let the user open any url with shell.openExternal
      // or it will lead to remote execution https://benjamin-altpeter.de/shell-openexternal-dangers/

      // only open valid https urls to prevent remote execution
      // will throw if url isn't valid
      const validatedUrl = new URL(originalUrl)
      let serializedUrl = ''

      // make an exception for ipfs stats
      if (validatedUrl.toString() === 'http://localhost:5001/webui/') {
        serializedUrl = validatedUrl.toString()
      } else if (validatedUrl.protocol === 'https:') {
        // open serialized url to prevent remote execution
        serializedUrl = validatedUrl.toString()
      } else {
        throw Error(`can't open url '${originalUrl}', it's not https and not the allowed http exception`)
      }

      shell.openExternal(serializedUrl)
    } catch (e) {
      console.warn(e)
    }
    return {action: 'deny'}
  })

  // deny permissions like location, notifications, etc https://www.electronjs.org/docs/latest/tutorial/security#5-handle-session-permission-requests-from-remote-content
  mainWindow.webContents.session.setPermissionRequestHandler((webContents, permission, callback) => {
    // deny all permissions
    return callback(false)
  })

  // deny attaching webview https://www.electronjs.org/docs/latest/tutorial/security#12-verify-webview-options-before-creation
  mainWindow.webContents.on('will-attach-webview', (e, webPreferences, params) => {
    // deny all
    e.preventDefault()
  })

  if (process.platform !== 'darwin') {
    // tray
    const trayIconPath = path.join(
      __dirname,
      '..',
      isDev ? 'public' : 'build',
      'electron-tray-icon.png'
    )
    const tray = new Tray(trayIconPath)
    tray.setToolTip('plebchan')
    const trayMenu = Menu.buildFromTemplate([
      {
        label: 'Open plebchan',
        click: () => {
          mainWindow.show()
        },
      },
      {
        label: 'Quit plebchan',
        click: () => {
          mainWindow.destroy()
          app.quit()
        },
      },
    ])
    tray.setContextMenu(trayMenu)

    // show/hide on tray right click
    tray.on('right-click', () => {
      mainWindow.isVisible() ? mainWindow.hide() : mainWindow.show()
    })

    // close to tray
    if (!isDev) {
      let isQuiting = false
      app.on('before-quit', () => {
        isQuiting = true
      })
      mainWindow.on('close', (event) => {
        if (!isQuiting) {
          event.preventDefault()
          mainWindow.hide()
          event.returnValue = false
        }
      })
    }
  }

  const appMenuBack = new MenuItem({
    label: '←',
    enabled: mainWindow?.webContents?.canGoBack(),
    click: () => mainWindow?.webContents?.goBack(),
  })
  const appMenuForward = new MenuItem({
    label: '→',
    enabled: mainWindow?.webContents?.canGoForward(),
    click: () => mainWindow?.webContents?.goForward(),
  })
  const appMenuReload = new MenuItem({
    label: '⟳',
    role: 'reload',
    click: () => mainWindow?.webContents?.reload(),
  })

  // application menu
  // hide useless electron help menu
  if (process.platform === 'darwin') {
    const appMenu = Menu.getApplicationMenu()
    appMenu.insert(1, appMenuBack)
    appMenu.insert(2, appMenuForward)
    appMenu.insert(3, appMenuReload)
    Menu.setApplicationMenu(appMenu)
  } else {
    // Other platforms
    const originalAppMenuWithoutHelp = Menu.getApplicationMenu()?.items.filter(
      (item) => item.role !== 'help'
    )
    const appMenu = [appMenuBack, appMenuForward, appMenuReload, ...originalAppMenuWithoutHelp]
    Menu.setApplicationMenu(Menu.buildFromTemplate(appMenu))
  }
}

app.whenReady().then(() => {
  createMainWindow()

  app.on('activate', () => {
    if (!BrowserWindow.getAllWindows().length) {
      createMainWindow()
    }
  })
})

app.on('window-all-closed', () => {
  if (process.platform !== 'darwin') {
    app.quit()
  }
})
