import * as plugins from '../plugins.js';

import type { Cloudly } from '../classes.cloudly.js';
import { logger } from '../logger.js';
import { Authorization } from './classes.authorization.js';
import { User } from './classes.user.js';

export interface IJwtData {
  userId: string;
  status: 'loggedIn' | 'loggedOut';
  expiresAt: number;
}

export class CloudlyAuthManager {
  cloudlyRef: Cloudly;
  public get db() {
    return this.cloudlyRef.mongodbConnector.smartdataDb;
  }
  public CUser = plugins.smartdata.setDefaultManagerForDoc(this, User);
  public CAuthorization = plugins.smartdata.setDefaultManagerForDoc(this, Authorization);

  public typedrouter = new plugins.typedrequest.TypedRouter();
  public smartjwtInstance: plugins.smartjwt.SmartJwt<IJwtData>;

  constructor(cloudlyRef: Cloudly) {
    this.cloudlyRef = cloudlyRef;
    this.cloudlyRef.typedrouter.addTypedRouter(this.typedrouter);
  }

  public async createNewSecureToken() {
    return plugins.smartunique.uniSimple('secureToken', 64);
  }

  public async start() {
    // lets setup the smartjwtInstance
    this.smartjwtInstance = new plugins.smartjwt.SmartJwt();
    await this.smartjwtInstance.init();
    const kvStore = await this.cloudlyRef.config.appData.getKvStore();

    const existingJwtKeys: plugins.tsclass.network.IJwtKeypair = (await kvStore.readKey(
      'jwtKeypair',
    )) as plugins.tsclass.network.IJwtKeypair;

    if (!existingJwtKeys) {
      await this.smartjwtInstance.createNewKeyPair();
      const newJwtKeys = this.smartjwtInstance.getKeyPairAsJson();
      await kvStore.writeKey('jwtKeypair', newJwtKeys);
    } else {
      this.smartjwtInstance.setKeyPairAsJson(existingJwtKeys);
    }

    this.typedrouter.addTypedHandler(
      new plugins.typedrequest.TypedHandler<plugins.servezoneInterfaces.requests.secret.IReq_Admin_LoginWithUsernameAndPassword>(
        'adminLoginWithUsernameAndPassword',
        async (dataArg) => {
          let jwt: string;
          let expiresAtTimestamp: number = Date.now() + 3600 * 1000 * 24 * 7;
          const user = await User.findUserByUsernameAndPassword(dataArg.username, dataArg.password);
          if (!user) {
            logger.log('warn', 'login failed');
            throw new plugins.typedrequest.TypedResponseError('login failed');
          } else {
            jwt = await this.smartjwtInstance.createJWT({
              userId: user.id,
              status: 'loggedIn',
              expiresAt: expiresAtTimestamp,
            });
            logger.log('success', 'login successful');
          }
          return {
            identity: {
              jwt,
              userId: user.id,
              name: user.data.username,
              expiresAt: expiresAtTimestamp,
              role: user.data.role,
              type: user.data.type,
            },
          };
        },
      ),
    );
  }

  public async stop() {}

  public validIdentityGuard = new plugins.smartguard.Guard<{
    identity: plugins.servezoneInterfaces.data.IIdentity;
  }>(
    async (dataArg) => {
      const jwt = dataArg.identity.jwt;
      const jwtData: IJwtData = await this.smartjwtInstance.verifyJWTAndGetData(jwt);
      const expired = jwtData.expiresAt < Date.now();
      plugins.smartexpect
        .expect(jwtData.status)
        .setFailMessage('user not logged in')
        .toEqual('loggedIn');
      plugins.smartexpect.expect(expired).setFailMessage(`jwt expired`).toBeFalse();
      plugins.smartexpect
        .expect(dataArg.identity.expiresAt)
        .setFailMessage(
          `expiresAt >>identity valid until:${dataArg.identity.expiresAt}, but jwt says: ${jwtData.expiresAt}<< has been tampered with`,
        )
        .toEqual(jwtData.expiresAt);
      plugins.smartexpect
        .expect(dataArg.identity.userId)
        .setFailMessage('userId has been tampered with')
        .toEqual(jwtData.userId);
      if (expired) {
        throw new Error('identity is expired');
      }
      return true;
    },
    {
      failedHint: 'identity is not valid.',
      name: 'validIdentityGuard',
    },
  );

  public adminIdentityGuard = new plugins.smartguard.Guard<{
    identity: plugins.servezoneInterfaces.data.IIdentity;
  }>(
    async (dataArg) => {
      await plugins.smartguard.passGuardsOrReject(dataArg, [this.validIdentityGuard]);
      const jwt = dataArg.identity.jwt;
      const jwtData: IJwtData = await this.smartjwtInstance.verifyJWTAndGetData(jwt);
      const user = await this.CUser.getInstance({ id: jwtData.userId });
      const isAdminBool = user.data.role === 'admin';
      console.log(`user is admin: ${isAdminBool}`);
      return isAdminBool;
    },
    {
      failedHint: 'user is not admin.',
      name: 'adminIdentityGuard',
    },
  );
}