import { beforeEach, describe, expect, it, vi } from 'vitest';
import { RuntimeSecurityMonitor } from './runtime-monitor';

describe('RuntimeSecurityMonitor', () => {
  let monitor: RuntimeSecurityMonitor;

  beforeEach(() => {
    monitor = new RuntimeSecurityMonitor({
      enableLogging: false, // Disable logging for tests
      maxEvents: 100,
    });
  });

  describe('recordEvent', () => {
    it('should record a security event', () => {
      const event = {
        type: 'xss_attempt' as const,
        severity: 'high' as const,
        source: { ip: '192.168.1.1', userAgent: 'test-agent' },
        details: { payload: '<script>alert("xss")</script>' },
        blocked: true,
      };

      monitor.recordEvent(event);

      const events = monitor.getEvents();
      expect(events).toHaveLength(1);
      expect(events[0]).toMatchObject(event);
      expect(events[0].id).toBeDefined();
      expect(events[0].timestamp).toBeInstanceOf(Date);
    });

    it('should maintain max events limit', () => {
      const smallMonitor = new RuntimeSecurityMonitor({
        maxEvents: 2,
        enableLogging: false
      });

      // Add 3 events
      for (let i = 0; i < 3; i++) {
        smallMonitor.recordEvent({
          type: 'xss_attempt',
          severity: 'low',
          source: { ip: '192.168.1.1' },
          details: { index: i },
          blocked: false,
        });
      }

      const events = smallMonitor.getEvents();
      expect(events).toHaveLength(2);

      // The events array should contain the last 2 events (indices 1 and 2)
      // getEvents() returns them in reverse chronological order (most recent first)
      const indices = events.map(e => e.details.index);
      expect(indices).toContain(1);
      expect(indices).toContain(2);
      expect(indices).not.toContain(0); // First event should be removed
    });
  });

  describe('specialized recording methods', () => {
    it('should record XSS attempts', () => {
      monitor.recordXSSAttempt({
        payload: '<script>alert("xss")</script>',
        source: { ip: '192.168.1.1' },
        blocked: true,
        context: 'user input field',
      });

      const events = monitor.getEvents();
      expect(events).toHaveLength(1);
      expect(events[0].type).toBe('xss_attempt');
      expect(events[0].severity).toBe('high');
      expect(events[0].blocked).toBe(true);
      expect(events[0].details.payload).toBe('<script>alert("xss")</script>');
      expect(events[0].details.context).toBe('user input field');
    });

    it('should record CSRF violations', () => {
      monitor.recordCSRFViolation({
        expectedToken: 'abc123',
        receivedToken: 'def456',
        source: { ip: '192.168.1.1', sessionId: 'session123' },
        endpoint: '/api/transfer',
      });

      const events = monitor.getEvents();
      expect(events).toHaveLength(1);
      expect(events[0].type).toBe('csrf_violation');
      expect(events[0].severity).toBe('high');
      expect(events[0].blocked).toBe(true);
      expect(events[0].details.endpoint).toBe('/api/transfer');
    });

    it('should record injection attempts', () => {
      monitor.recordInjectionAttempt({
        type: 'sql',
        payload: "'; DROP TABLE users; --",
        source: { ip: '192.168.1.1' },
        blocked: true,
        query: 'SELECT * FROM users WHERE id = ?',
      });

      const events = monitor.getEvents();
      expect(events).toHaveLength(1);
      expect(events[0].type).toBe('injection_attempt');
      expect(events[0].severity).toBe('critical');
      expect(events[0].details.injectionType).toBe('sql');
      expect(events[0].details.payload).toBe("'; DROP TABLE users; --");
    });

    it('should record rate limit exceeded', () => {
      monitor.recordRateLimitExceeded({
        limit: 100,
        current: 150,
        window: '1h',
        source: { ip: '192.168.1.1' },
        endpoint: '/api/data',
      });

      const events = monitor.getEvents();
      expect(events).toHaveLength(1);
      expect(events[0].type).toBe('rate_limit_exceeded');
      expect(events[0].severity).toBe('medium');
      expect(events[0].details.limit).toBe(100);
      expect(events[0].details.current).toBe(150);
    });

    it('should record auth failures', () => {
      monitor.recordAuthFailure({
        reason: 'invalid_credentials',
        source: { ip: '192.168.1.1' },
        username: 'testuser',
        endpoint: '/login',
      });

      const events = monitor.getEvents();
      expect(events).toHaveLength(1);
      expect(events[0].type).toBe('auth_failure');
      expect(events[0].severity).toBe('high');
      expect(events[0].details.reason).toBe('invalid_credentials');
      expect(events[0].details.username).toBe('testuser');
    });

    it('should record suspicious activity with risk-based severity', () => {
      // High risk activity
      monitor.recordSuspiciousActivity({
        activity: 'Multiple failed login attempts from different IPs',
        riskScore: 85,
        source: { ip: '192.168.1.1' },
        context: { attempts: 10, timeWindow: '5m' },
      });

      // Low risk activity
      monitor.recordSuspiciousActivity({
        activity: 'Unusual user agent string',
        riskScore: 25,
        source: { ip: '192.168.1.2' },
      });

      const events = monitor.getEvents();
      expect(events).toHaveLength(2);

      const highRiskEvent = events.find(e => e.details.riskScore === 85);
      const lowRiskEvent = events.find(e => e.details.riskScore === 25);

      expect(highRiskEvent?.severity).toBe('critical');
      expect(highRiskEvent?.blocked).toBe(true);
      expect(lowRiskEvent?.severity).toBe('low');
      expect(lowRiskEvent?.blocked).toBe(false);
    });
  });

  describe('getMetrics', () => {
    beforeEach(() => {
      // Add some test events
      monitor.recordXSSAttempt({
        payload: '<script>alert(1)</script>',
        source: { ip: '192.168.1.1' },
        blocked: true,
      });

      monitor.recordCSRFViolation({
        source: { ip: '192.168.1.1' },
        endpoint: '/api/test',
      });

      monitor.recordInjectionAttempt({
        type: 'sql',
        payload: 'DROP TABLE',
        source: { ip: '192.168.1.2' },
        blocked: false,
      });
    });

    it('should calculate correct metrics', () => {
      const metrics = monitor.getMetrics();

      expect(metrics.totalEvents).toBe(3);
      expect(metrics.blockedEvents).toBe(2);
      expect(metrics.eventsByType).toEqual({
        xss_attempt: 1,
        csrf_violation: 1,
        injection_attempt: 1,
      });
      expect(metrics.eventsBySeverity).toEqual({
        high: 2,
        critical: 1,
      });
      expect(metrics.topSources).toHaveLength(2);
      expect(metrics.topSources[0].ip).toBe('192.168.1.1');
      expect(metrics.topSources[0].count).toBe(2);
    });

    it('should include time range in metrics', () => {
      const metrics = monitor.getMetrics();

      expect(metrics.timeRange.start).toBeInstanceOf(Date);
      expect(metrics.timeRange.end).toBeInstanceOf(Date);
      expect(metrics.timeRange.end.getTime()).toBeGreaterThanOrEqual(
        metrics.timeRange.start.getTime()
      );
    });
  });

  describe('getEvents with filters', () => {
    beforeEach(() => {
      monitor.recordXSSAttempt({
        payload: 'test1',
        source: { ip: '192.168.1.1' },
        blocked: true,
      });

      monitor.recordCSRFViolation({
        source: { ip: '192.168.1.2' },
        endpoint: '/test',
      });

      monitor.recordInjectionAttempt({
        type: 'sql',
        payload: 'test2',
        source: { ip: '192.168.1.3' },
        blocked: false,
      });
    });

    it('should filter by event type', () => {
      const xssEvents = monitor.getEvents({ type: 'xss_attempt' });
      expect(xssEvents).toHaveLength(1);
      expect(xssEvents[0].type).toBe('xss_attempt');
    });

    it('should filter by severity', () => {
      const criticalEvents = monitor.getEvents({ severity: 'critical' });
      expect(criticalEvents).toHaveLength(1);
      expect(criticalEvents[0].type).toBe('injection_attempt');
    });

    it('should filter by date', () => {
      const now = new Date();
      const oneMinuteAgo = new Date(now.getTime() - 60000);

      const recentEvents = monitor.getEvents({ since: oneMinuteAgo });
      expect(recentEvents).toHaveLength(3); // All events are recent

      const futureEvents = monitor.getEvents({
        since: new Date(now.getTime() + 60000)
      });
      expect(futureEvents).toHaveLength(0);
    });

    it('should limit results', () => {
      const limitedEvents = monitor.getEvents({ limit: 2 });
      expect(limitedEvents).toHaveLength(2);
    });

    it('should return events in reverse chronological order', () => {
      const events = monitor.getEvents();
      expect(events).toHaveLength(3);

      // Events should be ordered by timestamp descending
      for (let i = 1; i < events.length; i++) {
        expect(events[i - 1].timestamp.getTime()).toBeGreaterThanOrEqual(
          events[i].timestamp.getTime()
        );
      }
    });
  });

  describe('clearEvents', () => {
    it('should clear all events', () => {
      monitor.recordXSSAttempt({
        payload: 'test',
        source: { ip: '192.168.1.1' },
        blocked: true,
      });

      expect(monitor.getEvents()).toHaveLength(1);

      monitor.clearEvents();

      expect(monitor.getEvents()).toHaveLength(0);
      expect(monitor.getMetrics().totalEvents).toBe(0);
    });
  });

  describe('exportEvents', () => {
    beforeEach(() => {
      monitor.recordXSSAttempt({
        payload: '<script>test</script>',
        source: { ip: '192.168.1.1', userAgent: 'test-agent' },
        blocked: true,
      });
    });

    it('should export events as JSON', () => {
      const jsonExport = monitor.exportEvents('json');
      const events = JSON.parse(jsonExport);

      expect(Array.isArray(events)).toBe(true);
      expect(events).toHaveLength(1);
      expect(events[0].type).toBe('xss_attempt');
    });

    it('should export events as CSV', () => {
      const csvExport = monitor.exportEvents('csv');
      const lines = csvExport.split('\n');

      expect(lines[0]).toContain('id,type,severity,timestamp,blocked,source_ip,user_agent,details');
      expect(lines[1]).toContain('xss_attempt,high');
      expect(lines[1]).toContain('192.168.1.1');
      expect(lines[1]).toContain('test-agent');
    });
  });

  describe('alert thresholds', () => {
    it('should trigger alerts when thresholds are exceeded', () => {
      const alertCallback = vi.fn();
      const alertMonitor = new RuntimeSecurityMonitor({
        alertThresholds: { critical: 1, high: 2, medium: 3 },
        onAlert: alertCallback,
      });

      // First critical event should trigger alert
      alertMonitor.recordInjectionAttempt({
        type: 'sql',
        payload: 'DROP TABLE',
        source: { ip: '192.168.1.1' },
        blocked: true,
      });

      expect(alertCallback).toHaveBeenCalledTimes(1);
      expect(alertCallback).toHaveBeenCalledWith(
        expect.objectContaining({
          type: 'injection_attempt',
          severity: 'critical',
        })
      );
    });

    it('should not trigger alerts below threshold', () => {
      const alertCallback = vi.fn();
      const alertMonitor = new RuntimeSecurityMonitor({
        alertThresholds: { critical: 1, high: 2, medium: 3 },
        onAlert: alertCallback,
      });

      // First high severity event should not trigger alert (threshold is 2)
      alertMonitor.recordXSSAttempt({
        payload: 'test',
        source: { ip: '192.168.1.1' },
        blocked: true,
      });

      expect(alertCallback).not.toHaveBeenCalled();
    });
  });

  describe('metrics updates', () => {
    it('should call metrics update callback when configured', () => {
      const metricsCallback = vi.fn();
      const metricsMonitor = new RuntimeSecurityMonitor({
        onMetricsUpdate: metricsCallback,
      });

      metricsMonitor.recordXSSAttempt({
        payload: 'test',
        source: { ip: '192.168.1.1' },
        blocked: true,
      });

      expect(metricsCallback).toHaveBeenCalledTimes(1);
      expect(metricsCallback).toHaveBeenCalledWith(
        expect.objectContaining({
          totalEvents: 1,
          blockedEvents: 1,
        })
      );
    });
  });
});
