import { tap, expect } from '@git.zone/tstest/tapbundle';
import * as plugins from '../ts/plugins.js';
import * as paths from '../ts/paths.js';
import { StorageManager } from '../ts/storage/classes.storagemanager.js';
import { DKIMCreator } from '../ts/mail/security/classes.dkimcreator.js';
import { BounceManager } from '../ts/mail/core/classes.bouncemanager.js';
import { EmailRouter } from '../ts/mail/routing/classes.email.router.js';
import type { IEmailRoute } from '../ts/mail/routing/interfaces.js';

tap.test('Storage Persistence Across Restarts', async () => {
  const testDir = plugins.path.join(paths.dataDir, '.test-integration-persistence');
  
  // Phase 1: Create storage and write data
  {
    const storage = new StorageManager({ fsPath: testDir });
    
    // Write some test data
    await storage.set('/test/key1', 'value1');
    await storage.setJSON('/test/json', { data: 'test', count: 42 });
    await storage.set('/other/key2', 'value2');
  }
  
  // Phase 2: Create new instance and verify data persists
  {
    const storage = new StorageManager({ fsPath: testDir });
    
    // Verify data persists
    const value1 = await storage.get('/test/key1');
    expect(value1).toEqual('value1');
    
    const jsonData = await storage.getJSON('/test/json');
    expect(jsonData).toEqual({ data: 'test', count: 42 });
    
    const value2 = await storage.get('/other/key2');
    expect(value2).toEqual('value2');
    
    // Test list operation
    const testKeys = await storage.list('/test');
    expect(testKeys.length).toEqual(2);
    expect(testKeys).toContain('/test/key1');
    expect(testKeys).toContain('/test/json');
  }
  
  // Clean up
  await plugins.fs.promises.rm(testDir, { recursive: true, force: true }).catch(() => {});
});

tap.test('DKIM Storage Integration', async () => {
  const testDir = plugins.path.join(paths.dataDir, '.test-integration-dkim');
  const keysDir = plugins.path.join(testDir, 'keys');
  
  // Phase 1: Generate DKIM keys with storage
  {
    const storage = new StorageManager({ fsPath: testDir });
    const dkimCreator = new DKIMCreator(keysDir, storage);
    
    await dkimCreator.handleDKIMKeysForDomain('storage.example.com');
    
    // Verify keys exist
    const keys = await dkimCreator.readDKIMKeys('storage.example.com');
    expect(keys.privateKey).toBeTruthy();
    expect(keys.publicKey).toBeTruthy();
  }
  
  // Phase 2: New instance should find keys in storage
  {
    const storage = new StorageManager({ fsPath: testDir });
    const dkimCreator = new DKIMCreator(keysDir, storage);
    
    // Keys should be loaded from storage
    const keys = await dkimCreator.readDKIMKeys('storage.example.com');
    expect(keys.privateKey).toBeTruthy();
    expect(keys.publicKey).toBeTruthy();
    expect(keys.privateKey).toContain('BEGIN RSA PRIVATE KEY');
  }
  
  // Clean up
  await plugins.fs.promises.rm(testDir, { recursive: true, force: true }).catch(() => {});
});

tap.test('Bounce Manager Storage Integration', async () => {
  const testDir = plugins.path.join(paths.dataDir, '.test-integration-bounce');
  
  // Phase 1: Add to suppression list with storage
  {
    const storage = new StorageManager({ fsPath: testDir });
    const bounceManager = new BounceManager({
      storageManager: storage
    });
    
    // Add emails to suppression list
    bounceManager.addToSuppressionList('bounce1@example.com', 'Hard bounce: invalid_recipient');
    bounceManager.addToSuppressionList('bounce2@example.com', 'Soft bounce: temporary', Date.now() + 3600000);
    
    // Verify suppression
    expect(bounceManager.isEmailSuppressed('bounce1@example.com')).toEqual(true);
    expect(bounceManager.isEmailSuppressed('bounce2@example.com')).toEqual(true);
  }
  
  // Wait a moment to ensure async save completes
  await new Promise(resolve => setTimeout(resolve, 100));
  
  // Phase 2: New instance should load suppression list from storage
  {
    const storage = new StorageManager({ fsPath: testDir });
    const bounceManager = new BounceManager({
      storageManager: storage
    });
    
    // Wait for async load
    await new Promise(resolve => setTimeout(resolve, 100));
    
    // Verify persistence
    expect(bounceManager.isEmailSuppressed('bounce1@example.com')).toEqual(true);
    expect(bounceManager.isEmailSuppressed('bounce2@example.com')).toEqual(true);
    expect(bounceManager.isEmailSuppressed('notbounced@example.com')).toEqual(false);
    
    // Check suppression info
    const info1 = bounceManager.getSuppressionInfo('bounce1@example.com');
    expect(info1).toBeTruthy();
    expect(info1?.reason).toContain('Hard bounce');
    expect(info1?.expiresAt).toBeUndefined(); // Permanent
    
    const info2 = bounceManager.getSuppressionInfo('bounce2@example.com');
    expect(info2).toBeTruthy();
    expect(info2?.reason).toContain('Soft bounce');
    expect(info2?.expiresAt).toBeGreaterThan(Date.now());
  }
  
  // Clean up
  await plugins.fs.promises.rm(testDir, { recursive: true, force: true }).catch(() => {});
});

