import Keycloak from 'keycloak-connect';
import session from 'express-session';
import dotenv from 'dotenv';
import axios from 'axios';
import jwt from 'jsonwebtoken';
import log4js from 'log4js';
import NodeCache from 'node-cache';
import { userModel } from './database/schemas/management/user.schema.js';
import { getObject } from './database/database.js';
import { hostModel } from './database/schemas/management/host.schema.js';

dotenv.config();
const logger = log4js.getLogger('Keycloak');
logger.level = process.env.LOG_LEVEL || 'info';

// Initialize NodeCache with 5-minute TTL
const userCache = new NodeCache({ stdTTL: 300 }); // 300 seconds = 5 minutes

// Cache event listeners for monitoring
userCache.on('expired', (key, value) => {
  logger.debug(`Cache entry expired: ${key}`);
});

userCache.on('flush', () => {
  logger.info('Cache flushed');
});

// User lookup function with caching
const lookupUser = async (preferredUsername) => {
  try {
    // Check cache first
    const cachedUser = userCache.get(preferredUsername);
    if (cachedUser) {
      logger.debug(`User found in cache: ${preferredUsername}`);
      return cachedUser;
    }

    // If not in cache, query database
    logger.debug(`User not in cache, querying database: ${preferredUsername}`);
    const user = await userModel.findOne({ username: preferredUsername });

    if (user) {
      // Store in cache
      userCache.set(preferredUsername, user);
      logger.debug(`User stored in cache: ${preferredUsername}`);
      return user;
    }

    logger.warn(`User not found in database: ${preferredUsername}`);
    return null;
  } catch (error) {
    logger.error(`Error looking up user ${preferredUsername}:`, error.message);
    return null;
  }
};

// Initialize Keycloak
const keycloakConfig = {
  realm: process.env.KEYCLOAK_REALM || 'farm-control',
  'auth-server-url': process.env.KEYCLOAK_URL || 'http://localhost:8080/auth',
  'ssl-required': process.env.NODE_ENV === 'production' ? 'external' : 'none',
  resource: process.env.KEYCLOAK_CLIENT_ID || 'farmcontrol-client',
  'confidential-port': 0,
  'bearer-only': true,
  'public-client': false,
  'use-resource-role-mappings': true,
  'verify-token-audience': true,
  credentials: {
    secret: process.env.KEYCLOAK_CLIENT_SECRET,
  },
};

const memoryStore = new session.MemoryStore();

var expressSession = session({
  secret: process.env.SESSION_SECRET || 'n00Dl3s23!',
  resave: false,
  saveUninitialized: true, // Set this to true to ensure session is initialized
  store: memoryStore,
  cookie: {
    maxAge: 1800000, // 30 minutes
  },
});

var keycloak = new Keycloak({ store: memoryStore }, keycloakConfig);

// Custom middleware to check if the user is authenticated
const isAuthenticated = async (req, res, next) => {
  let token = null;

  const authHeader = req.headers.authorization || req.headers.Authorization;
  if (authHeader && authHeader.startsWith('Bearer ')) {
    token = authHeader.substring(7);

    try {
      // Verify token with Keycloak introspection endpoint
      const response = await axios.post(
        `${process.env.KEYCLOAK_URL}/realms/${process.env.KEYCLOAK_REALM}/protocol/openid-connect/token/introspect`,
        new URLSearchParams({
          token: token,
          client_id: process.env.KEYCLOAK_CLIENT_ID,
          client_secret: process.env.KEYCLOAK_CLIENT_SECRET,
        }),
        {
          headers: {
            'Content-Type': 'application/x-www-form-urlencoded',
          },
        }
      );

      const introspection = response.data;
      if (!introspection.active) {
        logger.info('Token is not active');
        logger.debug('Token:', token);
        return res.status(401).json({ error: 'Session Inactive', code: 'UNAUTHORIZED' });
      }

      return next();
    } catch (error) {
      logger.error('Token verification error:', error.message);
      return res.status(401).json({ error: 'Verification Error', code: 'UNAUTHORIZED' });
    }
  }

  console.log('Checking host authentication', req.headers);

  const hostId = req.headers['x-host-id'];
  const authCode = req.headers['x-auth-code'];
  if (hostId && authCode) {
    const host = await getObject({ model: hostModel, id: hostId });
    if (host && host.authCode == authCode) {
      return next();
    }
  } else {
    return res.status(401).json({ error: 'Not Authenticated', code: 'UNAUTHORIZED' });
  }

  // Fallback to session-based authentication
  console.log('Using session token');
  if (req.session && req.session['keycloak-token']) {
    const sessionToken = req.session['keycloak-token'];
    if (sessionToken.expires_at > new Date().getTime()) {
      return next();
    }
  }

  return res.status(401).json({ error: 'Not Authenticated', code: 'UNAUTHORIZED' });
};

// Helper function to extract roles from token
function 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;
}

// Cache management utility functions
const clearUserCache = () => {
  userCache.flushAll();
  logger.info('User cache cleared');
};

const getUserCacheStats = () => {
  return userCache.getStats();
};

const removeUserFromCache = (username) => {
  userCache.del(username);
  logger.debug(`User removed from cache: ${username}`);
};

export {
  keycloak,
  expressSession,
  isAuthenticated,
  lookupUser,
  clearUserCache,
  getUserCacheStats,
  removeUserFromCache,
};
