import { describe, it, expect, beforeEach, vi, afterEach } from 'vitest';
import { HomeTitleAlertPipeline } from '../src/alert.pipeline';
import {
  ChangeDetectionResult,
  PropertyAlert,
  PropertySnapshot,
  ChangeType,
  Severity,
  PropertyChange,
} from '../src/types';

// Mock @shieldai/db
const mockPrisma = vi.fn();

vi.mock('@shieldai/db', () => ({
  prisma: mockPrisma(),
  AlertSeverity: {
    INFO: 'INFO',
    WARNING: 'WARNING',
    CRITICAL: 'CRITICAL',
  },
  AlertChannel: {
    EMAIL: 'email',
    PUSH: 'push',
    SMS: 'sms',
  },
}));

// Mock @shieldai/shared-notifications
let mockSendNotification = vi.fn();

vi.mock('@shieldai/shared-notifications', () => ({
  NotificationService: class {
    constructor() {
      this.send = mockSendNotification;
    }
  },
  loadNotificationConfig: () => ({
    apiKey: 'test-key',
    baseUrl: 'http://localhost:3000',
  }),
}));

// Mock @shieldai/shared-notifications
const mockSendNotification = vi.fn();
vi.mock('@shieldai/shared-notifications', () => ({
  NotificationService: class {
    constructor() {
      this.send = mockSendNotification;
    }
  },
  loadNotificationConfig: () => ({
    apiKey: 'test-key',
    baseUrl: 'http://localhost:3000',
  }),
}));

function buildChangeResult(overrides: Partial<ChangeDetectionResult> = {}): ChangeDetectionResult {
  return {
    propertyId: 'prop-001',
    changeType: 'ownership_transfer' as ChangeType,
    severity: 'major' as Severity,
    confidence: 0.95,
    changes: [
      { field: 'ownerName', oldValue: 'John Doe', newValue: 'Jane Smith', changeType: 'ownership_transfer' as ChangeType },
    ],
    previousSnapshot: {
      id: 'snap-1',
      propertyId: 'prop-001',
      capturedAt: '2026-01-01T00:00:00Z',
      ownerName: 'John Doe',
      address: { streetNumber: '123', streetName: 'main', city: 'springfield', state: 'IL', zip: '62701' },
      propertyType: 'residential',
    } as PropertySnapshot,
    currentSnapshot: {
      id: 'snap-2',
      propertyId: 'prop-001',
      capturedAt: '2026-02-01T00:00:00Z',
      ownerName: 'Jane Smith',
      address: { streetNumber: '123', streetName: 'main', city: 'springfield', state: 'IL', zip: '62701' },
      propertyType: 'residential',
    } as PropertySnapshot,
    detectedAt: '2026-05-14T12:00:00Z',
    ...overrides,
  };
}

