import type { NextFunction, Request, Response } from 'express';
import jwt from 'jsonwebtoken';
import { config } from './config';
import type { UserRole } from './db';
import { pool } from './db';

export interface AuthUser {
  id: string;
  email: string | null;
  username: string | null;
  displayName: string;
  role: UserRole;
  accountType: 'candidate' | 'guardian' | 'representative';
}

export interface AuthenticatedRequest extends Request {
  authUser?: AuthUser;
}

export const createToken = (user: AuthUser, sessionVersion: number): string =>
  jwt.sign({ role: user.role, sessionVersion }, config.jwtSecret, {
    expiresIn: config.jwtExpiresIn as jwt.SignOptions['expiresIn'],
    subject: user.id,
  });

export const requireAuth = (
  request: AuthenticatedRequest,
  response: Response,
  next: NextFunction,
): void => {
  const authorization = request.header('authorization');
  if (!authorization?.startsWith('Bearer ')) {
    response.status(401).json({ error: 'Authentication required' });
    return;
  }

  let claims: jwt.JwtPayload;
  try {
    const verified = jwt.verify(authorization.slice('Bearer '.length), config.jwtSecret);
    if (typeof verified === 'string' || !verified.sub) throw new Error('Invalid token claims');
    claims = verified;
  } catch {
    response.status(401).json({ error: 'Invalid or expired session' });
    return;
  }

  void pool.query<{
    id: string;
    email: string;
    username: string | null;
    display_name: string;
    role: UserRole;
    account_type: 'candidate' | 'guardian' | 'representative';
    is_active: boolean;
    token_version: number;
  }>(
    'SELECT id, email, username, display_name, role, account_type, is_active, token_version FROM users WHERE id = $1',
    [claims.sub],
  ).then((result) => {
    const user = result.rows[0];
    if (!user || !user.is_active || user.token_version !== claims.sessionVersion) {
      response.status(401).json({ error: 'Invalid or expired session' });
      return;
    }
    request.authUser = {
      id: user.id,
      email: user.email,
      username: user.username,
      displayName: user.display_name,
      role: user.role,
      accountType: user.account_type,
    };
    next();
  }).catch(next);
};

export const requireAdmin = (
  request: AuthenticatedRequest,
  response: Response,
  next: NextFunction,
): void => {
  if (request.authUser?.role !== 'admin') {
    response.status(403).json({ error: 'Administrator access required' });
    return;
  }
  next();
};
