const path = require('path');
const ConfigManager = require('../src/configs/index');

describe('ConfigManager', () => {
  const configDir = path.resolve(__dirname, '../src/configs');
  let cm;

  beforeEach(() => {
    cm = new ConfigManager(configDir);
  });

  // ── getConfig() ──────────────────────────────────────────────────────
  describe('getConfig()', () => {
    it('should load and parse a known JSON config file', () => {
      const config = cm.getConfig('baseConfig');
      expect(config).toBeDefined();
      expect(typeof config).toBe('object');
    });

    it('should return the same content on successive calls', () => {
      const a = cm.getConfig('baseConfig');
      const b = cm.getConfig('baseConfig');
      expect(a).toEqual(b);
    });

    it('should throw when the config file does not exist', () => {
      expect(() => cm.getConfig('nonExistentConfig_xyz'))
        .toThrow(/Failed to load config/);
    });

    it('should throw a descriptive message including the config name', () => {
      expect(() => cm.getConfig('missing'))
        .toThrow("Failed to load config 'missing'");
    });
  });

  // ── hasConfig() ──────────────────────────────────────────────────────
  describe('hasConfig()', () => {
    it('should return true for a config that exists', () => {
      expect(cm.hasConfig('baseConfig')).toBe(true);
    });

    it('should return false for a config that does not exist', () => {
      expect(cm.hasConfig('doesNotExist_abc')).toBe(false);
    });
  });

  // ── getAvailableConfigs() ────────────────────────────────────────────
  describe('getAvailableConfigs()', () => {
    it('should return an array of strings', () => {
      const configs = cm.getAvailableConfigs();
      expect(Array.isArray(configs)).toBe(true);
      configs.forEach(name => expect(typeof name).toBe('string'));
    });

    it('should include known config names without .json extension', () => {
      const configs = cm.getAvailableConfigs();
      expect(configs).toContain('baseConfig');
      expect(configs).toContain('measurement');
    });

    it('should not include .json extension in returned names', () => {
      const configs = cm.getAvailableConfigs();
      configs.forEach(name => {
        expect(name).not.toMatch(/\.json$/);
      });
    });

    it('should throw when pointed at a non-existent directory', () => {
      const bad = new ConfigManager('/tmp/nonexistent_dir_xyz_123');
      expect(() => bad.getAvailableConfigs()).toThrow(/Failed to read config directory/);
    });
  });

  // ── buildConfig() ────────────────────────────────────────────────────
  describe('buildConfig()', () => {
    it('should return an object with general and functionality sections', () => {
      const uiConfig = { name: 'TestNode', unit: 'bar', enableLog: true, logLevel: 'debug' };
      const result = cm.buildConfig('measurement', uiConfig, 'node-id-1');
      expect(result).toHaveProperty('general');
      expect(result).toHaveProperty('functionality');
    });

    it('should populate general.name from uiConfig.name', () => {
      const uiConfig = { name: 'MySensor' };
      const result = cm.buildConfig('measurement', uiConfig, 'id-1');
      expect(result.general.name).toBe('MySensor');
    });

    it('should default general.name to nodeName when uiConfig.name is empty', () => {
      const result = cm.buildConfig('measurement', {}, 'id-1');
      expect(result.general.name).toBe('measurement');
    });

    it('should set general.id from the nodeId argument', () => {
      const result = cm.buildConfig('valve', {}, 'node-42');
      expect(result.general.id).toBe('node-42');
    });

    it('should default unit to unitless', () => {
      const result = cm.buildConfig('valve', {}, 'id-1');
      expect(result.general.unit).toBe('unitless');
    });

    it('should default logging.enabled to true when enableLog is undefined', () => {
      const result = cm.buildConfig('valve', {}, 'id-1');
      expect(result.general.logging.enabled).toBe(true);
    });

    it('should respect enableLog = false', () => {
      const result = cm.buildConfig('valve', { enableLog: false }, 'id-1');
      expect(result.general.logging.enabled).toBe(false);
    });

    it('should default logLevel to info', () => {
      const result = cm.buildConfig('valve', {}, 'id-1');
      expect(result.general.logging.logLevel).toBe('info');
    });

    it('should set functionality.softwareType to lowercase nodeName', () => {
      const result = cm.buildConfig('Valve', {}, 'id-1');
      expect(result.functionality.softwareType).toBe('valve');
    });

    it('should default positionVsParent to atEquipment', () => {
      const result = cm.buildConfig('valve', {}, 'id-1');
      expect(result.functionality.positionVsParent).toBe('atEquipment');
    });

    it('should set distance when hasDistance is true', () => {
      const result = cm.buildConfig('valve', { hasDistance: true, distance: 5.5 }, 'id-1');
      expect(result.functionality.distance).toBe(5.5);
    });

    it('should set distance to undefined when hasDistance is false', () => {
      const result = cm.buildConfig('valve', { hasDistance: false, distance: 5.5 }, 'id-1');
      expect(result.functionality.distance).toBeUndefined();
    });

    // ── asset section ──────────────────────────────────────────────────
    it('should not include asset section when no asset fields provided', () => {
      const result = cm.buildConfig('valve', {}, 'id-1');
      expect(result.asset).toBeUndefined();
    });

    it('should include asset section when supplier is provided', () => {
      const result = cm.buildConfig('valve', { supplier: 'Siemens' }, 'id-1');
      expect(result.asset).toBeDefined();
      expect(result.asset.supplier).toBe('Siemens');
    });

    it('should populate asset defaults for missing optional fields', () => {
      const result = cm.buildConfig('valve', { supplier: 'ABB' }, 'id-1');
      expect(result.asset.category).toBe('sensor');
      expect(result.asset.type).toBe('Unknown');
      expect(result.asset.model).toBe('Unknown');
    });

    // ── domainConfig merge ─────────────────────────────────────────────
    it('should merge domainConfig sections into the result', () => {
      const domain = { scaling: { enabled: true, factor: 2 } };
      const result = cm.buildConfig('measurement', {}, 'id-1', domain);
      expect(result.scaling).toEqual({ enabled: true, factor: 2 });
    });

    it('should handle empty domainConfig gracefully', () => {
      const result = cm.buildConfig('measurement', {}, 'id-1', {});
      expect(result).toHaveProperty('general');
      expect(result).toHaveProperty('functionality');
    });
  });

  // ── createEndpoint() ─────────────────────────────────────────────────
  describe('createEndpoint()', () => {
    it('should return a JavaScript string containing the node name', () => {
      const script = cm.createEndpoint('baseConfig');
      expect(typeof script).toBe('string');
      expect(script).toContain('baseConfig');
      expect(script).toContain('window.EVOLV');
    });

    it('should throw for a non-existent config', () => {
      expect(() => cm.createEndpoint('doesNotExist_xyz'))
        .toThrow(/Failed to create endpoint/);
    });
  });

  // ── getBaseConfig() ──────────────────────────────────────────────────
  describe('getBaseConfig()', () => {
    it('should load the baseConfig.json file', () => {
      const base = cm.getBaseConfig();
      expect(base).toBeDefined();
      expect(typeof base).toBe('object');
    });
  });
});
