import type { MiddlewareHandler } from 'hono';
import { verify } from '@@/jwt';
import * as errors from '@@/custom-errors';
import { isOriginAllowed } from './origin-validation';
import { isLockdown, noteBlocked } from '../api/auth/panic';
import { getUserById, isTokenBlacklisted } from 'officerdb';

export const userMiddleware: MiddlewareHandler = async function (ctx, next) {
  // Duress lockdown: reject every authenticated request, cutting off all existing sessions.
  if (isLockdown()) {
    noteBlocked(`${ctx.req.method} ${ctx.req.path}`);
    throw errors.UNAUTHORIZED();
  }

  const { authorization } = ctx.req.header();

  // Support token in query param for media elements (<audio>, <video>, <img>)
  // that cannot send Authorization headers
  let token: string | undefined;
  if (authorization) {
    [, token] = authorization.split(' ');
  } else {
    token = ctx.req.query('token') || undefined;
  }
  if (!token) throw errors.UNAUTHORIZED();

  // Validate origin for authenticated routes
  const origin = ctx.get('origin') as string | undefined;
  const host = ctx.req.header('host');
  if (!isOriginAllowed(origin, host)) {
    throw errors.FORBIDDEN('Invalid origin');
  }

  try {
    const user = await verify(token);
    if (!user) throw errors.UNAUTHORIZED();

    // Check if token is blacklisted (explicit signout)
    if (user.jti) {
      if (await isTokenBlacklisted(user.jti)) throw errors.UNAUTHORIZED();
    }

    // Check if token was issued before password change
    if (user.iat && user.id) {
      const dbUser = await getUserById(user.id);
      if (dbUser?.passwordChangedAt) {
        // iat is in seconds, passwordChangedAt is a Date
        const tokenIssuedAt = user.iat * 1000;
        if (tokenIssuedAt < dbUser.passwordChangedAt.getTime()) {
          throw errors.UNAUTHORIZED();
        }
      }
    }

    ctx.set('user', user);
    return next();
  } catch (ex) {
    if (ex instanceof errors.CustomError) throw ex;
    throw errors.UNAUTHORIZED();
  }
};
