/**
 * Plan Enforcement Middleware
 *
 * Middleware for validating plan limits and features before allowing operations
 */

import { Request, Response, NextFunction } from 'express';
import { DataSource } from 'typeorm';
import { PlanLimitsService } from '../services/plan-limits.service.js';
import { TenantSubscription, PlanFeature } from '../entities/index.js';
import { logger } from '../../../shared/utils/logger.js';

// Extend Express Request to include user info
interface AuthenticatedRequest extends Request {
  user?: {
    id: string;
    tenantId: string;
    userId: string;
    email: string;
    role: string;
  };
}

// Configuration for limit checks
export interface LimitCheckConfig {
  limitKey: string;
  getCurrentUsage?: (req: AuthenticatedRequest, tenantId: string) => Promise<number>;
  requestedUnits?: number;
  errorMessage?: string;
}

// Configuration for feature checks
export interface FeatureCheckConfig {
  featureKey: string;
  errorMessage?: string;
}

/**
 * Create a middleware that checks plan limits
 */
export function requireLimit(
  dataSource: DataSource,
  config: LimitCheckConfig
) {
  const planLimitsService = new PlanLimitsService(dataSource);

  return async (req: AuthenticatedRequest, res: Response, next: NextFunction) => {
    try {
      const tenantId = req.user?.tenantId;

      if (!tenantId) {
        return res.status(401).json({
          success: false,
          error: 'No autenticado',
        });
      }

      // Get current usage
      let currentUsage = 0;
      if (config.getCurrentUsage) {
        currentUsage = await config.getCurrentUsage(req, tenantId);
      } else {
        currentUsage = await planLimitsService.getCurrentUsage(tenantId, config.limitKey);
      }

      // Check if within limits
      const check = await planLimitsService.checkUsage(
        tenantId,
        config.limitKey,
        currentUsage,
        config.requestedUnits || 1
      );

      if (!check.allowed) {
        logger.warn('Plan limit exceeded', {
          tenantId,
          limitKey: config.limitKey,
          currentUsage,
          limit: check.limit,
        });

        return res.status(403).json({
          success: false,
          error: config.errorMessage || check.message,
          details: {
            limitKey: config.limitKey,
            currentUsage: check.currentUsage,
            limit: check.limit,
            remaining: check.remaining,
          },
        });
      }

      // Add limit info to request for downstream use
      (req as any).limitCheck = check;

      next();
    } catch (error) {
      logger.error('Plan limit check failed', {
        error: (error as Error).message,
        limitKey: config.limitKey,
      });
      next(error);
    }
  };
}

/**
 * Create a middleware that checks if tenant has a specific feature
 */
export function requireFeature(
  dataSource: DataSource,
  config: FeatureCheckConfig
) {
  const featureRepository = dataSource.getRepository(PlanFeature);
  const subscriptionRepository = dataSource.getRepository(TenantSubscription);

  return async (req: AuthenticatedRequest, res: Response, next: NextFunction) => {
    try {
      const tenantId = req.user?.tenantId;

      if (!tenantId) {
        return res.status(401).json({
          success: false,
          error: 'No autenticado',
        });
      }

      // Get tenant's subscription
      const subscription = await subscriptionRepository.findOne({
        where: { tenantId, status: 'active' },
      });

      let planId: string | null = null;

      if (subscription) {
        planId = subscription.planId;
      } else {
        // Check for free plan
        const freePlanFeature = await featureRepository.findOne({
          where: { featureKey: config.featureKey },
          relations: ['plan'],
        });

        if (freePlanFeature?.plan?.code === 'FREE') {
          planId = freePlanFeature.plan.id;
        }
      }

      if (!planId) {
        return res.status(403).json({
          success: false,
          error: config.errorMessage || 'Suscripción requerida para esta función',
        });
      }

      // Check if plan has the feature
      const feature = await featureRepository.findOne({
        where: { planId, featureKey: config.featureKey },
      });

      if (!feature || !feature.enabled) {
        logger.warn('Feature not available', {
          tenantId,
          featureKey: config.featureKey,
          planId,
        });

        return res.status(403).json({
          success: false,
          error: config.errorMessage || `Función no disponible: ${config.featureKey}`,
          details: {
            featureKey: config.featureKey,
            upgrade: true,
          },
        });
      }

      // Add feature info to request
      (req as any).feature = feature;

      next();
    } catch (error) {
      logger.error('Feature check failed', {
        error: (error as Error).message,
        featureKey: config.featureKey,
      });
      next(error);
    }
  };
}

/**
 * Create a middleware that checks subscription status
 */
