import { Request, Response, NextFunction } from 'express';
import jwt from 'jsonwebtoken';
import { authService, type ResolvedAccess } from '../services/auth';
import { looksLikeApiKey, resolveApiKey } from '../services/apiKeys';

const JWT_SECRET = process.env.JWT_SECRET || 'change-me-in-production';

export interface AuthPayload {
  pubkey: string;
  role: string;
}

declare global {
  namespace Express {
    interface Request {
      user?: AuthPayload;
      access?: ResolvedAccess;
      isApiKey?: boolean;
    }
  }
}

export async function requireAuth(req: Request, res: Response, next: NextFunction): Promise<void> {
  const header = req.headers.authorization;
  if (!header || !header.startsWith('Bearer ')) {
    res.status(401).json({ error: 'Missing or invalid authorization header' });
    return;
  }

  const token = header.slice(7);

  // API keys authenticate programmatic clients. They carry their own scoped
  // permission set rather than a role, so we pre-resolve access here.
  if (looksLikeApiKey(token)) {
    const access = await resolveApiKey(token);
    if (!access) {
      res.status(401).json({ error: 'Invalid or revoked API key' });
      return;
    }
    req.user = { pubkey: access.pubkey, role: 'apikey' };
    req.access = access;
    req.isApiKey = true;
    next();
    return;
  }

  try {
    const payload = jwt.verify(token, JWT_SECRET) as AuthPayload;
    req.user = payload;
    next();
  } catch {
    res.status(401).json({ error: 'Invalid or expired token' });
  }
}

// Resolves the requester's effective access (env SuperAdmin list plus database)
// and gates the request on a single permission key. SuperAdmin bypasses every
// check. The resolved access is attached to req.access for downstream handlers.
export function requires(permission: string) {
  return async (req: Request, res: Response, next: NextFunction): Promise<void> => {
    if (!req.user) {
      res.status(401).json({ error: 'Not authenticated' });
      return;
    }
    try {
      // API key access is already resolved (and scoped) in requireAuth; for JWT
      // callers, resolve their live role-based access here.
      let access = req.access;
      if (!req.isApiKey || !access) {
        access = await authService.resolveAccess(req.user.pubkey);
        req.access = access;
      }
      if (access.isSuperAdmin || access.permissions.has(permission)) {
        next();
        return;
      }
      res.status(403).json({ error: 'Insufficient permissions' });
    } catch (err) {
      console.error('Permission check error:', err);
      res.status(500).json({ error: 'Internal server error' });
    }
  };
}
