import { Request, Response, NextFunction, CookieOptions } from 'express';
import jwt, { type SignOptions } from 'jsonwebtoken';
import { validate as isUuid } from 'uuid';
import pool from '../db';

if (!process.env.JWT_SECRET) {
  throw new Error('JWT_SECRET environment variable is required');
}
const JWT_SECRET = process.env.JWT_SECRET;
const JWT_EXPIRES_IN = (process.env.JWT_EXPIRES_IN || '24h') as SignOptions['expiresIn'];
const AUTH_COOKIE_MAX_AGE_MS = 24 * 60 * 60 * 1000;

export const AUTH_COOKIE_NAME = 'rm_auth';
export const AUTH_STATE_COOKIE_NAME = 'rm_logged_in';

export interface AuthPayload {
  userId: string;
  email: string;
  tokenVersion?: number;
}

declare global {
  namespace Express {
    interface Request {
      user?: AuthPayload;
    }
  }
}

function shouldUseSecureCookies(): boolean {
  return process.env.NODE_ENV === 'production';
}

function getAuthCookieOptions(): CookieOptions {
  return {
    httpOnly: true,
    sameSite: 'lax',
    secure: shouldUseSecureCookies(),
    path: '/',
    maxAge: AUTH_COOKIE_MAX_AGE_MS,
  };
}

function getAuthStateCookieOptions(): CookieOptions {
  return {
    sameSite: 'lax',
    secure: shouldUseSecureCookies(),
    path: '/',
    maxAge: AUTH_COOKIE_MAX_AGE_MS,
  };
}

export function setAuthCookies(res: Response, token: string): void {
  res.cookie(AUTH_COOKIE_NAME, token, getAuthCookieOptions());
  res.cookie(AUTH_STATE_COOKIE_NAME, '1', getAuthStateCookieOptions());
}

export function clearAuthCookies(res: Response): void {
  const authCookieOptions = getAuthCookieOptions();
  const authStateCookieOptions = getAuthStateCookieOptions();
  delete authCookieOptions.maxAge;
  delete authStateCookieOptions.maxAge;
  res.clearCookie(AUTH_COOKIE_NAME, authCookieOptions);
  res.clearCookie(AUTH_STATE_COOKIE_NAME, authStateCookieOptions);
}

function getAuthTokenFromRequest(req: Request): string | null {
  const header = req.headers.authorization;
  if (header?.startsWith('Bearer ')) {
    return header.slice(7);
  }

  const cookieToken = req.cookies?.[AUTH_COOKIE_NAME];
  if (typeof cookieToken === 'string' && cookieToken.length > 0) {
    return cookieToken;
  }

  return null;
}

export function authMiddleware(req: Request, res: Response, next: NextFunction): void {
  const token = getAuthTokenFromRequest(req);
  if (!token) {
    res.status(401).json({ error: 'Authentication required' });
    return;
  }

  verifyToken(token)
    .then((decoded) => {
      if (!decoded) {
        res.status(401).json({ error: 'Invalid or expired token' });
        return;
      }
      req.user = decoded;
      next();
    })
    .catch((err) => {
      console.error('Auth middleware error:', err);
      res.status(500).json({ error: 'Authentication unavailable' });
    });
}

export function optionalAuth(req: Request, _res: Response, next: NextFunction): void {
  const token = getAuthTokenFromRequest(req);
  if (!token) {
    next();
    return;
  }

  verifyToken(token)
    .then((decoded) => {
      if (decoded) req.user = decoded;
      next();
    })
    .catch((err) => {
      console.error('Optional auth error:', err);
      next();
    });
}

export function signToken(payload: AuthPayload): string {
  return jwt.sign({
    ...payload,
    tokenVersion: normalizeTokenVersion(payload.tokenVersion),
  }, JWT_SECRET, { algorithm: 'HS256', expiresIn: JWT_EXPIRES_IN });
}

function normalizeTokenVersion(value: unknown): number {
  const parsed = Number(value);
  return Number.isInteger(parsed) && parsed >= 0 ? parsed : 0;
}

export async function getAuthPayloadFromRequest(req: Request): Promise<AuthPayload | null> {
  const token = getAuthTokenFromRequest(req);
  if (!token) return null;
  return verifyToken(token);
}

async function getCurrentTokenVersion(userId: string): Promise<number | null> {
  const { rows } = await pool.query(
    'SELECT auth_token_version FROM rm_users WHERE id = $1',
    [userId],
  );
  if (!rows[0]) return null;
  return normalizeTokenVersion(rows[0].auth_token_version);
}

async function verifyToken(token: string): Promise<AuthPayload | null> {
  try {
    const decoded = jwt.verify(token, JWT_SECRET, { algorithms: ['HS256'] }) as AuthPayload;
    if (typeof decoded.userId !== 'string' || !isUuid(decoded.userId)) {
      return null;
    }
    if (typeof decoded.email !== 'string' || decoded.email.length === 0) {
      return null;
    }
    const currentTokenVersion = await getCurrentTokenVersion(decoded.userId);
    if (currentTokenVersion === null) return null;

    const tokenVersion = normalizeTokenVersion(decoded.tokenVersion);
    if (tokenVersion !== currentTokenVersion) return null;

    return {
      userId: decoded.userId,
      email: decoded.email,
      tokenVersion,
    };
  } catch {
    return null;
  }
}
