import fs from 'node:fs';
import jwt from 'jsonwebtoken';
import Router from '@koa/router';

import {JWT_TOKEN_SECRET} from '../util/secret';
import log from '../util/logger';

/**
 * Load keys to sign token and verify it.
 * In a real app the authentication would be performed on a separate server.
 */
let privateKey: string;
let publicKey: string;

try {
  log.info('Loading keys');
  publicKey = fs.readFileSync('./key/jwt.pub', 'utf8');
  privateKey = fs.readFileSync('./key/jwt.key', 'utf8');
  log.info('Loaded keys');
} catch (err) {
  log.error('Unable to load keys');
  process.exit(-1);
}

/**
 * Define sample JWT controller.
 */
export const routerJWT = new Router({prefix: '/api/v1'});

/**
 * Sign JWT using a secret, return using a cookie.
 */
routerJWT.post('/login/secret', async (ctx) => {
  const user = ctx.request.body.name;
  const jwtToken = jwt.sign(
    {name: user, exp: Math.floor(Date.now() / 1000) + 5},
    JWT_TOKEN_SECRET,
  );
  ctx.cookies.set('jwt', jwtToken);
  ctx.body = {jwt: jwtToken};
});

/**
 * Sign JWT using a private SSL key, return using a cookie.
 */
routerJWT.post('/login/key', async (ctx) => {
  try {
    const user = ctx.request.body.name;
    const jwtToken = jwt.sign(
      {name: user, exp: Math.floor(Date.now() / 1000) + 20},
      privateKey,
      {algorithm: 'RS256'},
    );
    ctx.cookies.set('jwt', jwtToken);
    ctx.body = {jwt: jwtToken};
  } catch (err) {
    ctx.log.error('Failed to sign key');
  }
});

/**
 * Verify JWT using a secret, using a cookie.
 */
routerJWT.get('/protected/secret', async (ctx) => {
  try {
    const jwtToken = ctx.cookies.get('jwt') ?? '';
    process.stdout.write(`jwt: ${jwtToken}\n`);
    const jwtDecoded = jwt.verify(jwtToken, JWT_TOKEN_SECRET);

    if (jwtDecoded['name'] === 'Rajinder') {
      ctx.body = 'Passed';
    } else {
      ctx.log.error('Invalid User');
      ctx.throw(403, 'Invalid User');
    }
  } catch (ex) {
    ctx.log.error('Invalid JWT token');
    ctx.throw(401, 'Invalid JWT token');
  }
});

/**
 * Verify JWT using a SSL public key, using a cookie.
 */
routerJWT.get('/protected/key', async (ctx) => {
  const jwtToken = ctx.cookies.get('jwt') ?? '';
  ctx.log.info('jwt: ', jwtToken);
  jwt.verify(
    jwtToken,
    publicKey,
    {algorithms: ['RS256']},
    function (err, jwtDecoded) {
      // if token alg != RS256,  err == invalid signature
      if (jwtDecoded && jwtDecoded['name'] === 'Rajinder') {
        ctx.body = 'Passed';
      } else {
        log.error('Invalid User');
        ctx.throw(401, 'Invalid User');
      }
    },
  );
});

/**
 * Verify JWT using a SSL public key, using bearer header.
 * Authorization: Bearer xx.yy.zz
 */
routerJWT.get('/protected/bearer', async (ctx) => {
  const bearerToken = ctx.get('Authorization');
  const tok = (bearerToken && bearerToken.split(' ')) ?? [];
  const jwtToken = tok[1];

  ctx.log.info('jwt: ', jwtToken);
  jwt.verify(
    jwtToken,
    publicKey,
    {algorithms: ['RS256']},
    function (err, jwtDecoded) {
      // if token alg != RS256,  err == invalid signature
      if (jwtDecoded && jwtDecoded['name'] === 'Rajinder') {
        ctx.body = 'Passed';
      } else {
        ctx.log.error('Invalid User');
        ctx.throw(401, 'Invalid User');
      }
    },
  );
});
