import { Response, NextFunction } from 'express';
import { query, queryOne } from '../../config/database.js';
import { AuthenticatedRequest } from '../types/index.js';
import { logger } from '../utils/logger.js';

// ============================================================================
// 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
// ============================================================================

/**
 * Get cache key for user/model combination
 */
function getCacheKey(userId: string, tenantId: string, modelName: string): string {
  return `${tenantId}:${userId}:${modelName}`;
}

/**
 * Load field permissions for a user on a specific model
 */
async function loadFieldPermissions(
  userId: string,
  tenantId: string,
  modelName: string
): Promise<ModelFieldPermissions | null> {
  // Check cache first
  const cacheKey = getCacheKey(userId, tenantId, modelName);
  const cached = permissionsCache.get(cacheKey);

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

  // Load from database
  const result = await query<{
    field_name: string;
    can_read: boolean;
    can_write: boolean;
  }>(
    `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.length === 0) {
    // No permissions defined = allow all
    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,
    });
  }

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

  return permissions;
}

/**
 * Filter object fields based on read permissions
 */
function filterReadFields<T extends Record<string, any>>(
  data: T,
  permissions: ModelFieldPermissions | null
): Partial<T> {
  // No permissions defined = return all fields
  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 no permission defined for field, allow it
    // If permission exists and can_read is true, allow it
    if (!fieldPerm || fieldPerm.can_read) {
      filtered[key] = value;
    }
  }

  return filtered as Partial<T>;
}

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

/**
 * Validate write permissions for incoming data
 */
function validateWriteFields<T extends Record<string, any>>(
  data: T,
  permissions: ModelFieldPermissions | null
): { valid: boolean; forbiddenFields: string[] } {
  // No permissions defined = allow all writes
  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 permission exists and can_write is false, it's forbidden
    if (fieldPerm && !fieldPerm.can_write) {
      forbiddenFields.push(key);
    }
  }

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

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

/**
 * Middleware to filter response fields based on read permissions
 * Use this on GET endpoints
 */
export function filterResponseFields(modelName: string) {
  return async (req: AuthenticatedRequest, res: Response, next: NextFunction): Promise<void> => {
    // Store original json method
    const originalJson = res.json.bind(res);

    // Override json method to filter fields
    res.json = function(body: any) {
      (async () => {
        try {
          // Only filter for authenticated requests
          if (!req.user) {
            return originalJson(body);
          }

          // Load permissions
          const permissions = await loadFieldPermissions(
            req.user.userId,
            req.user.tenantId,
            modelName
          );

          // If no permissions defined or super_admin, return original
          if (!permissions || req.user.roles.includes('super_admin')) {
            return originalJson(body);
          }

          // Filter the response
          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();
  };
}

/**
 * Middleware to validate write permissions on incoming data
 * Use this on POST/PUT/PATCH endpoints
 */
export function validateWritePermissions(modelName: string) {
  return async (req: AuthenticatedRequest, res: Response, next: NextFunction): Promise<void> => {
    try {
      // Skip for unauthenticated requests (they'll fail auth anyway)
      if (!req.user) {
        return next();
      }

      // Super admins bypass field permission checks
      if (req.user.roles.includes('super_admin')) {
        return next();
      }

      // Load permissions
      const permissions = await loadFieldPermissions(
        req.user.userId,
        req.user.tenantId,
        modelName
      );

      // No permissions defined = allow all
      if (!permissions) {
        return next();
      }

      // Validate write fields in request body
      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.userId,
            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);
    }
  };
}

/**
 * Combined middleware for both read and write validation
 */
export function fieldPermissions(modelName: string) {
  const readFilter = filterResponseFields(modelName);
  const writeValidator = validateWritePermissions(modelName);

  return async (req: AuthenticatedRequest, res: Response, next: NextFunction): Promise<void> => {
    // For write operations, validate first
    if (['POST', 'PUT', 'PATCH'].includes(req.method)) {
      await writeValidator(req, res, () => {
        // If write validation passed, apply read filter for response
        readFilter(req, res, next);
      });
    } else {
      // For read operations, just apply read filter
      await readFilter(req, res, next);
    }
  };
}

/**
 * Clear permissions cache for a user (call after permission changes)
 */
export function clearPermissionsCache(userId?: string, tenantId?: string): void {
  if (userId && tenantId) {
    // Clear specific user's cache
    const prefix = `${tenantId}:${userId}:`;
    for (const key of permissionsCache.keys()) {
      if (key.startsWith(prefix)) {
        permissionsCache.delete(key);
      }
    }
  } else {
    // Clear all cache
    permissionsCache.clear();
  }
}

/**
 * Get list of restricted fields for a user on a model
 * Useful for frontend to know which fields to hide/disable
 */
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 };
}
