// auth.js - Keycloak authentication handler
import axios from "axios";
import jwt from "jsonwebtoken";
import log4js from "log4js";
// Load configuration
import { loadConfig } from "../config.js";

const config = loadConfig();

const logger = log4js.getLogger("Auth");
logger.level = config.logLevel;

export class KeycloakAuth {
  constructor(config) {
    this.config = config.auth;
    this.tokenCache = new Map(); // Cache for verified tokens
  }

  // Verify a token with Keycloak server
  async verifyToken(token) {
    // Check cache first
    if (this.tokenCache.has(token)) {
      const cachedInfo = this.tokenCache.get(token);
      if (cachedInfo.expiresAt > Date.now()) {
        return { valid: true, user: cachedInfo.user };
      } else {
        // Token expired, remove from cache
        this.tokenCache.delete(token);
      }
    }

    try {
      // Verify token with Keycloak introspection endpoint
      const response = await axios.post(
        `${this.config.keycloak.url}/realms/${this.config.keycloak.realm}/protocol/openid-connect/token/introspect`,
        new URLSearchParams({
          token: token,
          client_id: this.config.keycloak.clientId,
          client_secret: this.config.keycloak.clientSecret,
        }),
        {
          headers: {
            "Content-Type": "application/x-www-form-urlencoded",
          },
        }
      );

      const introspection = response.data;

      if (!introspection.active) {
        logger.info("Token is not active");
        return { valid: false };
      }

      // Verify required roles if configured
      if (this.config.requiredRoles && this.config.requiredRoles.length > 0) {
        const hasRequiredRole = this.checkRoles(
          introspection,
          this.config.requiredRoles
        );
        if (!hasRequiredRole) {
          logger.info("User doesn't have required roles");
          return { valid: false };
        }
      }

      // Parse token to extract user info
      const decodedToken = jwt.decode(token);
      const user = {
        id: decodedToken.sub,
        username: decodedToken.preferred_username,
        email: decodedToken.email,
        name: decodedToken.name,
        roles: this.extractRoles(decodedToken),
      };

      // Cache the verified token
      const expiresAt = introspection.exp * 1000; // Convert to milliseconds
      this.tokenCache.set(token, { expiresAt, user });

      return { valid: true, user };
    } catch (error) {
      logger.error("Token verification error:", error.message);
      return { valid: false };
    }
  }

  // Extract roles from token
  extractRoles(token) {
    const roles = [];

    // Extract realm roles
    if (token.realm_access && token.realm_access.roles) {
      roles.push(...token.realm_access.roles);
    }

    // Extract client roles
    if (token.resource_access) {
      for (const client in token.resource_access) {
        if (token.resource_access[client].roles) {
          roles.push(
            ...token.resource_access[client].roles.map(
              (role) => `${client}:${role}`
            )
          );
        }
      }
    }

    return roles;
  }

  // Check if user has required roles
  checkRoles(tokenInfo, requiredRoles) {
    // Extract roles from token
    const userRoles = this.extractRoles(tokenInfo);

    // Check if user has any of the required roles
    return requiredRoles.some((role) => userRoles.includes(role));
  }
}

// Socket.IO middleware for authentication
export function createAuthMiddleware(auth) {
  return async (socket, next) => {
    const token = socket.handshake.auth.token;

    if (!token) {
      return next(new Error("Authentication token is required"));
    }

    try {
      const authResult = await auth.verifyToken(token);

      if (!authResult.valid) {
        return next(new Error("Invalid authentication token"));
      }

      // Attach user information to socket
      socket.user = authResult.user;
      next();
    } catch (err) {
      logger.error("Authentication error:", err);
      next(new Error("Authentication failed"));
    }
  };
}
