const { User, Role, Permission, RolePermission, UserRole, PermissionAudit } = require('../models');
const { Op } = require('sequelize');

// Cache for user permissions (in-memory cache, can be replaced with Redis)
const permissionCache = new Map();
const CACHE_TTL = 5 * 60 * 1000; // 5 minutes

/**
 * Get all permissions for a user
 * @param {number} userId - User ID
 * @param {number} companyId - Company ID
 * @returns {Promise<Array>} Array of permission codes
 */
async function getUserPermissions(userId, companyId) {
  const cacheKey = `user_${userId}_company_${companyId}`;
  const cached = permissionCache.get(cacheKey);

  if (cached && (Date.now() - cached.timestamp) < CACHE_TTL) {
    return cached.permissions;
  }

  try {
    const user = await User.findOne({
      where: { id: userId, company_id: companyId },
      include: [
        {
          model: Role,
          as: 'roles',
          through: {
            model: UserRole,
            as: 'userRoles',
            where: {
              status: 'ACTIVE',
              [Op.or]: [
                { effective_from: null },
                { effective_from: { [Op.lte]: new Date() } }
              ],
              [Op.or]: [
                { effective_to: null },
                { effective_to: { [Op.gte]: new Date() } }
              ]
            }
          },
          where: {
            status: 'ACTIVE',
            [Op.or]: [
              { company_id: null },
              { company_id: companyId }
            ]
          },
          include: [
            {
              model: Permission,
              as: 'permissions',
              through: {
                model: RolePermission,
                as: 'rolePermissions',
                where: { status: 'ACTIVE' }
              },
              where: { status: 'ACTIVE' }
            }
          ]
        }
      ]
    });

    if (!user) {
      return [];
    }

    // Extract unique permission codes
    const permissionSet = new Set();
    user.roles.forEach(role => {
      role.permissions.forEach(permission => {
        permissionSet.add(permission.permission_code);
      });
    });

    const permissions = Array.from(permissionSet);

    // Cache the result
    permissionCache.set(cacheKey, {
      permissions,
      timestamp: Date.now()
    });

    return permissions;
  } catch (error) {
    console.error('Error getting user permissions:', error);
    return [];
  }
}

/**
 * Check if user has a specific permission
 * @param {number} userId - User ID
 * @param {number} companyId - Company ID
 * @param {string} permissionCode - Permission code to check
 * @returns {Promise<boolean>} True if user has permission
 */
async function hasPermission(userId, companyId, permissionCode) {
  // Super admin has all permissions
  const user = await User.findByPk(userId);
  if (user && user.is_super_admin) {
    return true;
  }

  const permissions = await getUserPermissions(userId, companyId);
  return permissions.includes(permissionCode);
}

/**
 * Abbreviation mapping for dot-notation permissions to underscore-notation.
 * Sync this with frontend/src/components/navigation/MenuConfig.js
 */
const abbreviationMap = {
  'PRODUCTION.PO': 'PRODUCTION_PRODUCTION_ORDER',
  'PRODUCTION.MI': 'PRODUCTION_MATERIAL_ISSUE',
  'PRODUCTION.PC': 'PRODUCTION_PRODUCTION_CONFIRMATION',
  'PRODUCTION.SR': 'PRODUCTION_SCRAP_REPORT',
  'PRODUCTION.WIP': 'PRODUCTION_WIP',
  'PRODUCTION.MRP': 'PRODUCTION_MRP',
  'STORES.MR': 'STORES_MATERIAL_REQUEST',
  'STORES.MI': 'STORES_MATERIAL_ISSUE',
  'STORES.PR': 'STORES_PURCHASE_REQUISITION',
  'PURCHASE.PR': 'PURCHASE_PURCHASE_REQUISITION',
  'PURCHASE.RFQ': 'PURCHASE_REQUEST_FOR_QUOTATION',
  'PURCHASE.PO': 'PURCHASE_PURCHASE_ORDER',
  'PURCHASE.GRN': 'PURCHASE_GRN',
  'PURCHASE.VI': 'PURCHASE_VENDOR_INVOICE',
  'INVENTORY.GR': 'INVENTORY_GOODS_RECEIPT',
  'INVENTORY.GI': 'INVENTORY_GOODS_ISSUE',
  'INVENTORY.ST': 'INVENTORY_STOCK_TRANSFER',
  'INVENTORY.SA': 'INVENTORY_STOCK_ADJUSTMENT',
  'INVENTORY.STOCK_REGISTER': 'INVENTORY_STOCK_REGISTER',
  'INVENTORY.TRANSACTION_HISTORY': 'INVENTORY_TRANSACTION_HISTORY',
  'INVENTORY.BATCH_SERIAL': 'INVENTORY_BATCH_SERIAL',
  'CUSTOMER.PO': 'CUSTOMER_CUSTOMER_PO',
  'CUSTOMER.QUOTATION': 'CUSTOMER_CUSTOMER_QUOTATION',
  'SALES.QUOTATION': 'SALES_SALES_QUOTATION',
  'SALES.ORDER': 'SALES_SALES_ORDER',
  'SALES.INVOICE': 'SALES_SALES_INVOICE',
};

