import * as plugins from '../plugins.js';
import { LoginSession } from './classes.loginsession.js';
import { Reception } from './classes.reception.js';
import { logger } from './logging.js';

export class LoginSessionManager {
  // refs
  public receptionRef: Reception;
  public get db() {
    return this.receptionRef.db.smartdataDb;
  }

  public CLoginSession = plugins.smartdata.setDefaultManagerForDoc(this, LoginSession);

  public loginSessions = new plugins.lik.ObjectMap<LoginSession>();

  public typedRouter = new plugins.typedrequest.TypedRouter();

  public emailTokenMap = new plugins.lik.ObjectMap<{
    email: string;
    token: string;
    action: 'emailLogin' | 'passwordReset';
  }>();

  constructor(receptionRefArg: Reception) {
    this.receptionRef = receptionRefArg;
    this.receptionRef.typedrouter.addTypedRouter(this.typedRouter);
    this.typedRouter.addTypedHandler(
      new plugins.typedrequest.TypedHandler<plugins.idpInterfaces.request.IReq_LoginWithEmailOrUsernameAndPassword>(
        'loginWithEmailOrUsernameAndPassword',
        async (requestData) => {
          let user = await this.receptionRef.userManager.CUser.getInstance({
            data: {
              username: requestData.username,
              passwordHash: await this.receptionRef.userManager.CUser.hashPassword(
                requestData.password
              ),
            },
          });

          if (!user && requestData.username.includes('@')) {
            user = await this.receptionRef.userManager.CUser.getInstance({
              data: {
                email: requestData.username,
                passwordHash: await this.receptionRef.userManager.CUser.hashPassword(
                  requestData.password
                ),
              },
            });
          }

          if (user) {
            // lets recheck
            if (
              (user.data.username !== requestData.username &&
                user.data.email !== requestData.username) ||
              user.data.passwordHash !==
                (await this.receptionRef.userManager.CUser.hashPassword(requestData.password))
            ) {
              throw new Error(
                'database returned a user that does not match wanted criterea. CRITICAL!'
              );
            }

            const loginSession = await LoginSession.createLoginSessionForUser(user);
            this.loginSessions.add(loginSession);
            const refreshToken = await loginSession.getRefreshToken();

            return {
              status: 'ok',
              refreshToken: refreshToken,
              twoFaNeeded: false,
            };
          } else {
            throw new plugins.typedrequest.TypedResponseError('User not found!');
          }
        }
      )
    );

    this.typedRouter.addTypedHandler(
      new plugins.typedrequest.TypedHandler<plugins.idpInterfaces.request.IReq_LoginWithEmail>(
        'loginWithEmail',
        async (requestDataArg) => {
          logger.log('info', `loginWithEmail requested for: ${requestDataArg.email}`);
          const existingUser = await this.receptionRef.userManager.CUser.getInstance({
            data: {
              email: requestDataArg.email,
            },
          });
          if (existingUser) {
            logger.log('info', `loginWithEmail found user: ${existingUser.data.email}`);
            this.emailTokenMap.findOneAndRemoveSync(
              (itemArg) => itemArg.email === existingUser.data.email
            );
            const loginEmailToken = plugins.smartunique.uuid4();
            this.emailTokenMap.add({
              email: existingUser.data.email,
              token: loginEmailToken,
              action: 'emailLogin',
            });
            // lets make sure its only valid for 10 minutes
            plugins.smartdelay.delayFor(600000, null, true).then(() => {
              this.emailTokenMap.findOneAndRemoveSync(
                (itemArg) => itemArg.token === loginEmailToken
              );
            });
            this.receptionRef.receptionMailer.sendLoginWithEMailMail(existingUser, loginEmailToken);
          } else {
            logger.log('info', `loginWithEmail did not find user: ${requestDataArg.email}`);
          }
          return {
            status: 'ok',
            testOnlyToken: process.env.TEST_MODE
              ? this.emailTokenMap.findSync((itemArg) => itemArg.email === existingUser.data.email)
                  .token
              : null,
          };
        }
      )
    );

    this.typedRouter.addTypedHandler(
      new plugins.typedrequest.TypedHandler<plugins.idpInterfaces.request.IReq_LoginWithEmailAfterEmailTokenAquired>(
        'loginWithEmailAfterEmailTokenAquired',
        async (requestArg) => {
          const tokenObject = this.emailTokenMap.findSync((itemArg) => {
            return itemArg.email === requestArg.email && itemArg.token === requestArg.token;
          });
          if (tokenObject) {
            const user = await this.receptionRef.userManager.CUser.getInstance({
              data: {
                email: requestArg.email,
              },
            });
            const loginSession = await LoginSession.createLoginSessionForUser(user);
            this.loginSessions.add(loginSession);
            return {
              refreshToken: await loginSession.getRefreshToken(),
            };
          } else {
            throw new plugins.typedrequest.TypedResponseError('Validation Token not found');
          }
        }
      )
    );

    this.typedRouter.addTypedHandler<plugins.idpInterfaces.request.ILogoutRequest>(
      new plugins.typedrequest.TypedHandler('logout', async (requestDataArg) => {
        const loginSession = await this.CLoginSession.getLoginSessionByRefreshToken(requestDataArg.refreshToken);
        await loginSession.invalidate();
        return {}
      })
    );

    this.typedRouter.addTypedHandler<plugins.idpInterfaces.request.IReq_ExchangeRefreshTokenAndTransferToken>(
      new plugins.typedrequest.TypedHandler(
        'exchangeRefreshTokenAndTransferToken',
        async (requestDataArg) => {
          switch (true) {
            case !!requestDataArg.refreshToken:
              const loginSession = await this.loginSessions.find(async (loginSessionArg) => {
                return loginSessionArg.validateRefreshToken(requestDataArg.refreshToken);
              });
              if (!loginSession) {
                throw new plugins.typedrequest.TypedResponseError('your refresh token is invalid');
              }
              return {
                transferToken: await loginSession.getTransferToken(),
              };
              break;
            case !!requestDataArg.transferToken:
              let transferToken: string;
              const loginSession2 = await this.loginSessions.find(async (loginSessionArg) => {
                return loginSessionArg.validateTransferToken(requestDataArg.transferToken);
              });
              if (!loginSession2) {
                throw new plugins.typedrequest.TypedResponseError(
                  'Your transfer token is not valid.'
                );
              }
              return {
                refreshToken: await loginSession2.getRefreshToken(),
              };
              break;
          }
        }
      )
    );

    this.typedRouter.addTypedHandler(
      new plugins.typedrequest.TypedHandler<plugins.idpInterfaces.request.IReq_ResetPassword>(
        'resetPassword',
        async (requestDataArg) => {
          const emailOfPasswordToReset = requestDataArg.email;
          const existingUser = await this.receptionRef.userManager.CUser.getInstance({
            data: {
              email: emailOfPasswordToReset,
            },
          });
          if (existingUser) {
            this.emailTokenMap.findOneAndRemoveSync(
              (itemArg) => itemArg.email === existingUser.data.email
            );
            this.emailTokenMap.add({
              email: existingUser.data.email,
              token: plugins.smartunique.shortId(),
              action: 'passwordReset',
            });
            plugins.smartdelay.delayFor(600000, null, true).then(() => {
              this.emailTokenMap.findOneAndRemoveSync(
                (itemArg) => itemArg.email === existingUser.data.email
              );
            });
            this.receptionRef.receptionMailer.sendPasswordResetMail(
              existingUser,
              this.emailTokenMap.findSync((itemArg) => itemArg.email === existingUser.data.email)
                .token
            );
          }
          // note: we always return ok here, since we don't want to give any indication as to wether a user is already registered with us.
          return {
            status: 'ok',
          };
        }
      )
    );

    this.typedRouter.addTypedHandler(
      new plugins.typedrequest.TypedHandler<plugins.idpInterfaces.request.IReq_SetNewPassword>(
        'setNewPassword',
        async (requestData) => {
          return {
            status: 'ok',
          };
        }
      )
    );

    /**
     * returns a device id by simply returning a uuid4
     */
    this.typedRouter.addTypedHandler(
      new plugins.typedrequest.TypedHandler<plugins.idpInterfaces.request.IReq_ObtainDeviceId>('obtainDeviceId', async (reqData) => {
        reqData;
        return {
          deviceId: {
            id: plugins.smartunique.uuid4()
          }
        }
      })
    )

    this.typedRouter.addTypedHandler(
      new plugins.typedrequest.TypedHandler<plugins.idpInterfaces.request.IReq_AttachDeviceId>('attachDeviceId', async (reqData) => {
        // TODO: Blocked by proper JWT handling
        reqData.jwt;
        return {
          ok: false
        }
      })
    )
  }
}