tap.test('Email Router Storage Integration', async () => {
  const testDir = plugins.path.join(paths.dataDir, '.test-integration-router');
  
  const testRoutes: IEmailRoute[] = [
    {
      name: 'test-route-1',
      match: { recipients: '*@test.com' },
      action: { type: 'forward', forward: { host: 'test.server.com', port: 25 } },
      priority: 100
    },
    {
      name: 'test-route-2',
      match: { senders: '*@internal.com' },
      action: { type: 'process', process: { scan: true, dkim: true } },
      priority: 50
    }
  ];
  
  // Phase 1: Save routes with storage
  {
    const storage = new StorageManager({ fsPath: testDir });
    const router = new EmailRouter([], {
      storageManager: storage,
      persistChanges: true
    });
    
    // Add routes
    await router.addRoute(testRoutes[0]);
    await router.addRoute(testRoutes[1]);
    
    // Verify routes
    const routes = router.getRoutes();
    expect(routes.length).toEqual(2);
    expect(routes[0].name).toEqual('test-route-1'); // Higher priority first
    expect(routes[1].name).toEqual('test-route-2');
  }
  
  // Phase 2: New instance should load routes from storage
  {
    const storage = new StorageManager({ fsPath: testDir });
    const router = new EmailRouter([], {
      storageManager: storage,
      persistChanges: true
    });
    
    // Wait for async load
    await new Promise(resolve => setTimeout(resolve, 100));
    
    // Manually load routes (since constructor load is fire-and-forget)
    await router.loadRoutes({ replace: true });
    
    // Verify persistence
    const routes = router.getRoutes();
    expect(routes.length).toEqual(2);
    expect(routes[0].name).toEqual('test-route-1');
    expect(routes[0].priority).toEqual(100);
    expect(routes[1].name).toEqual('test-route-2');
    expect(routes[1].priority).toEqual(50);
    
    // Test route retrieval
    const route1 = router.getRoute('test-route-1');
    expect(route1).toBeTruthy();
    expect(route1?.match.recipients).toEqual('*@test.com');
  }
  
  // Clean up
  await plugins.fs.promises.rm(testDir, { recursive: true, force: true }).catch(() => {});
});

tap.test('Storage Backend Switching', async () => {
  const testDir = plugins.path.join(paths.dataDir, '.test-integration-switching');
  const testData = { key: 'value', nested: { data: true } };
  
  // Phase 1: Start with memory storage
  const memoryStore = new Map<string, string>();
  {
    const storage = new StorageManager(); // Memory backend
    await storage.setJSON('/switch/test', testData);
    
    // Verify it's in memory
    expect(storage.getBackend()).toEqual('memory');
  }
  
  // Phase 2: Switch to custom backend
  {
    const storage = new StorageManager({
      readFunction: async (key) => memoryStore.get(key) || null,
      writeFunction: async (key, value) => { memoryStore.set(key, value); }
    });
    
    // Write data
    await storage.setJSON('/switch/test', testData);
    
    // Verify backend
    expect(storage.getBackend()).toEqual('custom');
    expect(memoryStore.has('/switch/test')).toEqual(true);
  }
  
  // Phase 3: Switch to filesystem
  {
    const storage = new StorageManager({ fsPath: testDir });
    
    // Migrate data from custom backend
    const dataStr = memoryStore.get('/switch/test');
    if (dataStr) {
      await storage.set('/switch/test', dataStr);
    }
    
    // Verify data migrated
    const data = await storage.getJSON('/switch/test');
    expect(data).toEqual(testData);
    expect(storage.getBackend()).toEqual('filesystem'); // fsPath is now properly reported as filesystem
  }
  
  // Clean up
  await plugins.fs.promises.rm(testDir, { recursive: true, force: true }).catch(() => {});
});

tap.test('Data Migration Between Backends', async () => {
  const testDir1 = plugins.path.join(paths.dataDir, '.test-migration-source');
  const testDir2 = plugins.path.join(paths.dataDir, '.test-migration-dest');
  
  // Create test data structure
  const testData = {
    '/config/app': JSON.stringify({ name: 'test-app', version: '1.0.0' }),
    '/config/database': JSON.stringify({ host: 'localhost', port: 5432 }),
    '/data/users/1': JSON.stringify({ id: 1, name: 'User One' }),
    '/data/users/2': JSON.stringify({ id: 2, name: 'User Two' }),
    '/logs/app.log': 'Log entry 1\nLog entry 2\nLog entry 3'
  };
  
  // Phase 1: Populate source storage
  {
    const source = new StorageManager({ fsPath: testDir1 });
    
    for (const [key, value] of Object.entries(testData)) {
      await source.set(key, value);
    }
    
    // Verify data written
    const keys = await source.list('/');
    expect(keys.length).toBeGreaterThanOrEqual(5);
  }
  
  // Phase 2: Migrate to destination
  {
    const source = new StorageManager({ fsPath: testDir1 });
    const dest = new StorageManager({ fsPath: testDir2 });
    
    // List all keys from source
    const allKeys = await source.list('/');
    
    // Migrate each key
    for (const key of allKeys) {
      const value = await source.get(key);
      if (value !== null) {
        await dest.set(key, value);
      }
    }
    
    // Verify migration
    for (const [key, expectedValue] of Object.entries(testData)) {
      const value = await dest.get(key);
      expect(value).toEqual(expectedValue);
    }
    
    // Verify structure preserved
    const configKeys = await dest.list('/config');
    expect(configKeys.length).toEqual(2);
    
    const userKeys = await dest.list('/data/users');
    expect(userKeys.length).toEqual(2);
  }
  
  // Clean up
  await plugins.fs.promises.rm(testDir1, { recursive: true, force: true }).catch(() => {});
  await plugins.fs.promises.rm(testDir2, { recursive: true, force: true }).catch(() => {});
});

export default tap.start();