// Example of the server https is taken from here: https://engineering.circle.com/https-authorized-certs-with-node-js-315e548354a2
// Renew certificates:
// - openssl x509 -req -extfile server.cnf -days 9999 -passin "pass:password" -in server-csr.pem -CA ca-crt.pem -CAkey ca-key.pem -CAcreateserial -out server-crt.pem
// - openssl x509 -req -extfile client1.cnf -days 9999 -passin "pass:password" -in client1-csr.pem -CA ca-crt.pem -CAkey ca-key.pem -CAcreateserial -out client1-crt.pem
// - openssl x509 -req -extfile client2.cnf -days 9999 -passin "pass:password" -in client2-csr.pem -CA ca-crt.pem -CAkey ca-key.pem -CAcreateserial -out client2-crt.pem
// Verify certificates:
// - openssl verify -CAfile ca-crt.pem server-crt.pem
// - openssl verify -CAfile ca-crt.pem client1-crt.pem
// - openssl verify -CAfile ca-crt.pem client2-crt.pem
// Conversion of client1-crt.pem to certificate.pfx: https://stackoverflow.com/a/38408666/4637638
// - openssl pkcs12 -export -out certificate.pfx -inkey client1-key.pem -in client1-crt.pem -certfile ca-crt.pem
// - Overwrite certificate.pfx to example/test_assets/certificate.pfx
const express = require('express');
const http = require('http');
const net = require('net');
const https = require('https');
const cors = require('cors');
const auth = require('basic-auth');
const app = express();
const appHttps = express();
const appAuthBasic = express();
const fs = require('fs')
const path = require('path')
const bodyParser = require('body-parser');
const multiparty = require('multiparty');

var options = {
  key: fs.readFileSync('server-key.pem'),
  cert: fs.readFileSync('server-crt.pem'),
  ca: fs.readFileSync('ca-crt.pem'),
  requestCert: true,
  rejectUnauthorized: false,
  ciphers: "DEFAULT:@SECLEVEL=0"
};

appHttps.get('/', (req, res) => {
  console.log(JSON.stringify(req.headers))
	const cert = req.connection.getPeerCertificate()

  // The `req.client.authorized` flag will be true if the certificate is valid and was issued by a CA we white-listed
  // earlier in `opts.ca`. We display the name of our user (CN = Common Name) and the name of the issuer, which is
  // `localhost`.

	if (req.client.authorized) {
		res.send(`
      <html>
        <head>
        </head>
        <body>
          <h1>Authorized</h1>
        </body>
      </html>
    `);
  // They can still provide a certificate which is not accepted by us. Unfortunately, the `cert` object will be an empty
  // object instead of `null` if there is no certificate at all, so we have to check for a known field rather than
  // truthiness.

	} else if (cert.subject) {
    console.log(`Sorry ${cert.subject.CN}, certificates from ${cert.issuer.CN} are not welcome here.`);
		res.status(403).send(`
      <html>
        <head>
        </head>
        <body>
          <h1>Forbidden</h1>
        </body>
      </html>
    `);
  // And last, they can come to us with no certificate at all:
	} else {
	  console.log(`Sorry, but you need to provide a client certificate to continue.`)
		res.status(401).send(`
      <html>
        <head>
        </head>
        <body>
          <h1>Unauthorized</h1>
        </body>
      </html>
    `);
	}
	res.end()
})

// Let's create our HTTPS server and we're ready to go.
https.createServer(options, appHttps).listen(4433)



// Ensure this is before any other middleware or routes
appAuthBasic.use((req, res, next) => {
  let user = auth(req)

  if (user === undefined || user['name'] !== 'USERNAME' || user['pass'] !== 'PASSWORD') {
    res.statusCode = 401
    res.setHeader('WWW-Authenticate', 'Basic realm="Node"')
    res.send(`
        <html>
          <head>
          </head>
          <body>
            <h1>Unauthorized</h1>
          </body>
        </html>
      `);
    res.end()
  } else {
    next()
  }
});

appAuthBasic.use(express.static(__dirname + '/public'));

