import {
  Injectable,
  NotFoundException,
  Logger,
} from '@nestjs/common';
import { PrismaService } from '@chatapp/database';

/**
 * FASE 6 — DLP (Data Loss Prevention) Service
 *
 * Escanea mensajes en busca de patrones sensibles (números de tarjetas,
 * SSN, emails, números de teléfono, etc.) y aplica acciones de bloqueo
 * o alerta según las reglas configuradas.
 */
@Injectable()
export class DlpService {
  private readonly logger = new Logger(DlpService.name);

  constructor(private readonly prisma: PrismaService) {}

  // ============ Reglas DLP ============

  /**
   * Crea una regla DLP.
   */
  async createRule(wsId: string, userId: string, dto: {
    name: string;
    description?: string;
    pattern: string;
    pattern_type?: string;
    action?: string;
    channel_ids?: string[];
    notify_admins?: boolean;
  }) {
    return this.prisma.dlpRule.create({
      data: {
        workspaceId: wsId,
        name: dto.name,
        description: dto.description,
        pattern: dto.pattern,
        patternType: dto.pattern_type || 'regex',
        action: dto.action || 'block',
        channelIds: dto.channel_ids || [],
        isActive: true,
        notifyAdmins: dto.notify_admins ?? true,
        createdBy: userId,
      },
    });
  }

  /**
   * Lista las reglas DLP del workspace.
   */
  async listRules(wsId: string) {
    const rules = await this.prisma.dlpRule.findMany({
      where: { workspaceId: wsId },
      orderBy: { createdAt: 'desc' },
    });
    return rules.map((r) => ({
      id: r.id,
      name: r.name,
      description: r.description,
      pattern: r.pattern,
      pattern_type: r.patternType,
      action: r.action,
      channel_ids: r.channelIds,
      is_active: r.isActive,
      notify_admins: r.notifyAdmins,
      created_at: r.createdAt,
      updated_at: r.updatedAt,
    }));
  }

  /**
   * Actualiza una regla DLP.
   */
  async updateRule(wsId: string, ruleId: string, dto: any) {
    const rule = await this.prisma.dlpRule.findFirst({
      where: { id: ruleId, workspaceId: wsId },
    });
    if (!rule) throw new NotFoundException('DLP rule not found');

    const data: any = {};
    const allowed = ['name', 'description', 'pattern', 'patternType', 'action', 'channelIds', 'isActive', 'notifyAdmins'];
    const mapping: Record<string, string> = {
      pattern_type: 'patternType',
      channel_ids: 'channelIds',
      is_active: 'isActive',
      notify_admins: 'notifyAdmins',
    };
    for (const key of Object.keys(dto)) {
      const prismaKey = mapping[key] || key;
      if (allowed.includes(prismaKey)) data[prismaKey] = dto[key];
    }

    return this.prisma.dlpRule.update({ where: { id: ruleId }, data });
  }

  /**
   * Elimina una regla DLP.
   */
  async deleteRule(wsId: string, ruleId: string) {
    const rule = await this.prisma.dlpRule.findFirst({
      where: { id: ruleId, workspaceId: wsId },
    });
    if (!rule) throw new NotFoundException('DLP rule not found');
    await this.prisma.dlpRule.delete({ where: { id: ruleId } });
    return { deleted: true };
  }

  // ============ Violaciones DLP ============

  /**
   * Lista las violaciones DLP con paginación.
   */
  async listViolations(
    wsId: string,
    opts: { page?: number; limit?: number; ruleId?: string } = {},
  ) {
    const page = Math.max(opts.page ?? 1, 1);
    const limit = Math.min(opts.limit ?? 50, 100);
    const skip = (page - 1) * limit;

    const where: any = { workspaceId: wsId };
    if (opts.ruleId) where.ruleId = opts.ruleId;

    const [items, total] = await Promise.all([
      this.prisma.dlpViolation.findMany({
        where,
        orderBy: { createdAt: 'desc' },
        skip,
        take: limit,
      }),
      this.prisma.dlpViolation.count({ where }),
    ]);

    return {
      violations: items.map((v) => ({
        id: v.id,
        rule_id: v.ruleId,
        message_id: v.messageId,
        user_id: v.userId,
        channel_id: v.channelId,
        matched_text: v.matchedText,
        action: v.action,
        created_at: v.createdAt,
      })),
      total,
      page,
      limit,
      pages: Math.ceil(total / limit),
    };
  }

  // ============ Scanning ============

