import Koa from "koa"
import serve from "koa-static"
import http from "http"
import webpack from "webpack"
import path from "path"
import {devMiddleware, hotMiddleware} from "koa-webpack-middleware"
import fetch from "node-fetch"
import VirtualModulesPlugin from "webpack-virtual-modules"
import MemoryFS from "memory-fs"
import realFs from "fs"
const UglifyJsPlugin = require('uglifyjs-webpack-plugin')

const fs = new MemoryFS()

const externalDeps = {
  "react-dom": `commonjs react-dom`,
  "react": `commonjs react`,
  "classnames": `commonjs classnames`,
  "core-js": `commonjs core-js`,
  "history": `commonjs history`,
  "react-redux": `commonjs react-redux`,
  "redux": `commonjs redux`,
}

const sandboxVirtualModules = new VirtualModulesPlugin()

const loadTmpModules = () => {
  const cwd = process.cwd()
  try {
    const tmpContent = realFs.readFileSync(path.resolve(cwd, `src/tmp-content.json`), `utf8`)
    sandboxVirtualModules.writeModule(`node_modules/weblium/block-content.json`, tmpContent)
  } catch (e) {
    sandboxVirtualModules.writeModule(`node_modules/weblium/block-content.json`, '{}')
  }
  try {
    const tmpModifier = realFs.readFileSync(path.resolve(cwd, `src/tmp-modifier.json`), `utf8`)
    sandboxVirtualModules.writeModule(`node_modules/weblium/block-modifier.json`, tmpModifier)
  } catch (e) {
    sandboxVirtualModules.writeModule(`node_modules/weblium/block-modifier.json`, '{}')
  }
  try {
    const blockMap = fs.readFileSync(`/block.js.map`).toString(`utf8`)
    sandboxVirtualModules.writeModule(`node_modules/block.js.map`, blockMap)
  } catch (e) {
    console.log(e)
  }

  try {
    const themeBlocks = realFs.readFileSync(path.resolve(cwd, 'theme-blocks.txt'), 'utf8')
    sandboxVirtualModules.writeModule(
      'node_modules/weblium/theme-blocks',
      `module.exports = (function() {return ${JSON.stringify(themeBlocks)}.split('\\n').filter(Boolean)})()`)
  } catch (e) {
    sandboxVirtualModules.writeModule('node_modules/weblium/theme-blocks', 'module.exports = []')
  }

}
const getSandboxConfig = async (type = '') => {
  const cwd = process.cwd()
  return {
    entry: [
      ...(type.match(/editor|theme/) ? [] : [`babel-polyfill`]),
      `./app/sandbox/client/index.js`,
      `webpack-hot-middleware/client?path=/__webpack_hmr&timeout=20000`,
    ],
    context: path.join(__dirname, `../../..`),
    resolve: {
      modules: [`node_modules`],
    },
    module: {
      rules: [
        {
          test: /\.(js|jsx)$/,
          loader: `babel-loader`,
          exclude: /node_modules[/\\].+[/\\]node_modules|weblium[/\\]editor/,
          options: {
            presets: [`env`, `react`],
            plugins: [`transform-decorators-legacy`, `transform-class-properties`, `transform-object-rest-spread`],
          },
        },
        {
          test: /\.s?css$/,
          use: [
            {
              loader: `style-loader`,
              options: {
                singleton: true,
              },
            },
            {
              loader: `css-loader`,
              options: {
                sourceMap: true,
                importLoaders: 1,
              },
            },
            {
              loader: `postcss-loader`, // translates CSS into CommonJS
              options: {
                plugins: () => [
                  require('postcss-nested'),
                ],
              },
            },
          ],
        },
      ],
    },
    plugins: [
      sandboxVirtualModules,
      new webpack.HotModuleReplacementPlugin(),
      new webpack.ProvidePlugin({
        React: `react`,
        PropTypes: `prop-types`,
        _: `lodash/fp`,
        classNames: `classnames`,
      }),
      new webpack.DefinePlugin({
        __DEV__: false,
        __PROD__: true,
        "process.env.NODE_ENV": JSON.stringify(process.env.NODE_ENV || 'development'),
        __MODE__: JSON.stringify(type),
      }),
    ],
    devtool: `cheap-module-source-map`,
  }
}