describe('HomeTitleAlertPipeline', () => {
  let pipeline: HomeTitleAlertPipeline;

  beforeEach(() => {
    vi.useFakeTimers();
    pipeline = new HomeTitleAlertPipeline();
    vi.clearAllMocks();
  });

  afterEach(() => {
    vi.useRealTimers();
  });

  describe('processChangeDetection', () => {
    it('creates alert for major severity change', async () => {
      mockPrisma.subscription.findUnique.mockResolvedValue({ tier: 'premium' });
      mockPrisma.alert.findFirst.mockResolvedValue(null);
      mockPrisma.alert.create.mockResolvedValue({
        id: 'alert-001',
        subscriptionId: 'sub-001',
        userId: 'user-001',
        type: 'system_warning',
        title: '[MAJOR] Ownership Transfer detected',
        message: 'Change detected',
        severity: 'CRITICAL',
        channel: ['email', 'push', 'sms'],
        createdAt: new Date('2026-05-14T12:00:00Z'),
      });

      const result = buildChangeResult({
        changeType: 'ownership_transfer',
        severity: 'major',
        confidence: 0.95,
      });

      const alert = await pipeline.processChangeDetection(result, 'sub-001', 'user-001');

      expect(alert).toBeDefined();
      expect(alert?.changeType).toBe('ownership_transfer');
      expect(alert?.severity).toBe('critical');
      expect(mockPrisma.alert.create).toHaveBeenCalled();
    });

    it('creates alert for moderate severity change', async () => {
      mockPrisma.subscription.findUnique.mockResolvedValue({ tier: 'plus' });
      mockPrisma.alert.findFirst.mockResolvedValue(null);
      mockPrisma.alert.create.mockResolvedValue({
        id: 'alert-002',
        subscriptionId: 'sub-001',
        userId: 'user-001',
        type: 'system_warning',
        title: '[MODERATE] Deed Change detected',
        message: 'Change detected',
        severity: 'WARNING',
        channel: ['email', 'push'],
        createdAt: new Date('2026-05-14T12:00:00Z'),
      });

      const result = buildChangeResult({
        changeType: 'deed_change',
        severity: 'moderate',
        confidence: 0.85,
      });

      const alert = await pipeline.processChangeDetection(result, 'sub-001', 'user-001');

      expect(alert).toBeDefined();
      expect(alert?.changeType).toBe('deed_change');
      expect(alert?.severity).toBe('warning');
    });

    it('returns null when subscription not found', async () => {
      mockPrisma.subscription.findUnique.mockResolvedValue(null);

      const result = buildChangeResult();
      const alert = await pipeline.processChangeDetection(result, 'sub-999', 'user-001');

      expect(alert).toBeNull();
    });

    it('returns null for minor severity with default minSeverity', async () => {
      mockPrisma.subscription.findUnique.mockResolvedValue({ tier: 'premium' });

      const result = buildChangeResult({
        changeType: 'tax_change',
        severity: 'minor',
        confidence: 0.85,
      });

      const alert = await pipeline.processChangeDetection(result, 'sub-001', 'user-001');

      expect(alert).toBeNull();
    });

    it('returns null when confidence below threshold', async () => {
      mockPrisma.subscription.findUnique.mockResolvedValue({ tier: 'premium' });

      const result = buildChangeResult({
        confidence: 0.5,
      });

      const alert = await pipeline.processChangeDetection(result, 'sub-001', 'user-001');

      expect(alert).toBeNull();
    });

    it('deduplicates alerts within 24h window', async () => {
      mockPrisma.subscription.findUnique.mockResolvedValue({ tier: 'premium' });
      mockPrisma.alert.findFirst.mockResolvedValue({ id: 'existing-alert' });

      const result = buildChangeResult();
      const first = await pipeline.processChangeDetection(result, 'sub-001', 'user-001');
      const second = await pipeline.processChangeDetection(result, 'sub-001', 'user-001');

      expect(first).toBeDefined();
      expect(second).toBeNull();
    });

    it('creates normalized alert for integration with correlation engine', async () => {
      mockPrisma.subscription.findUnique.mockResolvedValue({ tier: 'premium' });
      mockPrisma.alert.findFirst.mockResolvedValue(null);
      mockPrisma.alert.create.mockResolvedValue({
        id: 'alert-003',
        subscriptionId: 'sub-001',
        userId: 'user-001',
        type: 'system_warning',
        title: '[MAJOR] Ownership Transfer detected',
        message: 'Change detected',
        severity: 'CRITICAL',
        channel: ['email', 'push', 'sms'],
        createdAt: new Date('2026-05-14T12:00:00Z'),
      });

      const result = buildChangeResult();
      await pipeline.processChangeDetection(result, 'sub-001', 'user-001');

      expect(mockPrisma.normalizedAlert.create).toHaveBeenCalledWith(
        expect.objectContaining({
          source: 'DARKWATCH',
          userId: 'user-001',
          severity: 'INFO',
        })
      );
    });

    it('dispatches notifications for premium tier', async () => {
      mockPrisma.subscription.findUnique.mockResolvedValue({ tier: 'premium' });
      mockPrisma.alert.findFirst.mockResolvedValue(null);
      mockPrisma.alert.create.mockResolvedValue({
        id: 'alert-004',
        subscriptionId: 'sub-001',
        userId: 'user-001',
        type: 'system_warning',
        title: '[MAJOR] Ownership Transfer detected',
        message: 'Change detected on property prop-001.\n\nChanges:\n- ownerName: John Doe → Jane Smith',
        severity: 'CRITICAL',
        channel: ['email', 'push', 'sms'],
        createdAt: new Date('2026-05-14T12:00:00Z'),
      });
      mockPrisma.user.findUnique.mockResolvedValue({
        email: 'test@example.com',
        name: 'Test User',
      });

      const result = buildChangeResult();
      await pipeline.processChangeDetection(result, 'sub-001', 'user-001');

      expect(mockSendNotification).toHaveBeenCalled();
    });

    it('builds correct dedup key', async () => {
      mockPrisma.subscription.findUnique.mockResolvedValue({ tier: 'premium' });
      mockPrisma.alert.findFirst.mockResolvedValue(null);
      mockPrisma.alert.create.mockResolvedValue({
        id: 'alert-005',
        subscriptionId: 'sub-001',
        userId: 'user-001',
        type: 'system_warning',
        title: '[MAJOR] Ownership Transfer detected',
        message: 'Change detected',
        severity: 'CRITICAL',
        channel: ['email'],
        createdAt: new Date('2026-05-14T12:00:00Z'),
      });

      const result = buildChangeResult({ changeType: 'ownership_transfer' });
      await pipeline.processChangeDetection(result, 'sub-001', 'user-001');

      // Verify in-memory dedup was recorded
      const cleanupCount = pipeline.cleanupExpiredDedups();
      // No expired dedups at this point
      expect(cleanupCount).toBe(0);
    });
  });

  describe('processBatch', () => {
    it('processes multiple change results', async () => {
      mockPrisma.subscription.findUnique.mockResolvedValue({ tier: 'premium' });
      mockPrisma.alert.findFirst.mockResolvedValue(null);
      mockPrisma.alert.create.mockResolvedValue({
        id: 'alert-batch-1',
        subscriptionId: 'sub-001',
        userId: 'user-001',
        type: 'system_warning',
        title: '[MAJOR] Ownership Transfer detected',
        message: 'Change 1',
        severity: 'CRITICAL',
        channel: ['email'],
        createdAt: new Date('2026-05-14T12:00:00Z'),
      });
      mockPrisma.user.findUnique.mockResolvedValue({ email: 'test@example.com', name: 'Test' });

      const results = [
        buildChangeResult({ changeType: 'ownership_transfer', propertyId: 'prop-001' }),
        buildChangeResult({ changeType: 'deed_change', propertyId: 'prop-002' }),
      ];

      const alerts = await pipeline.processBatch(results, 'sub-001', 'user-001');

      expect(alerts.length).toBeGreaterThanOrEqual(1);
      expect(mockPrisma.alert.create).toHaveBeenCalledTimes(results.length);
    });

    it('creates correlation group for multiple alerts', async () => {
      mockPrisma.subscription.findUnique.mockResolvedValue({ tier: 'premium' });
      mockPrisma.alert.findFirst.mockResolvedValue(null);
      mockPrisma.alert.create.mockResolvedValue({
        id: `alert-batch-${Date.now()}`,
        subscriptionId: 'sub-001',
        userId: 'user-001',
        type: 'system_warning',
        title: '[MAJOR] Ownership Transfer detected',
        message: 'Change',
        severity: 'CRITICAL',
        channel: ['email'],
        createdAt: new Date('2026-05-14T12:00:00Z'),
      });
      mockPrisma.user.findUnique.mockResolvedValue({ email: 'test@example.com', name: 'Test' });

      const results = [
        buildChangeResult({ changeType: 'ownership_transfer', propertyId: 'prop-001' }),
        buildChangeResult({ changeType: 'deed_change', propertyId: 'prop-002' }),
      ];

      await pipeline.processBatch(results, 'sub-001', 'user-001');

      expect(mockPrisma.correlationGroup.create).toHaveBeenCalled();
    });

    it('returns empty array when all results are deduplicated', async () => {
      mockPrisma.subscription.findUnique.mockResolvedValue({ tier: 'premium' });
      mockPrisma.alert.findFirst.mockResolvedValue({ id: 'existing-alert' });

      const results = [
        buildChangeResult({ changeType: 'ownership_transfer', propertyId: 'prop-001' }),
      ];

      const alerts = await pipeline.processBatch(results, 'sub-001', 'user-001');
      expect(alerts).toEqual([]);
    });
  });

  describe('cleanupExpiredDedups', () => {
    it('removes expired dedup entries', async () => {
      mockPrisma.subscription.findUnique.mockResolvedValue({ tier: 'premium' });
      mockPrisma.alert.findFirst.mockResolvedValue(null);
      mockPrisma.alert.create.mockResolvedValue({
        id: 'alert-cleanup',
        subscriptionId: 'sub-001',
        userId: 'user-001',
        type: 'system_warning',
        title: '[MAJOR] Ownership Transfer detected',
        message: 'Change',
        severity: 'CRITICAL',
        channel: ['email'],
        createdAt: new Date('2026-05-14T12:00:00Z'),
      });

      const result = buildChangeResult();
      await pipeline.processChangeDetection(result, 'sub-001', 'user-001');

      // Advance timer past the dedup window (24 hours)
      vi.advanceTimersByTime(25 * 60 * 60 * 1000);

      const cleaned = pipeline.cleanupExpiredDedups();
      expect(cleaned).toBeGreaterThanOrEqual(1);
    });
  });

  describe('severity mapping', () => {
    it('maps major to critical', async () => {
      mockPrisma.subscription.findUnique.mockResolvedValue({ tier: 'premium' });
      mockPrisma.alert.findFirst.mockResolvedValue(null);
      mockPrisma.alert.create.mockResolvedValue({
        id: 'alert-sev-1',
        subscriptionId: 'sub-001',
        userId: 'user-001',
        type: 'system_warning',
        title: '[MAJOR] Ownership Transfer detected',
        message: 'Change',
        severity: 'CRITICAL',
        channel: ['email'],
        createdAt: new Date('2026-05-14T12:00:00Z'),
      });

      const result = buildChangeResult({ severity: 'major' });
      await pipeline.processChangeDetection(result, 'sub-001', 'user-001');

      expect(mockPrisma.alert.create).toHaveBeenCalledWith(
        expect.objectContaining({ severity: 'CRITICAL' })
      );
    });

    it('maps moderate to warning', async () => {
      mockPrisma.subscription.findUnique.mockResolvedValue({ tier: 'premium' });
      mockPrisma.alert.findFirst.mockResolvedValue(null);
      mockPrisma.alert.create.mockResolvedValue({
        id: 'alert-sev-2',
        subscriptionId: 'sub-001',
        userId: 'user-001',
        type: 'system_warning',
        title: '[MODERATE] Deed Change detected',
        message: 'Change',
        severity: 'WARNING',
        channel: ['email'],
        createdAt: new Date('2026-05-14T12:00:00Z'),
      });

      const result = buildChangeResult({ severity: 'moderate', changeType: 'deed_change' });
      await pipeline.processChangeDetection(result, 'sub-001', 'user-001');

      expect(mockPrisma.alert.create).toHaveBeenCalledWith(
        expect.objectContaining({ severity: 'WARNING' })
      );
    });
  });
});
