import { describe, it, expect, beforeEach, vi } from 'vitest';
import { ProcessStore } from '../process-store.js';
import { ProcessDefinition, ProcessExecution } from '../types.js';
import { ConfigManager } from '../../../config/config-manager.js';
import { InMemoryFileSystemAdapter } from '../file-system-adapter.js';
import path from 'path';

describe('ProcessStore', () => {
  let store: ProcessStore;
  let mockConfigManager: vi.Mocked<ConfigManager>;
  let mockFs: InMemoryFileSystemAdapter;
  
  it('should create ProcessStore instance', () => {
    const config = {} as any;
    const testStore = new ProcessStore(config);
    expect(testStore).toBeInstanceOf(ProcessStore);
  });
  
  beforeEach(async () => {
    vi.clearAllMocks();
    
    mockConfigManager = {
      getStorageManager: vi.fn().mockReturnValue({
        getModuleDataPath: vi.fn().mockResolvedValue('/mock')
      })
    } as any;
    
    mockFs = new InMemoryFileSystemAdapter();
    store = new ProcessStore(mockConfigManager, mockFs);
  });
  
  describe('initialization', () => {
    it('should create data directory on init', async () => {
      await store.init();
      
      expect(mockFs.hasDirectory('/mock/processes')).toBe(true);
      expect(mockFs.hasDirectory('/mock/executions')).toBe(true);
      expect(mockFs.hasDirectory('/mock/templates')).toBe(true);
    });
  });
  
  describe('saveProcess', () => {
    it('should save process to file', async () => {
      await store.init();
      const process: ProcessDefinition = createTestProcess();
      
      await store.saveProcess(process);
      
      expect(mockFs.hasFile(`/mock/processes/${process.id}.json`)).toBe(true);
      const savedData = await mockFs.readFile(`/mock/processes/${process.id}.json`, 'utf-8');
      expect(JSON.parse(savedData)).toEqual(process);
    });
    
    it('should update cache after save', async () => {
      await store.init();
      const process = createTestProcess();
      
      await store.saveProcess(process);
      
      // Should retrieve from cache without reading file
      const retrieved = await store.getProcess(process.id);
      expect(retrieved).toEqual(process);
      
      // The file should have been written only once
      expect(mockFs.getFileCount()).toBe(1);
    });
  });
  
  describe('getProcess', () => {
    it('should retrieve process from file', async () => {
      await store.init();
      const process = createTestProcess();
      
      // Save the process first
      await mockFs.writeFile(`/mock/processes/${process.id}.json`, JSON.stringify(process));
      
      const retrieved = await store.getProcess(process.id);
      
      expect(retrieved).toEqual(process);
    });
    
    it('should return null for non-existent process', async () => {
      await store.init();
      // Don't create the file, so it doesn't exist
      
      const result = await store.getProcess('non-existent');
      
      expect(result).toBeNull();
    });
    
    it('should use cache for repeated reads', async () => {
      await store.init();
      const process = createTestProcess();
      
      // Save the process first
      await mockFs.writeFile(`/mock/processes/${process.id}.json`, JSON.stringify(process));
      
      // Clear the file to ensure cache is used
      const fileCountBefore = mockFs.getFileCount();
      
      // First read
      await store.getProcess(process.id);
      // Second read
      const cached = await store.getProcess(process.id);
      
      // Should get the same object from cache
      expect(cached).toEqual(process);
      // File count should remain the same
      expect(mockFs.getFileCount()).toBe(fileCountBefore);
    });
  });
  
  describe('getAllProcesses', () => {
    it('should return all processes', async () => {
      await store.init();
      const process1 = createTestProcess({ id: 'proc-1' });
      const process2 = createTestProcess({ id: 'proc-2' });
      
      // Save processes
      await mockFs.writeFile('/mock/processes/proc-1.json', JSON.stringify(process1));
      await mockFs.writeFile('/mock/processes/proc-2.json', JSON.stringify(process2));
      
      const processes = await store.getAllProcesses();
      
      expect(processes).toHaveLength(2);
      expect(processes).toContainEqual(process1);
      expect(processes).toContainEqual(process2);
    });
    
    it('should filter by persona', async () => {
      await store.init();
      const founderProcess = createTestProcess({ id: 'proc-1', persona: 'founder' });
      const engineerProcess = createTestProcess({ id: 'proc-2', persona: 'software-engineer' });
      
      // Save processes
      await mockFs.writeFile('/mock/processes/proc-1.json', JSON.stringify(founderProcess));
      await mockFs.writeFile('/mock/processes/proc-2.json', JSON.stringify(engineerProcess));
      
      const processes = await store.getAllProcesses({ persona: 'founder' });
      
      expect(processes).toHaveLength(1);
      expect(processes[0].persona).toBe('founder');
    });
    
    it('should filter by enabled triggers', async () => {
      await store.init();
      const processWithEnabled = createTestProcess({
        id: 'proc-1',
        triggers: [{ id: 't1', type: 'manual', name: 'Manual', enabled: true }]
      });
      const processWithDisabled = createTestProcess({
        id: 'proc-2',
        triggers: [{ id: 't2', type: 'manual', name: 'Manual', enabled: false }]
      });
      
      // Save processes
      await mockFs.writeFile('/mock/processes/proc-1.json', JSON.stringify(processWithEnabled));
      await mockFs.writeFile('/mock/processes/proc-2.json', JSON.stringify(processWithDisabled));
      
      const processes = await store.getAllProcesses({ hasEnabledTriggers: true });
      
      expect(processes).toHaveLength(1);
      expect(processes[0].id).toBe('proc-1');
    });
    
    it('should handle empty directory', async () => {
      await store.init();
      // Don't add any files
      
      const processes = await store.getAllProcesses();
      
      expect(processes).toEqual([]);
    });
  });
  
  describe('deleteProcess', () => {
    it('should delete process file', async () => {
      await store.init();
      const processId = 'proc-to-delete';
      
      // Save a process first
      await mockFs.writeFile(`/mock/processes/${processId}.json`, JSON.stringify(createTestProcess({ id: processId })));
      
      await store.deleteProcess(processId);
      
      expect(mockFs.hasFile(`/mock/processes/${processId}.json`)).toBe(false);
    });
    
    it('should remove from cache after delete', async () => {
      await store.init();
      const process = createTestProcess();
      
      // First save and cache it
      await store.saveProcess(process);
      
      // Verify it's cached
      const cached = await store.getProcess(process.id);
      expect(cached).toEqual(process);
      
      // Then delete
      await store.deleteProcess(process.id);
      
      // Try to get again - should return null
      const result = await store.getProcess(process.id);
      expect(result).toBeNull();
    });
  });
  
  describe('saveExecution', () => {
    it('should save execution to file', async () => {
      await store.init();
      const execution: ProcessExecution = createTestExecution();
      
      await store.saveExecution(execution);
      
      expect(mockFs.hasFile(`/mock/executions/${execution.processId}/${execution.id}.json`)).toBe(true);
      const savedData = await mockFs.readFile(`/mock/executions/${execution.processId}/${execution.id}.json`, 'utf-8');
      expect(JSON.parse(savedData)).toEqual(execution);
    });
    
    it('should create process execution directory', async () => {
      await store.init();
      const execution = createTestExecution();
      
      await store.saveExecution(execution);
      
      expect(mockFs.hasDirectory(`/mock/executions/${execution.processId}`)).toBe(true);
    });
  });
  
  describe('getExecution', () => {
    it('should retrieve execution from file', async () => {
      await store.init();
      const execution = createTestExecution();
      
      // Save the execution first
      await mockFs.mkdir(`/mock/executions/${execution.processId}`, { recursive: true });
      await mockFs.writeFile(
        `/mock/executions/${execution.processId}/${execution.id}.json`,
        JSON.stringify(execution)
      );
      
      const retrieved = await store.getExecution(execution.processId, execution.id);
      
      expect(retrieved).toEqual(execution);
    });
    
    it('should return null for non-existent execution', async () => {
      await store.init();
      
      const result = await store.getExecution('proc-1', 'exec-none');
      
      expect(result).toBeNull();
    });
  });
  
  describe('getProcessExecutions', () => {
    it('should return all executions for a process', async () => {
      await store.init();
      const processId = 'proc-1';
      const exec1 = createTestExecution({ id: 'exec-1', processId });
      const exec2 = createTestExecution({ id: 'exec-2', processId });
      
      // Save executions
      await mockFs.mkdir(`/mock/executions/${processId}`, { recursive: true });
      await mockFs.writeFile(`/mock/executions/${processId}/exec-1.json`, JSON.stringify(exec1));
      await mockFs.writeFile(`/mock/executions/${processId}/exec-2.json`, JSON.stringify(exec2));
      
      const executions = await store.getProcessExecutions(processId);
      
      expect(executions).toHaveLength(2);
      expect(executions).toContainEqual(exec1);
      expect(executions).toContainEqual(exec2);
    });
    
    it('should filter by status', async () => {
      await store.init();
      const processId = 'proc-1';
      const completedExec = createTestExecution({ 
        id: 'exec-1', 
        processId,
        status: 'completed' 
      });
      const failedExec = createTestExecution({ 
        id: 'exec-2', 
        processId,
        status: 'failed' 
      });
      
      // Save executions
      await mockFs.mkdir(`/mock/executions/${processId}`, { recursive: true });
      await mockFs.writeFile(`/mock/executions/${processId}/exec-1.json`, JSON.stringify(completedExec));
      await mockFs.writeFile(`/mock/executions/${processId}/exec-2.json`, JSON.stringify(failedExec));
      
      const executions = await store.getProcessExecutions(processId, {
        status: 'completed'
      });
      
      expect(executions).toHaveLength(1);
      expect(executions[0].status).toBe('completed');
    });
    
    it('should limit results', async () => {
      await store.init();
      const processId = 'proc-1';
      const executions = Array.from({ length: 5 }, (_, i) => 
        createTestExecution({ id: `exec-${i}`, processId })
      );
      
      // Save executions
      await mockFs.mkdir(`/mock/executions/${processId}`, { recursive: true });
      for (const exec of executions) {
        await mockFs.writeFile(`/mock/executions/${processId}/${exec.id}.json`, JSON.stringify(exec));
      }
      
      const results = await store.getProcessExecutions(processId, { limit: 3 });
      
      expect(results).toHaveLength(3);
    });
    
    it('should sort by date descending', async () => {
      await store.init();
      const processId = 'proc-1';
      const oldExec = createTestExecution({ 
        id: 'exec-old',
        processId,
        startedAt: '2024-01-01T00:00:00Z'
      });
      const newExec = createTestExecution({ 
        id: 'exec-new',
        processId,
        startedAt: '2024-01-02T00:00:00Z'
      });
      
      // Save executions
      await mockFs.mkdir(`/mock/executions/${processId}`, { recursive: true });
      await mockFs.writeFile(`/mock/executions/${processId}/exec-old.json`, JSON.stringify(oldExec));
      await mockFs.writeFile(`/mock/executions/${processId}/exec-new.json`, JSON.stringify(newExec));
      
      const executions = await store.getProcessExecutions(processId);
      
      expect(executions[0].id).toBe('exec-new');
      expect(executions[1].id).toBe('exec-old');
    });
  });
  
  describe('searchProcesses', () => {
    it('should search by name', async () => {
      await store.init();
      const processes = [
        createTestProcess({ id: 'p1', name: 'Daily Report Generator' }),
        createTestProcess({ id: 'p2', name: 'Weekly Summary' }),
        createTestProcess({ id: 'p3', name: 'Report Builder' })
      ];
      
      // Save processes
      for (const p of processes) {
        await mockFs.writeFile(`/mock/processes/${p.id}.json`, JSON.stringify(p));
      }
      
      const results = await store.searchProcesses('report');
      
      expect(results).toHaveLength(2);
      expect(results.map(p => p.id).sort()).toEqual(['p1', 'p3']);
    });
    
    it('should search by description', async () => {
      await store.init();
      const processes = [
        createTestProcess({ 
          id: 'p1', 
          name: 'Process 1',
          description: 'Generates financial reports'
        }),
        createTestProcess({ 
          id: 'p2', 
          name: 'Process 2',
          description: 'Sends email notifications'
        })
      ];
      
      // Save processes
      for (const p of processes) {
        await mockFs.writeFile(`/mock/processes/${p.id}.json`, JSON.stringify(p));
      }
      
      const results = await store.searchProcesses('financial');
      
      expect(results).toHaveLength(1);
      expect(results[0].id).toBe('p1');
    });
    
    it('should be case insensitive', async () => {
      await store.init();
      const process = createTestProcess({ 
        id: 'p1', 
        name: 'UPPERCASE Process'
      });
      
      await mockFs.writeFile(`/mock/processes/${process.id}.json`, JSON.stringify(process));
      
      const results = await store.searchProcesses('uppercase');
      
      expect(results).toHaveLength(1);
    });
  });
  
  describe('getStats', () => {
    it('should return process statistics', async () => {
      await store.init();
      const processes = [
        createTestProcess({ 
          id: 'p1',
          persona: 'founder',
          triggers: [
            { id: 't1', type: 'schedule', name: 'Daily', enabled: true, config: { cron: '0 9 * * *' } }
          ]
        }),
        createTestProcess({ 
          id: 'p2',
          persona: 'founder',
          triggers: [
            { id: 't2', type: 'manual', name: 'Manual', enabled: false }
          ]
        }),
        createTestProcess({ 
          id: 'p3',
          persona: 'software-engineer',
          triggers: [
            { id: 't3', type: 'event', name: 'Event', enabled: true, config: { eventType: 'test' } }
          ]
        })
      ];
      
      // Save processes
      for (const p of processes) {
        await mockFs.writeFile(`/mock/processes/${p.id}.json`, JSON.stringify(p));
      }
      
      const stats = await store.getStats();
      
      expect(stats).toEqual({
        totalProcesses: 3,
        byPersona: {
          founder: 2,
          'software-engineer': 1
        },
        byTriggerType: {
          schedule: 1,
          manual: 1,
          event: 1
        },
        enabledTriggers: 2,
        totalTriggers: 3
      });
    });
  });
});

// Helper functions
function createTestProcess(overrides: Partial<ProcessDefinition> = {}): ProcessDefinition {
  return {
    id: 'process-test-123',
    name: 'Test Process',
    version: '1.0.0',
    triggers: [],
    activities: [],
    variables: {},
    metadata: {
      createdAt: new Date().toISOString(),
      updatedAt: new Date().toISOString(),
      executionCount: 0
    },
    ...overrides
  };
}

function createTestExecution(overrides: Partial<ProcessExecution> = {}): ProcessExecution {
  return {
    id: 'exec-test-123',
    processId: 'process-123',
    processVersion: '1.0.0',
    status: 'completed',
    triggeredBy: 'manual',
    startedAt: new Date().toISOString(),
    completedAt: new Date().toISOString(),
    variables: {},
    activityResults: [],
    logs: [],
    ...overrides
  };
}