const getBlockConfig = async (type = '') => {
  const cwd = process.cwd()
  const resolve = (module) => path.resolve(__dirname, `../../../node_modules`, module)
  const styleLoader = type.match(/theme/)
    ? {
      test: /\.s?css$/,
      exclude: /node_modules/,
      use: [
        {
          loader: `style-loader`,
          options: {
            singleton: false,
          },
        }, {
          loader: 'css-loader',
          options: {
            importLoaders: 3,
          },
        }, {
          loader: 'postcss-loader',
          options: {
            plugins: () => [
              require('postcss-prefixer')({
                prefix: 'ui-',
                ignore: ['.site-root'],
              }),
            ],
          },
        },
        'sass-loader',
        {
          loader: `postcss-loader`, // translates CSS into CommonJS
          options: {
            syntax: 'postcss-scss',
            plugins: () => [
              require('postcss-import'),
              require('postcss-custom-media'),
              require('postcss-flexbugs-fixes'),
            ],
          },
        },
      ],
    }
    : {
      test: /\.s?css$/,
      exclude: /node_modules/,
      use: [
        {
          loader: `style-loader`,
          options: {
            singleton: false,
          },
        },
        {
          loader: `css-loader`,
          options: {
            importLoaders: 1,
            modules: true,
            sourceMap: true,
            localIdentName: `[local]_[hash:base64:3]`,
            camelCase: `dashes`,
          },
        },
        {
          loader: `postcss-loader`, // translates CSS into CommonJS
          options: {
            plugins: () => [
              require('postcss-import'),
              require('postcss-nested'), //theme disable
              require('postcss-custom-media'),
              require('./postcss-css-variables/index.js'),
              require('postcss-flexbugs-fixes'),
            ],
          },
        },
      ],
    }
  const entry = {block: './src/index.js'}
  return {
    target: 'node',
    entry,
    output: {
      path: `/`,
      filename: `block.js`,
      libraryTarget: `umd`,
    },
    context: cwd,
    resolveLoader: {
      modules: [path.resolve(__dirname, `../../../node_modules`)],
    },
    resolve: {
      extensions: [`.js`],
      modules: [path.resolve(cwd, `node_modules`)],
    },
    module: {
      rules: [
        {
          test: /\.(js|jsx)$/,
          loader: `babel-loader`,
          options: {
            presets: [require(`babel-preset-env`), require(`babel-preset-react`)],
            plugins: [
              require(`babel-plugin-transform-class-properties`),
              require(`babel-plugin-transform-object-rest-spread`),
            ],
          },
        },
        {
          include: /node_modules/,
          test: /\.s?css$/,
          use: [{
            loader: `style-loader`,
            options: {
              singleton: false,
            },
          },
          {
            loader: `css-loader`,
            options: {
              modules: false,
            },
          },
          ],
        }, styleLoader,
      ],
    },
    externals: (context, request, callback) => {
      if (request.match(/style-loader\/lib\/addStyles\.js/)) {
        return callback(null, `commonjs style-loader/lib/addStyles.js`)
      }
      if (request.startsWith(`weblium/editor`)) {
        return callback(null, `weblium/editor`)
      }
      if (request.startsWith(`lodash`)) {
        return callback(null, `commonjs ${request}`)
      }
      if (request.startsWith(`react-router`)) {
        return callback(null, `commonjs ${request}`)
      }
      if (request.startsWith(`core-js`)) {
        return callback(null, `commonjs ${request}`)
      }
      return externalDeps[request] ? callback(null, externalDeps[request]) : callback()
    },
    plugins: [],
    devtool: `cheap-module-source-map`,
  }
}

