/**
 * State Manager Tests
 *
 * Tests the thread-safe state management with file locking
 */

const StateManager = require('../state-manager');
const fs = require('fs').promises;
const path = require('path');
const os = require('os');

// Dedicated temp subdirectory avoids cross-test file collisions
const testDir = path.join(os.tmpdir(), `state-manager-test-${Date.now()}`);
const testFile = path.join(testDir, 'test-state.json');

describe('StateManager', () => {
  let stateManager;

  beforeAll(async () => {
    await fs.mkdir(testDir, { recursive: true });
  });

  beforeEach(async () => {
    // Clean up test file + stale lockfiles
    for (const f of [testFile, `${testFile}.lock`]) {
      try { await fs.unlink(f); } catch (e) { /* ignore */ }
    }

    stateManager = new StateManager(testFile, {
      lockRetries: 20,
      lockRetryInterval: 50,
      lockTimeout: 15000
    });
  });

  afterEach(async () => {
    for (const f of [testFile, `${testFile}.lock`]) {
      try { await fs.unlink(f); } catch (e) { /* ignore */ }
    }
  });

  afterAll(async () => {
    try { await fs.rm(testDir, { recursive: true }); } catch (e) { /* ignore */ }
  });

  describe('Basic Operations', () => {
    test('creates file with empty array if not exists', async () => {
      const data = await stateManager.read();
      expect(Array.isArray(data)).toBe(true);
      expect(data.length).toBe(0);
    });

    test('write and read roundtrip', async () => {
      const testData = [
        { id: '1', name: 'Test Service 1' },
        { id: '2', name: 'Test Service 2' }
      ];

      await stateManager.write(testData);
      const data = await stateManager.read();

      expect(data).toEqual(testData);
    });

    test('update with callback function', async () => {
      await stateManager.write([{ id: '1', name: 'Service 1' }]);

      const updated = await stateManager.update(items => {
        items.push({ id: '2', name: 'Service 2' });
        return items;
      });

      expect(updated.length).toBe(2);
      expect(updated[1].name).toBe('Service 2');
    });
  });

  describe('Convenience Methods', () => {
    test('addItem adds to array', async () => {
      await stateManager.addItem({ id: '1', name: 'Service 1' });
      await stateManager.addItem({ id: '2', name: 'Service 2' });

      const items = await stateManager.read();
      expect(items.length).toBe(2);
    });

    test('removeItem removes by ID', async () => {
      await stateManager.write([
        { id: '1', name: 'Service 1' },
        { id: '2', name: 'Service 2' },
        { id: '3', name: 'Service 3' }
      ]);

      await stateManager.removeItem('2');

      const items = await stateManager.read();
      expect(items.length).toBe(2);
      expect(items.find(i => i.id === '2')).toBeUndefined();
    });

    test('updateItem updates by ID', async () => {
      await stateManager.write([
        { id: '1', name: 'Service 1', status: 'offline' }
      ]);

      await stateManager.updateItem('1', { status: 'online' });

      const item = await stateManager.findItem('1');
      expect(item.status).toBe('online');
      expect(item.name).toBe('Service 1'); // Unchanged
    });

    test('findItem returns null for non-existent ID', async () => {
      await stateManager.write([{ id: '1', name: 'Service 1' }]);

      const item = await stateManager.findItem('999');
      expect(item).toBeNull();
    });
  });

  describe('Concurrent Access', () => {
    test('concurrent writes do not corrupt data', async () => {
      // Start with empty array
      await stateManager.write([]);

      // Simulate 10 concurrent writes
      const promises = [];
      for (let i = 0; i < 10; i++) {
        promises.push(
          stateManager.update(items => {
            items.push({ id: `service-${i}`, name: `Service ${i}` });
            return items;
          })
        );
      }

      await Promise.all(promises);

      // Verify all items were added
      const items = await stateManager.read();
      expect(items.length).toBe(10);

      // Verify JSON is valid (not corrupted)
      const fileContent = await fs.readFile(testFile, 'utf8');
      expect(() => JSON.parse(fileContent)).not.toThrow();
    });

    test('concurrent reads while writing', async () => {
      await stateManager.write([{ id: '1', name: 'Initial' }]);

      const writePromise = stateManager.update(async items => {
        // Simulate slow operation
        await new Promise(resolve => setTimeout(resolve, 100));
        items.push({ id: '2', name: 'New' });
        return items;
      });

      const readPromises = [];
      for (let i = 0; i < 5; i++) {
        readPromises.push(stateManager.read());
      }

      await Promise.all([writePromise, ...readPromises]);

      // Should complete without errors
      const final = await stateManager.read();
      expect(final.length).toBe(2);
    });
  });

  describe('Error Handling', () => {
    test('throws error on invalid JSON', async () => {
      // Write invalid JSON directly
      await fs.writeFile(testFile, '{invalid json', 'utf8');

      await expect(stateManager.read()).rejects.toThrow();
    });

    test('handles missing file gracefully', async () => {
      await fs.unlink(testFile);

      const data = await stateManager.read();
      expect(Array.isArray(data)).toBe(true);
    });

    test('update callback errors are caught', async () => {
      await expect(
        stateManager.update(() => {
          throw new Error('Test error');
        })
      ).rejects.toThrow('Test error');
    });
  });

  describe('Lock Management', () => {
    test('isLocked detects locked state', async () => {
      const lockfile = require('proper-lockfile');

      // Manually lock the file
      const release = await lockfile.lock(testFile);

      const locked = await stateManager.isLocked();
      expect(locked).toBe(true);

      await release();

      const unlocked = await stateManager.isLocked();
      expect(unlocked).toBe(false);
    });

    test('forceUnlock removes stuck lock', async () => {
      const lockfile = require('proper-lockfile');

      // Create a stuck lock
      await lockfile.lock(testFile);

      await stateManager.forceUnlock();

      // Should be able to write now
      await expect(stateManager.write([])).resolves.not.toThrow();
    });
  });

  describe('Performance', () => {
    test('handles large datasets efficiently', async () => {
      const largeDataset = [];
      for (let i = 0; i < 1000; i++) {
        largeDataset.push({
          id: `service-${i}`,
          name: `Service ${i}`,
          url: `https://service-${i}.example.com`,
          status: 'online'
        });
      }

      const startTime = Date.now();
      await stateManager.write(largeDataset);
      const writeTime = Date.now() - startTime;

      const readStart = Date.now();
      const data = await stateManager.read();
      const readTime = Date.now() - readStart;

      expect(data.length).toBe(1000);
      expect(writeTime).toBeLessThan(1000); // Should write in <1s
      expect(readTime).toBeLessThan(100);   // Should read in <100ms
    });
  });
});
