import { describe, expect, it, beforeEach, afterEach, vi } from 'vitest';
import { promises as fs } from 'fs';
import { join } from 'path';
import { tmpdir } from 'os';
import { FileStorage } from '../file-storage.js';
import { SessionError } from '../../errors.js';
import { createTestSnapshot, createTestManifest, createTestScope, createTestSnapshots, } from '../../__fixtures__/mock-storage-provider.js';
const SCOPE_ID = 'test-agent';
describe('FileStorage', () => {
    let storage;
    let testDir;
    beforeEach(async () => {
        testDir = join(tmpdir(), `file-storage-test-${Date.now()}-${Math.random().toString(36).slice(2)}`);
        await fs.mkdir(testDir, { recursive: true });
        storage = new FileStorage(testDir);
    });
    afterEach(async () => {
        try {
            await fs.rm(testDir, { recursive: true, force: true });
        }
        catch {
            // Ignore cleanup errors
        }
    });
    describe('saveSnapshot', () => {
        describe('FileSnapshotStorage_When_saveSnapshot_Then_CreatesFiles', () => {
            it('saves snapshot to history file', async () => {
                const location = { sessionId: 'test-session', scope: createTestScope(), scopeId: SCOPE_ID };
                const snapshot = createTestSnapshot();
                await storage.saveSnapshot({ location, snapshotId: '1', isLatest: false, snapshot });
                const historyPath = join(testDir, location.sessionId, 'scopes', 'agent', SCOPE_ID, 'snapshots', 'immutable_history', 'snapshot_1.json');
                const content = await fs.readFile(historyPath, 'utf8');
                expect(JSON.parse(content)).toEqual(snapshot);
            });
            it('saves snapshot as latest when isLatest is true', async () => {
                const location = { sessionId: 'test-session', scope: createTestScope(), scopeId: SCOPE_ID };
                const snapshot = createTestSnapshot();
                await storage.saveSnapshot({ location, snapshotId: '1', isLatest: true, snapshot });
                const latestPath = join(testDir, location.sessionId, 'scopes', 'agent', SCOPE_ID, 'snapshots', 'snapshot_latest.json');
                const content = await fs.readFile(latestPath, 'utf8');
                expect(JSON.parse(content)).toEqual(snapshot);
            });
            it('creates directories recursively', async () => {
                const location = {
                    sessionId: 'new-session',
                    scope: createTestScope('agent'),
                    scopeId: 'new-agent',
                };
                const snapshot = createTestSnapshot();
                await storage.saveSnapshot({ location, snapshotId: '1', isLatest: true, snapshot });
                const expectedDir = join(testDir, location.sessionId, 'scopes', 'agent', location.scopeId, 'snapshots');
                const stats = await fs.stat(expectedDir);
                expect(stats.isDirectory()).toBe(true);
            });
        });
        describe('FileSnapshotStorage_When_saveSnapshotFails_Then_ThrowsSessionError', () => {
            it('throws SessionError when write fails', async () => {
                const location = { sessionId: 'test-session', scope: createTestScope(), scopeId: SCOPE_ID };
                const snapshot = createTestSnapshot();
                vi.spyOn(fs, 'writeFile').mockRejectedValueOnce(new Error('Write failed'));
                await expect(storage.saveSnapshot({ location, snapshotId: '1', isLatest: false, snapshot })).rejects.toThrow(SessionError);
            });
        });
        describe('FileSnapshotStorage_When_MultiAgentScope_Then_SavesCorrectly', () => {
            it('saves multi-agent snapshot to correct path', async () => {
                const location = {
                    sessionId: 'multi-session',
                    scope: createTestScope('multiAgent'),
                    scopeId: 'graph-1',
                };
                const snapshot = createTestSnapshot({ scope: 'multiAgent' });
                await storage.saveSnapshot({ location, snapshotId: '1', isLatest: true, snapshot });
                const expectedPath = join(testDir, location.sessionId, 'scopes', 'multiAgent', location.scopeId, 'snapshots', 'snapshot_latest.json');
                const content = await fs.readFile(expectedPath, 'utf8');
                expect(JSON.parse(content)).toEqual(snapshot);
            });
        });
    });
    describe('loadSnapshot', () => {
        describe('FileSnapshotStorage_When_LoadLatestSnapshot_Then_ReturnsSnapshot', () => {
            it('loads latest snapshot when snapshotId is undefined', async () => {
                const location = { sessionId: 'test-session', scope: createTestScope(), scopeId: SCOPE_ID };
                const snapshot = createTestSnapshot();
                await storage.saveSnapshot({ location, snapshotId: '1', isLatest: true, snapshot });
                const result = await storage.loadSnapshot({ location });
                expect(result).toEqual(snapshot);
            });
        });
        describe('FileSnapshotStorage_When_LoadSpecificSnapshot_Then_ReturnsSnapshot', () => {
            it('loads specific snapshot by ID', async () => {
                const location = { sessionId: 'test-session', scope: createTestScope(), scopeId: SCOPE_ID };
                const snapshot = createTestSnapshot();
                await storage.saveSnapshot({ location, snapshotId: '5', isLatest: false, snapshot });
                const result = await storage.loadSnapshot({ location, snapshotId: '5' });
                expect(result).toEqual(snapshot);
            });
        });
        describe('FileSnapshotStorage_When_SnapshotNotFound_Then_ReturnsNull', () => {
            it('returns null when snapshot file does not exist', async () => {
                const result = await storage.loadSnapshot({
                    location: { sessionId: 'nonexistent', scope: 'agent', scopeId: SCOPE_ID },
                });
                expect(result).toBeNull();
            });
        });
        describe('FileSnapshotStorage_When_InvalidJSON_Then_ThrowsSessionError', () => {
            it('throws SessionError when JSON is invalid', async () => {
                const sessionId = 'test-session';
                const filePath = join(testDir, sessionId, 'scopes', 'agent', SCOPE_ID, 'snapshots', 'snapshot_latest.json');
                await fs.mkdir(join(testDir, sessionId, 'scopes', 'agent', SCOPE_ID, 'snapshots'), { recursive: true });
                await fs.writeFile(filePath, 'invalid json', 'utf8');
                await expect(storage.loadSnapshot({ location: { sessionId, scope: 'agent', scopeId: SCOPE_ID } })).rejects.toThrow(SessionError);
            });
        });
        describe('FileSnapshotStorage_When_ReadError_Then_ThrowsSessionError', () => {
            it('throws SessionError when file read fails', async () => {
                vi.spyOn(fs, 'readFile').mockRejectedValueOnce(new Error('Permission denied'));
                await expect(storage.loadSnapshot({ location: { sessionId: 'test-session', scope: 'agent', scopeId: SCOPE_ID } })).rejects.toThrow(SessionError);
            });
        });
    });
    describe('listSnapshotIds', () => {
        describe('FileSnapshotStorage_When_listSnapshots_Then_ReturnsOrderedIds', () => {
            it('returns sorted snapshot IDs', async () => {
                const location = { sessionId: 'test-session', scope: createTestScope(), scopeId: SCOPE_ID };
                const snapshots = createTestSnapshots(3);
                const ids = [
                    '019c9bf1-14e5-7eef-96fb-cc07ae54210f',
                    '019c9bf1-1d34-7eef-96fb-d1be20fd7bbd',
                    '019c9bf1-24bb-7eef-96fb-ddcc943cd859',
                ];
                await storage.saveSnapshot({ location, snapshotId: ids[2], isLatest: false, snapshot: snapshots[2] });
                await storage.saveSnapshot({ location, snapshotId: ids[0], isLatest: false, snapshot: snapshots[0] });
                await storage.saveSnapshot({ location, snapshotId: ids[1], isLatest: false, snapshot: snapshots[1] });
                const result = await storage.listSnapshotIds({ location });
                expect(result).toEqual(ids);
            });
            it('ignores non-snapshot files', async () => {
                const location = { sessionId: 'test-session', scope: createTestScope(), scopeId: SCOPE_ID };
                const snapshot = createTestSnapshot();
                const id = '019c9bf1-14e5-7eef-96fb-cc07ae54210f';
                await storage.saveSnapshot({ location, snapshotId: id, isLatest: false, snapshot });
                const historyDir = join(testDir, location.sessionId, 'scopes', 'agent', SCOPE_ID, 'snapshots', 'immutable_history');
                await fs.writeFile(join(historyDir, 'other-file.txt'), 'not a snapshot', 'utf8');
                const result = await storage.listSnapshotIds({ location });
                expect(result).toEqual([id]);
            });
            it('filters by startAfter for pagination', async () => {
                const location = { sessionId: 'test-session', scope: createTestScope(), scopeId: SCOPE_ID };
                const snapshots = createTestSnapshots(3);
                const ids = [
                    '019c9bf1-14e5-7eef-96fb-cc07ae54210f',
                    '019c9bf1-1d34-7eef-96fb-d1be20fd7bbd',
                    '019c9bf1-24bb-7eef-96fb-ddcc943cd859',
                ];
                for (let i = 0; i < ids.length; i++) {
                    await storage.saveSnapshot({ location, snapshotId: ids[i], isLatest: false, snapshot: snapshots[i] });
                }
                const result = await storage.listSnapshotIds({ location, startAfter: ids[0] });
                expect(result).toEqual([ids[1], ids[2]]);
            });
            it('limits results when limit is provided', async () => {
                const location = { sessionId: 'test-session', scope: createTestScope(), scopeId: SCOPE_ID };
                const snapshots = createTestSnapshots(3);
                const ids = [
                    '019c9bf1-14e5-7eef-96fb-cc07ae54210f',
                    '019c9bf1-1d34-7eef-96fb-d1be20fd7bbd',
                    '019c9bf1-24bb-7eef-96fb-ddcc943cd859',
                ];
                for (let i = 0; i < ids.length; i++) {
                    await storage.saveSnapshot({ location, snapshotId: ids[i], isLatest: false, snapshot: snapshots[i] });
                }
                const result = await storage.listSnapshotIds({ location, limit: 2 });
                expect(result).toEqual([ids[0], ids[1]]);
            });
            it('combines startAfter and limit', async () => {
                const location = { sessionId: 'test-session', scope: createTestScope(), scopeId: SCOPE_ID };
                const snapshots = createTestSnapshots(3);
                const ids = [
                    '019c9bf1-14e5-7eef-96fb-cc07ae54210f',
                    '019c9bf1-1d34-7eef-96fb-d1be20fd7bbd',
                    '019c9bf1-24bb-7eef-96fb-ddcc943cd859',
                ];
                for (let i = 0; i < ids.length; i++) {
                    await storage.saveSnapshot({ location, snapshotId: ids[i], isLatest: false, snapshot: snapshots[i] });
                }
                const result = await storage.listSnapshotIds({ location, startAfter: ids[0], limit: 1 });
                expect(result).toEqual([ids[1]]);
            });
        });
        describe('FileSnapshotStorage_When_DirectoryNotFound_Then_ReturnsEmptyArray', () => {
            it('returns empty array when directory does not exist', async () => {
                const result = await storage.listSnapshotIds({
                    location: { sessionId: 'nonexistent', scope: 'agent', scopeId: SCOPE_ID },
                });
                expect(result).toEqual([]);
            });
        });
        describe('FileSnapshotStorage_When_ReadDirFails_Then_ThrowsSessionError', () => {
            it('throws SessionError when readdir fails with non-ENOENT error', async () => {
                vi.spyOn(fs, 'readdir').mockRejectedValueOnce(new Error('Permission denied'));
                await expect(storage.listSnapshotIds({ location: { sessionId: 'test-session', scope: 'agent', scopeId: SCOPE_ID } })).rejects.toThrow(SessionError);
            });
        });
    });
    describe('deleteSession', () => {
        describe('FileSnapshotStorage_When_DeleteSession_Then_RemovesDirectory', () => {
            it('removes the entire session directory', async () => {
                const location = { sessionId: 'test-session', scope: createTestScope(), scopeId: SCOPE_ID };
                await storage.saveSnapshot({ location, snapshotId: '1', isLatest: true, snapshot: createTestSnapshot() });
                await storage.deleteSession({ sessionId: 'test-session' });
                await expect(fs.stat(join(testDir, 'test-session'))).rejects.toMatchObject({ code: 'ENOENT' });
            });
            it('no-ops when session directory does not exist', async () => {
                await expect(storage.deleteSession({ sessionId: 'nonexistent-session' })).resolves.toBeUndefined();
            });
        });
        describe('FileSnapshotStorage_When_DeleteSessionFails_Then_ThrowsSessionError', () => {
            it('throws SessionError when rm fails', async () => {
                vi.spyOn(fs, 'rm').mockRejectedValueOnce(new Error('Permission denied'));
                await expect(storage.deleteSession({ sessionId: 'test-session' })).rejects.toThrow(SessionError);
            });
        });
    });
    describe('saveManifest', () => {
        describe('FileSnapshotStorage_When_SaveManifest_Then_CreatesFile', () => {
            it('saves manifest to correct path', async () => {
                const location = { sessionId: 'test-session', scope: 'agent', scopeId: SCOPE_ID };
                const manifest = createTestManifest();
                await storage.saveManifest({ location, manifest });
                const manifestPath = join(testDir, location.sessionId, 'scopes', 'agent', SCOPE_ID, 'snapshots', 'manifest.json');
                const content = await fs.readFile(manifestPath, 'utf8');
                expect(JSON.parse(content)).toEqual(manifest);
            });
        });
        describe('FileSnapshotStorage_When_SaveManifestFails_Then_ThrowsSessionError', () => {
            it('throws SessionError when write fails', async () => {
                vi.spyOn(fs, 'writeFile').mockRejectedValueOnce(new Error('Write failed'));
                await expect(storage.saveManifest({
                    location: { sessionId: 'test-session', scope: 'agent', scopeId: SCOPE_ID },
                    manifest: createTestManifest(),
                })).rejects.toThrow(SessionError);
            });
        });
    });
    describe('loadManifest', () => {
        describe('FileSnapshotStorage_When_LoadManifest_Then_ReturnsManifest', () => {
            it('loads manifest from file', async () => {
                const location = { sessionId: 'test-session', scope: 'agent', scopeId: SCOPE_ID };
                const manifest = createTestManifest();
                await storage.saveManifest({ location, manifest });
                const result = await storage.loadManifest({ location });
                expect(result).toEqual(manifest);
            });
        });
        describe('FileSnapshotStorage_When_ManifestNotFound_Then_ReturnsDefault', () => {
            it('returns default manifest when manifest file does not exist', async () => {
                const result = await storage.loadManifest({
                    location: { sessionId: 'nonexistent', scope: 'agent', scopeId: SCOPE_ID },
                });
                expect(result).toEqual({
                    schemaVersion: '1.0',
                    updatedAt: expect.any(String),
                });
            });
        });
        describe('FileSnapshotStorage_When_InvalidManifestJSON_Then_ThrowsSessionError', () => {
            it('throws SessionError when JSON is invalid', async () => {
                const sessionId = 'test-session';
                const filePath = join(testDir, sessionId, 'scopes', 'agent', SCOPE_ID, 'snapshots', 'manifest.json');
                await fs.mkdir(join(testDir, sessionId, 'scopes', 'agent', SCOPE_ID, 'snapshots'), { recursive: true });
                await fs.writeFile(filePath, 'invalid json', 'utf8');
                await expect(storage.loadManifest({ location: { sessionId, scope: 'agent', scopeId: SCOPE_ID } })).rejects.toThrow(SessionError);
            });
        });
    });
});
//# sourceMappingURL=file-storage.test.node.js.map