appAuthBasic.get("/", (req, res) => {
  console.log(JSON.stringify(req.headers))
  res.send(`
    <html>
      <head>
      </head>
      <body>
        <h1>Authorized</h1>
      </body>
    </html>
  `);
  res.end()
});

appAuthBasic.get('/test-index', (req, res) => {
    res.sendFile(__dirname + '/public/test-index.html');
});

appAuthBasic.listen(8081);


app.use(cors());

app.use(bodyParser.urlencoded({extended: false}));
// Parse JSON bodies (as sent by API clients)
app.use(bodyParser.json());

app.use(express.static(__dirname + '/public'));

app.get("/", (req, res) => {
  console.log(JSON.stringify(req.headers))
  res.send(`
    <html>
      <head>
      </head>
      <body>
        <p>HELLO</p>
      </body>
    </html>
  `);
  res.end()
})

app.get("/echo-headers", (req, res) => {
  res.send(`
    <html>
      <head>
      </head>
      <body>
        <pre style="word-wrap: break-word; white-space: pre-wrap;">${JSON.stringify(req.headers)}</pre>
      </body>
    </html>
  `);
  res.end()
})

app.get('/test-index', (req, res) => {
    res.sendFile(__dirname + '/public/test-index.html');
})

app.post("/test-post", (req, res) => {
  console.log(JSON.stringify(req.headers))
  console.log(JSON.stringify(req.body))
  res.send(`
    <html>
      <head>
      </head>
      <body>
        <p>HELLO ${req.body.name}!</p>
      </body>
    </html>
  `);
  res.end()
})

app.post("/test-ajax-post", (req, res) => {
  console.log(JSON.stringify(req.headers));
  if (req.headers["content-type"].indexOf("multipart/form-data;") === 0) {
    const form = new multiparty.Form();
    form.parse(req, function(err, fields, files) {
      console.log(fields);
      res.set("Content-Type", "application/json")
      res.send(JSON.stringify({
        "firstname": fields.firstname[0],
        "lastname": fields.lastname[0],
      }));
      res.end();
    });
  } else {
    console.log(req.body);
    res.set("Content-Type", "application/json")
    res.send(JSON.stringify({
      "firstname": req.body.firstname,
      "lastname": req.body.lastname,
    }));
    res.end();
  }
})

app.get("/test-download-file", (req, res) => {
  console.log(JSON.stringify(req.headers))
  const filePath = path.join(__dirname, 'assets', 'flutter_logo.png');
  const stat = fs.statSync(filePath);
  const file = fs.readFileSync(filePath, 'binary');
  res.setHeader('Content-Length', stat.size);
  res.setHeader('Content-Type', 'image/png');
  res.setHeader('Content-Disposition', 'attachment; filename=flutter_logo.png');
  res.write(file, 'binary');
  res.end();
})

app.listen(8082)

// Proxy server
// Create an HTTP tunneling proxy
const proxy = http.createServer((req, res) => {
  console.log('proxy response', req.url);
  res.writeHead(200, { 'Content-Type': 'text/html' });
  res.end(`
    <html>
      <head>
      </head>
      <body>
        <h1>Proxy Works</h1>
        <p id="url">${req.url}</p>
        <p id="method">${req.method}</p>
        <p id="headers">${JSON.stringify(req.headers)}</p>
      </body>
    </html>
  `);
});
proxy.on('connect', (req, clientSocket, head) => {
  console.log('proxy connect request');
  // Connect to an origin server
  // const { port, hostname } = new URL(`http://${req.url}`);
  const { port, hostname } = new URL(`http://127.0.0.1:8083`);
  const serverSocket = net.connect(port || 80, hostname, () => {
    clientSocket.write('HTTP/1.1 200 Connection Established\r\n' +
                    'Proxy-agent: Node.js-Proxy\r\n' +
                    '\r\n');
    serverSocket.write(head);
    serverSocket.pipe(clientSocket);
    clientSocket.pipe(serverSocket);
  });
});
proxy.listen(8083, null, () => {
  console.log('proxy server listening on port 8083');
});

process.on('uncaughtException', function (err) {
  console.error(err);
});