export function requireActiveSubscription(dataSource: DataSource) {
  const subscriptionRepository = dataSource.getRepository(TenantSubscription);

  return async (req: AuthenticatedRequest, res: Response, next: NextFunction) => {
    try {
      const tenantId = req.user?.tenantId;

      if (!tenantId) {
        return res.status(401).json({
          success: false,
          error: 'No autenticado',
        });
      }

      const subscription = await subscriptionRepository.findOne({
        where: { tenantId },
      });

      if (!subscription) {
        // Allow free tier access
        (req as any).subscription = null;
        return next();
      }

      if (subscription.status === 'cancelled') {
        return res.status(403).json({
          success: false,
          error: 'Suscripción cancelada',
          details: {
            status: subscription.status,
            cancelledAt: subscription.cancelledAt,
          },
        });
      }

      if (subscription.status === 'past_due') {
        // Allow limited access for past_due, but warn
        logger.warn('Tenant accessing with past_due subscription', { tenantId });
      }

      // Add subscription to request
      (req as any).subscription = subscription;

      next();
    } catch (error) {
      logger.error('Subscription check failed', {
        error: (error as Error).message,
      });
      next(error);
    }
  };
}

/**
 * Create a rate limiting middleware based on plan
 */
export function planBasedRateLimit(
  dataSource: DataSource,
  options: {
    windowMs?: number;
    defaultLimit?: number;
    limitKey?: string;
  } = {}
) {
  const planLimitsService = new PlanLimitsService(dataSource);
  const windowMs = options.windowMs || 60 * 1000; // 1 minute default
  const defaultLimit = options.defaultLimit || 100;
  const limitKey = options.limitKey || 'api_calls_per_minute';

  // In-memory rate limit store (use Redis in production)
  const rateLimitStore = new Map<string, { count: number; resetAt: number }>();

  return async (req: AuthenticatedRequest, res: Response, next: NextFunction) => {
    try {
      const tenantId = req.user?.tenantId;

      if (!tenantId) {
        return next(); // Skip for unauthenticated requests
      }

      const now = Date.now();
      const key = `${tenantId}:${limitKey}`;

      // Get or create rate limit entry
      let entry = rateLimitStore.get(key);
      if (!entry || entry.resetAt < now) {
        entry = { count: 0, resetAt: now + windowMs };
        rateLimitStore.set(key, entry);
      }

      // Get plan limit
      const planLimit = await planLimitsService.getTenantLimit(tenantId, limitKey);
      const limit = planLimit > 0 ? planLimit : defaultLimit;

      // Check if exceeded
      if (entry.count >= limit) {
        const retryAfter = Math.ceil((entry.resetAt - now) / 1000);

        return res.status(429).json({
          success: false,
          error: 'Límite de peticiones excedido',
          details: {
            limit,
            remaining: 0,
            resetAt: new Date(entry.resetAt).toISOString(),
            retryAfter,
          },
        });
      }

      // Increment counter
      entry.count++;

      // Set rate limit headers
      res.set({
        'X-RateLimit-Limit': String(limit),
        'X-RateLimit-Remaining': String(limit - entry.count),
        'X-RateLimit-Reset': String(Math.ceil(entry.resetAt / 1000)),
      });

      next();
    } catch (error) {
      logger.error('Rate limit check failed', {
        error: (error as Error).message,
      });
      next(error);
    }
  };
}

/**
 * Utility: Get common usage getters
 */
export const usageGetters = {
  /**
   * Get user count for tenant
   */
  async getUserCount(dataSource: DataSource, tenantId: string): Promise<number> {
    const result = await dataSource.query(
      `SELECT COUNT(*) as count FROM auth.users WHERE tenant_id = $1 AND is_active = true`,
      [tenantId]
    );
    return parseInt(result[0]?.count || '0', 10);
  },

  /**
   * Get storage usage for tenant (in GB)
   */
  async getStorageUsage(dataSource: DataSource, tenantId: string): Promise<number> {
    // This would need to integrate with file storage system
    // Placeholder implementation
    return 0;
  },

  /**
   * Get API calls count for current month
   */
  async getApiCallsCount(dataSource: DataSource, tenantId: string): Promise<number> {
    const startOfMonth = new Date();
    startOfMonth.setDate(1);
    startOfMonth.setHours(0, 0, 0, 0);

    const result = await dataSource.query(
      `SELECT COALESCE(SUM(api_calls_count), 0) as count
       FROM billing.usage_tracking
       WHERE tenant_id = $1 AND period_start >= $2`,
      [tenantId, startOfMonth]
    );
    return parseInt(result[0]?.count || '0', 10);
  },
};
