import { describe, it, expect, beforeEach, vi } from 'vitest';
import { WorkspaceDataStore } from '../store.js';
import { ConfigManager } from '../../../config/config-manager.js';
import { Workspace, Repository } from '../types.js';
import { InMemoryFileSystemAdapter } from '../file-system-adapter.js';

vi.mock('../../../config/config-manager.js');

const MockConfigManager = ConfigManager as vi.MockedClass<typeof ConfigManager>;

describe('WorkspaceDataStore', () => {
  let workspaceStore: WorkspaceDataStore;
  let mockConfigManager: vi.Mocked<ConfigManager>;
  let mockFs: InMemoryFileSystemAdapter;

  beforeEach(async () => {
    vi.clearAllMocks();

    mockConfigManager = new MockConfigManager() as vi.Mocked<ConfigManager>;
    mockConfigManager.getDataPath = vi.fn().mockReturnValue('/test/data/workspace');
    mockConfigManager.getStorageManager = vi.fn().mockReturnValue({
      ensureStorageDirectories: vi.fn().mockResolvedValue(undefined),
      loadData: vi.fn().mockResolvedValue(null),
      saveData: vi.fn().mockResolvedValue(undefined),
      getModuleDataPath: vi.fn().mockResolvedValue('/test/data/workspace/workspaces.json')
    });

    mockFs = new InMemoryFileSystemAdapter();
    workspaceStore = new WorkspaceDataStore(mockConfigManager, mockFs);
    await workspaceStore.init();
  });

  describe('initialization', () => {
    it('should initialize with empty data when file does not exist', async () => {
      const newStore = new WorkspaceDataStore(mockConfigManager, mockFs);
      await newStore.init();

      expect(newStore.getWorkspaces()).toEqual([]);
      expect(mockConfigManager.getStorageManager().saveData).toHaveBeenCalledWith(
        'workspace',
        'workspace.json',
        expect.objectContaining({ workspaces: {} })
      );
    });

    it('should load existing workspace data', async () => {
      const existingData = {
        workspaces: {
          'ws-1': {
            id: 'ws-1',
            name: 'Test Workspace',
            rootPath: '/test/workspace',
            repositories: [],
            createdAt: new Date().toISOString(),
            updatedAt: new Date().toISOString(),
          },
        },
        activeWorkspaceId: 'ws-1',
      };

      const newConfigManager = new MockConfigManager() as vi.Mocked<ConfigManager>;
      newConfigManager.getStorageManager = vi.fn().mockReturnValue({
        ensureStorageDirectories: vi.fn().mockResolvedValue(undefined),
        loadData: vi.fn().mockResolvedValue(existingData),
        saveData: vi.fn().mockResolvedValue(undefined),
      });

      const newStore = new WorkspaceDataStore(newConfigManager, mockFs);
      await newStore.init();

      expect(newStore.getWorkspaces()).toHaveLength(1);
      expect(newStore.getActiveWorkspace()?.name).toBe('Test Workspace');
    });
  });

  describe('createWorkspace', () => {
    it('should create a new workspace', () => {
      const workspace = workspaceStore.createWorkspace(
        'My Workspace',
        '/path/to/workspace',
        'Test workspace description'
      );

      expect(workspace.name).toBe('My Workspace');
      expect(workspace.rootPath).toBe('/path/to/workspace');
      expect(workspace.description).toBe('Test workspace description');
      expect(workspace.repositories).toEqual([]);
      expect(workspace.id).toMatch(/^ws-[a-f0-9-]+$/);
      expect(workspace.createdAt).toBeDefined();
      expect(workspace.updatedAt).toBeDefined();
    });

    it('should set first workspace as active automatically', () => {
      const workspace = workspaceStore.createWorkspace('First Workspace', '/path');

      expect(workspaceStore.getActiveWorkspace()).toBe(workspace);
    });

    it('should not change active workspace when creating additional workspaces', () => {
      const firstWorkspace = workspaceStore.createWorkspace('First', '/path1');
      const secondWorkspace = workspaceStore.createWorkspace('Second', '/path2');

      expect(workspaceStore.getActiveWorkspace()).toBe(firstWorkspace);
      expect(workspaceStore.getActiveWorkspace()).not.toBe(secondWorkspace);
    });
  });

  describe('addRepository', () => {
    let workspace: Workspace;

    beforeEach(() => {
      workspace = workspaceStore.createWorkspace('Test Workspace', '/workspace');
    });

    it('should add repository to workspace by workspace ID', () => {
      const repoData = {
        name: 'frontend',
        path: '/workspace/frontend',
        type: 'git' as const,
        primary: true,
      };

      const repository = workspaceStore.addRepository(workspace.id, repoData);

      expect(repository).toBeDefined();
      expect(repository!.name).toBe('frontend');
      expect(repository!.primary).toBe(true);
      expect(repository!.id).toMatch(/^repo-[a-f0-9-]+$/);
      expect(workspace.repositories).toHaveLength(1);
    });

    it('should add repository to workspace by workspace name', () => {
      const repoData = {
        name: 'backend',
        path: '/workspace/backend',
        type: 'git' as const,
      };

      const repository = workspaceStore.addRepository(workspace.name, repoData);

      expect(repository).toBeDefined();
      expect(repository!.name).toBe('backend');
      expect(repository!.primary).toBeFalsy();
    });

    it('should return null if workspace not found', () => {
      const repoData = {
        name: 'repo',
        path: '/path',
        type: 'git' as const,
      };

      const repository = workspaceStore.addRepository('nonexistent', repoData);

      expect(repository).toBeNull();
    });

    it('should update workspace updatedAt timestamp', async () => {
      const originalUpdatedAt = workspace.updatedAt;
      
      // Wait a bit to ensure timestamp difference
      await new Promise(resolve => setTimeout(resolve, 10));
      
      workspaceStore.addRepository(workspace.id, {
        name: 'repo',
        path: '/path',
        type: 'git' as const,
      });
      
      // Get the workspace again to check updated timestamp
      const updatedWorkspace = workspaceStore.getWorkspace(workspace.id);
      expect(updatedWorkspace?.updatedAt).not.toBe(originalUpdatedAt);
    });
  });

  describe('detectRepositories', () => {
    it('should detect Git repositories in directory', async () => {
      // Create a fresh instance with fresh mockFs
      const freshMockFs = new InMemoryFileSystemAdapter();
      const freshStore = new WorkspaceDataStore(mockConfigManager, freshMockFs);
      await freshStore.init();
      
      const workspacePath = '/workspace';
      
      // Set up directory structure
      freshMockFs.addDirectory(workspacePath, [
        { name: 'frontend', isDirectory: true },
        { name: 'backend', isDirectory: true },
        { name: 'shared', isDirectory: true },
        { name: 'node_modules', isDirectory: true },
        { name: '.git', isDirectory: true },
      ]);
      
      // Add .git directories to frontend and backend
      freshMockFs.addGitDirectory('/workspace/frontend');
      freshMockFs.addGitDirectory('/workspace/backend');

      const repositories = await freshStore.detectRepositories(workspacePath);

      expect(repositories).toHaveLength(2);
      expect(repositories[0].name).toBe('frontend');
      expect(repositories[0].path).toBe('/workspace/frontend');
      expect(repositories[0].type).toBe('git');
      expect(repositories[1].name).toBe('backend');
    });

    it('should handle directories without Git repositories', async () => {
      // Create a fresh instance with fresh mockFs
      const freshMockFs = new InMemoryFileSystemAdapter();
      const freshStore = new WorkspaceDataStore(mockConfigManager, freshMockFs);
      await freshStore.init();
      
      // Set up directory structure without .git directories
      freshMockFs.addDirectory('/workspace', [
        { name: 'folder1', isDirectory: true },
        { name: 'folder2', isDirectory: true }
      ]);
      
      // Don't add any .git directories

      const repositories = await freshStore.detectRepositories('/workspace');

      expect(repositories).toHaveLength(0);
    });

    it('should handle file system errors gracefully', async () => {
      // Create a fresh mockFs that will throw an error
      const errorMockFs = new InMemoryFileSystemAdapter();
      const errorStore = new WorkspaceDataStore(mockConfigManager, errorMockFs);
      await errorStore.init();
      
      // Don't add the directory, so readdir will throw

      const repositories = await errorStore.detectRepositories('/workspace');

      expect(repositories).toHaveLength(0);
    });
  });

  describe('workspace management', () => {
    let workspace1: Workspace;
    let workspace2: Workspace;

    beforeEach(() => {
      workspace1 = workspaceStore.createWorkspace('Workspace 1', '/ws1');
      workspace2 = workspaceStore.createWorkspace('Workspace 2', '/ws2');
    });

    it('should get workspace by name', () => {
      const found = workspaceStore.getWorkspace('Workspace 1');
      expect(found).toBe(workspace1);
    });

    it('should get workspace by ID', () => {
      const found = workspaceStore.getWorkspace(workspace2.id);
      expect(found).toBe(workspace2);
    });

    it('should return undefined for nonexistent workspace', () => {
      const found = workspaceStore.getWorkspace('Nonexistent');
      expect(found).toBeUndefined();
    });

    it('should get all workspaces', () => {
      const workspaces = workspaceStore.getWorkspaces();
      expect(workspaces).toHaveLength(2);
      expect(workspaces).toContain(workspace1);
      expect(workspaces).toContain(workspace2);
    });

    it('should set active workspace', () => {
      const success = workspaceStore.setActiveWorkspace('Workspace 2');
      expect(success).toBe(true);
      expect(workspaceStore.getActiveWorkspace()).toBe(workspace2);
    });

    it('should return false when setting nonexistent workspace as active', () => {
      const success = workspaceStore.setActiveWorkspace('Nonexistent');
      expect(success).toBe(false);
      expect(workspaceStore.getActiveWorkspace()).toBe(workspace1); // Should remain unchanged
    });
  });

  describe('repository queries', () => {
    let workspace: Workspace;

    beforeEach(() => {
      workspace = workspaceStore.createWorkspace('Test Workspace', '/workspace');
      
      workspaceStore.addRepository(workspace.id, {
        name: 'primary-repo',
        path: '/workspace/primary',
        type: 'git',
        primary: true,
      });

      workspaceStore.addRepository(workspace.id, {
        name: 'secondary-repo',
        path: '/workspace/secondary',
        type: 'git',
      });
    });

    it('should find repository by name', () => {
      const repo = workspaceStore.findRepository('primary-repo');
      expect(repo).toBeDefined();
      expect(repo!.name).toBe('primary-repo');
      expect(repo!.primary).toBe(true);
    });

    it('should find repository by path', () => {
      const repo = workspaceStore.findRepositoryByPath('/workspace/secondary');
      expect(repo).toBeDefined();
      expect(repo!.name).toBe('secondary-repo');
    });

    it('should get primary repository', () => {
      const primaryRepo = workspaceStore.getPrimaryRepository();
      expect(primaryRepo).toBeDefined();
      expect(primaryRepo!.name).toBe('primary-repo');
      expect(primaryRepo!.primary).toBe(true);
    });

    it('should return undefined for nonexistent repository', () => {
      const repo = workspaceStore.findRepository('nonexistent');
      expect(repo).toBeUndefined();
    });
  });

  describe('data persistence', () => {
    it('should save workspace data to file', async () => {
      workspaceStore.createWorkspace('Test Save', '/test');
      
      await workspaceStore.save();

      expect(mockConfigManager.getStorageManager().saveData).toHaveBeenCalledWith(
        'workspace',
        'workspace.json',
        expect.objectContaining({
          workspaces: expect.objectContaining({})
        })
      );
    });

    it('should handle save errors gracefully', async () => {
      mockConfigManager.getStorageManager().saveData = vi.fn()
        .mockRejectedValueOnce(new Error('Write failed'));

      await expect(workspaceStore.save()).rejects.toThrow('Write failed');
    });

    it('should preserve data through save/load cycle', async () => {
      const workspace = workspaceStore.createWorkspace('Persistence Test', '/test');
      workspaceStore.addRepository(workspace.id, {
        name: 'test-repo',
        path: '/test/repo',
        type: 'git',
        primary: true,
      });

      // Save data
      await workspaceStore.save();

      // Get the saved data from mock
      const savedData = (mockConfigManager.getStorageManager().saveData as vi.Mock)
        .mock.calls[0][2]; // Third argument is the data

      // Create new store with the saved data
      const newConfigManager = new MockConfigManager() as vi.Mocked<ConfigManager>;
      newConfigManager.getStorageManager = vi.fn().mockReturnValue({
        ensureStorageDirectories: vi.fn().mockResolvedValue(undefined),
        loadData: vi.fn().mockResolvedValue(savedData),
        saveData: vi.fn().mockResolvedValue(undefined),
      });
      
      const newStore = new WorkspaceDataStore(newConfigManager, mockFs);
      await newStore.init();

      // Verify data was preserved
      const loadedWorkspace = newStore.getWorkspace('Persistence Test');
      expect(loadedWorkspace).toBeDefined();
      expect(loadedWorkspace!.repositories).toHaveLength(1);
      expect(loadedWorkspace!.repositories[0].name).toBe('test-repo');
    });
  });
});