  /**
   * Escanea el texto de un mensaje contra las reglas DLP activas.
   * Devuelve las violaciones encontradas.
   */
  async scanMessage(
    wsId: string,
    messageId: string,
    text: string,
    userId: string | null,
    channelId: string,
  ): Promise<{
    blocked: boolean;
    violations: any[];
  }> {
    const rules = await this.prisma.dlpRule.findMany({
      where: { workspaceId: wsId, isActive: true },
    });

    if (rules.length === 0) {
      return { blocked: false, violations: [] };
    }

    const violations: any[] = [];
    let shouldBlock = false;

    for (const rule of rules) {
      // Si la regla aplica a canales específicos, verificar
      if (rule.channelIds.length > 0 && !rule.channelIds.includes(channelId)) {
        continue;
      }

      const matches = this.matchPattern(text, rule.pattern, rule.patternType);
      if (matches.length > 0) {
        // Crear violación
        await this.prisma.dlpViolation.create({
          data: {
            workspaceId: wsId,
            ruleId: rule.id,
            messageId,
            userId,
            channelId,
            matchedText: this.maskSensitive(matches[0]),
            action: rule.action,
          },
        });

        violations.push({
          rule_id: rule.id,
          rule_name: rule.name,
          action: rule.action,
          matched: this.maskSensitive(matches[0]),
        });

        if (rule.action === 'block') {
          shouldBlock = true;
        }

        if (rule.notifyAdmins) {
          this.logger.warn(
            `DLP violation: rule "${rule.name}" triggered in channel ${channelId} by user ${userId}`,
          );
        }
      }
    }

    return { blocked: shouldBlock, violations };
  }

  /**
   * Escanea mensajes en lote de un canal.
   */
  async scanChannel(
    wsId: string,
    channelId: string,
    opts: { limit?: number } = {},
  ): Promise<{
    channel_id: string;
    scanned: number;
    violations: number;
    blocked: number;
  }> {
    const limit = Math.min(opts.limit ?? 100, 500);
    const messages = await this.prisma.message.findMany({
      where: { workspaceId: wsId, channelId, isDeleted: false, text: { not: null } },
      orderBy: { ts: 'desc' },
      take: limit,
      select: { id: true, text: true, userId: true, channelId: true },
    });

    let violations = 0;
    let blocked = 0;

    for (const msg of messages) {
      const result = await this.scanMessage(
        wsId,
        msg.id,
        msg.text || '',
        msg.userId,
        msg.channelId,
      );
      if (result.violations.length > 0) violations += result.violations.length;
      if (result.blocked) blocked++;
    }

    return {
      channel_id: channelId,
      scanned: messages.length,
      violations,
      blocked,
    };
  }

  // ============ Patrones predefinidos ============

  /**
   * Devuelve patrones DLP predefinidos comunes.
   */
  getPredefinedPatterns(): any[] {
    return [
      {
        name: 'Credit Card (Visa/Mastercard/Amex)',
        pattern: '\\b(?:\\d[ -]*?){13,16}\\b',
        pattern_type: 'regex',
        description: 'Detecta números de tarjeta de crédito',
      },
      {
        name: 'SSN (US)',
        pattern: '\\b\\d{3}-\\d{2}-\\d{4}\\b',
        pattern_type: 'regex',
        description: 'Detecta números de Seguro Social de EE.UU.',
      },
      {
        name: 'Email Address',
        pattern: '\\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\\.[A-Z|a-z]{2,}\\b',
        pattern_type: 'regex',
        description: 'Detecta direcciones de email',
      },
      {
        name: 'Phone Number',
        pattern: '\\b(?:\\+?\\d{1,3}[-.\\s]?)?\\(?\\d{3}\\)?[-.\\s]?\\d{3}[-.\\s]?\\d{4}\\b',
        pattern_type: 'regex',
        description: 'Detecta números de teléfono',
      },
      {
        name: 'IBAN',
        pattern: '\\b[A-Z]{2}\\d{2}[A-Z0-9]{10,30}\\b',
        pattern_type: 'regex',
        description: 'Detecta números IBAN',
      },
      {
        name: 'API Key (generic)',
        pattern: '\\b(?:api[_-]?key|secret|token|password)\\s*[:=]\\s*["\']?[A-Za-z0-9_\\-]{20,}["\']?',
        pattern_type: 'regex',
        description: 'Detecta posibles claves API o secretos',
      },
      {
        name: 'AWS Access Key',
        pattern: '\\bAKIA[0-9A-Z]{16}\\b',
        pattern_type: 'regex',
        description: 'Detecta claves de acceso de AWS',
      },
    ];
  }

  // ------------------------------------------------------------------

  private matchPattern(text: string, pattern: string, patternType: string): string[] {
    try {
      if (patternType === 'regex') {
        const regex = new RegExp(pattern, 'gi');
        const matches = text.match(regex);
        return matches || [];
      } else if (patternType === 'keyword') {
        const lower = text.toLowerCase();
        const kw = pattern.toLowerCase();
        return lower.includes(kw) ? [kw] : [];
      }
    } catch (err) {
      this.logger.warn(`Invalid DLP pattern: ${(err as Error).message}`);
    }
    return [];
  }

  private maskSensitive(text: string): string {
    if (text.length <= 4) return '****';
    return text.substring(0, 2) + '****' + text.substring(text.length - 2);
  }
}