/**
 * Field Permissions Middleware
 * Filtrado de campos a nivel de lectura/escritura por usuario/modelo
 * Compatible con erp-core fieldPermissions middleware
 */

import { Request, Response, NextFunction } from 'express';
import { AppDataSource } from '../database/typeorm.config';
import { logger } from '../utils/logger';

// ============================================================================
// TYPES
// ============================================================================

export interface FieldPermission {
  field_name: string;
  can_read: boolean;
  can_write: boolean;
}

export interface ModelFieldPermissions {
  model_name: string;
  fields: Map<string, FieldPermission>;
}

// Cache for field permissions per user/model
const permissionsCache = new Map<string, { permissions: ModelFieldPermissions; expires: number }>();
const CACHE_TTL = 5 * 60 * 1000; // 5 minutes

// ============================================================================
// HELPER FUNCTIONS
// ============================================================================

function getCacheKey(userId: string, tenantId: string, modelName: string): string {
  return `${tenantId}:${userId}:${modelName}`;
}

async function loadFieldPermissions(
  userId: string,
  tenantId: string,
  modelName: string
): Promise<ModelFieldPermissions | null> {
  const cacheKey = getCacheKey(userId, tenantId, modelName);
  const cached = permissionsCache.get(cacheKey);

  if (cached && cached.expires > Date.now()) {
    return cached.permissions;
  }

  try {
    const result = await AppDataSource.query(
      `SELECT
         mf.name as field_name,
         COALESCE(fp.can_read, true) as can_read,
         COALESCE(fp.can_write, true) as can_write
       FROM auth.model_fields mf
       JOIN auth.models m ON mf.model_id = m.id
       LEFT JOIN auth.field_permissions fp ON mf.id = fp.field_id
       LEFT JOIN auth.user_groups ug ON fp.group_id = ug.group_id
       WHERE m.model = $1
         AND m.tenant_id = $2
         AND (ug.user_id = $3 OR fp.group_id IS NULL)
       GROUP BY mf.name, fp.can_read, fp.can_write`,
      [modelName, tenantId, userId]
    );

    if (!result || result.length === 0) {
      return null;
    }

    const permissions: ModelFieldPermissions = {
      model_name: modelName,
      fields: new Map(),
    };

    for (const row of result) {
      permissions.fields.set(row.field_name, {
        field_name: row.field_name,
        can_read: row.can_read,
        can_write: row.can_write,
      });
    }

    permissionsCache.set(cacheKey, {
      permissions,
      expires: Date.now() + CACHE_TTL,
    });

    return permissions;
  } catch {
    // Tables might not exist yet - return null (allow all)
    return null;
  }
}

function filterReadFields<T extends Record<string, any>>(
  data: T,
  permissions: ModelFieldPermissions | null
): Partial<T> {
  if (!permissions || permissions.fields.size === 0) {
    return data;
  }

  const filtered: Record<string, any> = {};
  for (const [key, value] of Object.entries(data)) {
    const fieldPerm = permissions.fields.get(key);
    if (!fieldPerm || fieldPerm.can_read) {
      filtered[key] = value;
    }
  }
  return filtered as Partial<T>;
}

function filterReadFieldsArray<T extends Record<string, any>>(
  data: T[],
  permissions: ModelFieldPermissions | null
): Partial<T>[] {
  return data.map(item => filterReadFields(item, permissions));
}

function validateWriteFields<T extends Record<string, any>>(
  data: T,
  permissions: ModelFieldPermissions | null
): { valid: boolean; forbiddenFields: string[] } {
  if (!permissions || permissions.fields.size === 0) {
    return { valid: true, forbiddenFields: [] };
  }

  const forbiddenFields: string[] = [];
  for (const key of Object.keys(data)) {
    const fieldPerm = permissions.fields.get(key);
    if (fieldPerm && !fieldPerm.can_write) {
      forbiddenFields.push(key);
    }
  }

  return {
    valid: forbiddenFields.length === 0,
    forbiddenFields,
  };
}

// ============================================================================
// MIDDLEWARE FACTORIES
// ============================================================================

export function filterResponseFields(modelName: string) {
  return async (req: Request, res: Response, next: NextFunction): Promise<void> => {
    const originalJson = res.json.bind(res);

    res.json = function(body: any) {
      (async () => {
        try {
          if (!req.user) {
            return originalJson(body);
          }

          const permissions = await loadFieldPermissions(
            req.user.sub,
            req.user.tenantId,
            modelName
          );

          if (!permissions || req.user.roles.includes('super_admin')) {
            return originalJson(body);
          }

          if (body && typeof body === 'object') {
            if (body.data) {
              if (Array.isArray(body.data)) {
                body.data = filterReadFieldsArray(body.data, permissions);
              } else if (typeof body.data === 'object') {
                body.data = filterReadFields(body.data, permissions);
              }
            } else if (Array.isArray(body)) {
              body = filterReadFieldsArray(body, permissions);
            }
          }

          return originalJson(body);
        } catch (error) {
          logger.error('Error filtering response fields', { error, modelName });
          return originalJson(body);
        }
      })();
    } as typeof res.json;

    next();
  };
}

export function validateWritePermissions(modelName: string) {
  return async (req: Request, res: Response, next: NextFunction): Promise<void> => {
    try {
      if (!req.user) {
        return next();
      }

      if (req.user.roles.includes('super_admin')) {
        return next();
      }

      const permissions = await loadFieldPermissions(
        req.user.sub,
        req.user.tenantId,
        modelName
      );

      if (!permissions) {
        return next();
      }

      if (req.body && typeof req.body === 'object') {
        const { valid, forbiddenFields } = validateWriteFields(req.body, permissions);

        if (!valid) {
          logger.warn('Write permission denied for fields', {
            userId: req.user.sub,
            modelName,
            forbiddenFields,
          });

          res.status(403).json({
            success: false,
            error: `No tiene permisos para modificar los campos: ${forbiddenFields.join(', ')}`,
            forbiddenFields,
          });
          return;
        }
      }

      next();
    } catch (error) {
      logger.error('Error validating write permissions', { error, modelName });
      next(error);
    }
  };
}

export function fieldPermissions(modelName: string) {
  const readFilter = filterResponseFields(modelName);
  const writeValidator = validateWritePermissions(modelName);

  return async (req: Request, res: Response, next: NextFunction): Promise<void> => {
    if (['POST', 'PUT', 'PATCH'].includes(req.method)) {
      await writeValidator(req, res, () => {
        readFilter(req, res, next);
      });
    } else {
      await readFilter(req, res, next);
    }
  };
}

export function clearPermissionsCache(userId?: string, tenantId?: string): void {
  if (userId && tenantId) {
    const prefix = `${tenantId}:${userId}:`;
    for (const key of permissionsCache.keys()) {
      if (key.startsWith(prefix)) {
        permissionsCache.delete(key);
      }
    }
  } else {
    permissionsCache.clear();
  }
}

export async function getRestrictedFields(
  userId: string,
  tenantId: string,
  modelName: string
): Promise<{ readRestricted: string[]; writeRestricted: string[] }> {
  const permissions = await loadFieldPermissions(userId, tenantId, modelName);

  const readRestricted: string[] = [];
  const writeRestricted: string[] = [];

  if (permissions) {
    for (const [fieldName, perm] of permissions.fields) {
      if (!perm.can_read) readRestricted.push(fieldName);
      if (!perm.can_write) writeRestricted.push(fieldName);
    }
  }

  return { readRestricted, writeRestricted };
}