/**
 * Check if a user's stored permission code matches the required pattern.
 * Handles wildcard patterns (.*) and cross-format matching between
 * dot-format (STORES.MATERIAL_REQUEST.*) and underscore-format (STORES_MATERIAL_REQUEST_READ).
 */
function matchesPermission(stored, required) {
  // Normalize both to uppercase for comparison
  const s = stored.toUpperCase();
  const r = required.toUpperCase();

  // Exact match (same format)
  if (s === r) return true;

  if (r.endsWith('.*')) {
    // Strip the '.*' wildcard suffix to get the prefix pattern
    const basePattern = r.slice(0, -2); // Remove '.*'

    // Check abbreviation map first (e.g. INVENTORY.GR -> INVENTORY_GOODS_RECEIPT)
    let mappedPattern = abbreviationMap[basePattern];
    if (mappedPattern) {
      // Check both underscore and dot versions of the mapped pattern
      const dotPrefix = mappedPattern.replace(/_/g, '.');
      if (s === mappedPattern || s.startsWith(mappedPattern + '_')) return true;
      if (s === dotPrefix || s.startsWith(dotPrefix + '.')) return true;
    }

    // Standard prefix checking (dot and underscore variations)
    const dotPrefix = basePattern;
    const underPrefix = basePattern.replace(/\./g, '_');

    // stored in DOT format: STORES.MATERIAL_REQUEST.READ
    if (s === dotPrefix || s.startsWith(dotPrefix + '.')) return true;
    // stored in UNDERSCORE format: STORES_MATERIAL_REQUEST_READ
    if (s === underPrefix || s.startsWith(underPrefix + '_')) return true;
  }

  return false;
}

/**
 * Check if user has any of the specified permissions
 * @param {number} userId - User ID
 * @param {number} companyId - Company ID
 * @param {Array<string>} permissionCodes - Array of permission codes (supports wildcards)
 * @returns {Promise<boolean>} True if user has at least one permission
 */
async function hasAnyPermission(userId, companyId, permissionCodes) {
  if (!permissionCodes || permissionCodes.length === 0) {
    return true; // No permission required
  }

  const user = await User.findByPk(userId);
  if (user && user.is_super_admin) {
    return true;
  }

  const permissions = await getUserPermissions(userId, companyId);
  return permissionCodes.some(required =>
    permissions.some(stored => matchesPermission(stored, required))
  );
}

/**
 * Check if user has all of the specified permissions
 * @param {number} userId - User ID
 * @param {number} companyId - Company ID
 * @param {Array<string>} permissionCodes - Array of permission codes
 * @returns {Promise<boolean>} True if user has all permissions
 */
async function hasAllPermissions(userId, companyId, permissionCodes) {
  if (!permissionCodes || permissionCodes.length === 0) {
    return true; // No permission required
  }

  const user = await User.findByPk(userId);
  if (user && user.is_super_admin) {
    return true;
  }

  const permissions = await getUserPermissions(userId, companyId);
  return permissionCodes.every(required =>
    permissions.some(stored => matchesPermission(stored, required))
  );
}

/**
 * Log permission check to audit trail
 * @param {number} userId - User ID
 * @param {string} permissionCode - Permission code
 * @param {string} action - Action: GRANTED, DENIED, REVOKED
 * @param {Object} options - Additional options (resourceType, resourceId, ipAddress, userAgent)
 */
async function logPermissionCheck(userId, permissionCode, action, options = {}) {
  try {
    await PermissionAudit.create({
      user_id: userId,
      permission_code: permissionCode,
      action: action,
      resource_type: options.resourceType || null,
      resource_id: options.resourceId || null,
      ip_address: options.ipAddress || null,
      user_agent: options.userAgent || null
    });
  } catch (error) {
    console.error('Error logging permission check:', error);
    // Don't throw - audit logging should not break the flow
  }
}

/**
 * Clear permission cache for a user
 * @param {number} userId - User ID
 * @param {number} companyId - Company ID
 */
function clearPermissionCache(userId, companyId) {
  const cacheKey = `user_${userId}_company_${companyId}`;
  permissionCache.delete(cacheKey);
}

/**
 * Clear all permission cache
 */
function clearAllPermissionCache() {
  permissionCache.clear();
}

module.exports = {
  getUserPermissions,
  hasPermission,
  hasAnyPermission,
  hasAllPermissions,
  logPermissionCheck,
  clearPermissionCache,
  clearAllPermissionCache
};