let server
export const startServer = async ({port = 9999, uikitsList, blockUikitId, type, api}) => {
  const cwd = process.cwd()
  if (server) {
    return server
  }
  if (!realFs.statSync(path.join(cwd, `src`, `index.js`)).isFile()) {
    throw new Error(`Please create src folder with index.js entrypoint`)
  }

  const sandboxConfig = await getSandboxConfig(type)
  const blockConfig = await getBlockConfig(type)
  const compileBlock = webpack(blockConfig, (err, stats) => {
    if (err) {
      console.error(err)
    }
    if (stats.hasErrors()) {
      const stat = stats.toJson()
      process.stdout.write(stat.errors.join('\n----------------\n') + '\n')
    }
  })
  const compileSandbox = webpack(sandboxConfig)
  const editorPath = type.match(/editor|theme/)
        ? path.resolve(__dirname, `../../editor-sandbox.js`)
        : path.resolve(__dirname, `../../editor-style.js`)
  const editorSrc = realFs.readFileSync(editorPath, `utf8`)

  sandboxVirtualModules.writeModule(
    `node_modules/weblium/editor.js`,
    `
      ${editorSrc}
      Object.assign(exports, exports.default)
    `,
  )

  sandboxVirtualModules.writeModule(
    `node_modules/weblium/sandbox-data.json`,
    JSON.stringify({uikitsList, blockUikitId}),
  )

  compileBlock.outputFileSystem = fs

  await new Promise((resolve, reject) =>
    compileBlock.watch({ignored: /node_modules/}, (err, stats) => {
      if (err) {
        console.log(err.stack || err)
        if (err.details) {
          console.log(err.details)
        }
        reject(err)
      }
      if (stats.hasErrors()) {
        const stat = stats.toJson()
        process.stdout.write(stat.errors.join('\n----------------\n') + '\n')
        reject()
      }
      try {
        fs.statSync(`/block.js`).isFile()
        const blockModule = fs.readFileSync(`/block.js`).toString(`utf8`)
        sandboxVirtualModules.writeModule(`node_modules/block.js`, blockModule)
        loadTmpModules()
        resolve()
      } catch (e) {
        console.log(e)
        reject(e)
      }
    }),
  )

  compileSandbox.plugin('watch', function(callback) {
    if (fs.statSync('/block.js').isFile()) {
      const blockModule = fs.readFileSync('/block.js').toString('utf8')
      sandboxVirtualModules.writeModule('node_modules/block.js', blockModule)
    }
    callback()
  })

  const app = new Koa()
  app.use(
    devMiddleware(compileSandbox, {
      // display no info to console (only warnings and errors)
      noInfo: false,
      quiet: false,
      watchOptions: {
        aggregateTimeout: 300,
        poll: false,
        ignored: /node_modules/,
      },
      publicPath: `/`,
      stats: {
        colors: true,
      },
    }),
  )
  app.use(
    hotMiddleware(compileSandbox, {
      // log: console.log,
      // path: '/__webpack_hmr',
      // heartbeat: 10 * 1000
    }),
  )

  app.use(serve(path.join(__dirname, `..`, `public`)))
  app.use(serve(path.join(cwd, `assets`)))
  server = http.createServer(app.callback())
  server.listen(port)

  await new Promise((resolve, reject) => {
    console.log('initializing api')
    const io = require("socket.io")(server, {})

    io.on("error", reject)
    io.on("connection", (socket) => {
      resolve()
      socket.on("api", ({messageId, action, ...data}) => {
        const sendError = (error) => {
          console.log(`${action}:${messageId} => `, error)
          socket.emit("api", {messageId, error: {message: Error.message || "" + error}})
        }
        try {
          console.log(`${action}:${messageId} start`, data)
          const res = api[action](data)
          res.then((responseData) => {
            console.log(`${action}:${messageId}: end`)
            socket.emit("api", {...responseData, messageId})
          }).catch(sendError)
        } catch (e) {
          sendError(e)
        }
      })
    })
  })

  port = server.address().port
  console.log(`server started on port ${port}`)
  return () => server.close()
}
