// eslint-disable-next-line max-classes-per-file
import { describe, it, expect } from 'vitest';
import { type NodeJSProcessAccessor, NodeRuntimeEnvironment, type PlatformToOperatingSystemConverter } from '@/infrastructure/RuntimeEnvironment/Node/NodeRuntimeEnvironment';
import { itEachAbsentObjectValue } from '@tests/unit/shared/TestCases/AbsentTests';
import { OperatingSystem } from '@/domain/OperatingSystem';

describe('NodeRuntimeEnvironment', () => {
  describe('constructor', () => {
    describe('throws with missing process', () => {
      itEachAbsentObjectValue((absentValue) => {
        // arrange
        const expectedError = 'missing process';
        const absentProcess = absentValue;
        // act
        const act = () => new NodeRuntimeEnvironmentBuilder()
          .withProcess(absentProcess as never)
          .build();
        // assert
        expect(act).to.throw(expectedError);
      }, { excludeUndefined: true });
    });
  });
  describe('os', () => {
    it('matches specified OS', () => {
      // arrange
      const expectedOs = OperatingSystem.Android;
      const osConverterMock: PlatformToOperatingSystemConverter = () => expectedOs;
      // act
      const environment = new NodeRuntimeEnvironmentBuilder()
        .withOsConverter(osConverterMock)
        .build();
      // assert
      expect(environment.os).to.equal(expectedOs);
    });
    it('uses process platform to determine OS', () => {
      // arrange
      const expectedPlatform: NodeJS.Platform = 'win32';
      let actualPlatform: NodeJS.Platform | undefined;
      const osConverterMock: PlatformToOperatingSystemConverter = (platform) => {
        actualPlatform = platform;
        return undefined;
      };
      const nodeProcessMock = new NodeJSProcessAccessorStub()
        .withPlatform(expectedPlatform);
      // act
      new NodeRuntimeEnvironmentBuilder()
        .withOsConverter(osConverterMock)
        .withProcess(nodeProcessMock)
        .build();
      // assert
      expect(expectedPlatform).to.equal(actualPlatform);
    });
    it('is undefined for unknown platforms', () => {
      // arrange
      const expectedOs = undefined;
      const osConverterMock: PlatformToOperatingSystemConverter = () => undefined;
      // act
      const environment = new NodeRuntimeEnvironmentBuilder()
        .withOsConverter(osConverterMock)
        .build();
      // assert
      expect(environment.os).to.equal(expectedOs);
    });
  });
  describe('isRunningAsDesktopApplication', () => {
    it('is always true', () => {
      // arrange
      const expectedDesktopCondition = true;
      // act
      const environment = new NodeRuntimeEnvironment();
      /// assert
      expect(environment.isRunningAsDesktopApplication).to.equal(expectedDesktopCondition);
    });
  });
  describe('isNonProduction', () => {
    it('identifies development mode', () => {
      // arrange
      const expectedNonProductionCondition = true;
      const nodeProcess = new NodeJSProcessAccessorStub()
        .withNodeEnv('development');
      // act
      const environment = new NodeRuntimeEnvironmentBuilder()
        .withProcess(nodeProcess)
        .build();
      /// assert
      expect(environment.isNonProduction).to.equal(expectedNonProductionCondition);
    });
    it('identifies production mode', () => {
      // arrange
      const expectedNonProductionCondition = false;
      const nodeProcess = new NodeJSProcessAccessorStub()
        .withNodeEnv('production');
      // act
      const environment = new NodeRuntimeEnvironmentBuilder()
        .withProcess(nodeProcess)
        .build();
      /// assert
      expect(environment.isNonProduction).to.equal(expectedNonProductionCondition);
    });
  });
});

class NodeJSProcessAccessorStub implements NodeJSProcessAccessor {
  private nodeEnv?: string = 'development';

  public platform: NodeJS.Platform = 'linux';

  public get env() {
    return {
      NODE_ENV: this.nodeEnv,
    };
  }

  public withNodeEnv(nodeEnv?: string): this {
    this.nodeEnv = nodeEnv;
    return this;
  }

  public withPlatform(platform: NodeJS.Platform): this {
    this.platform = platform;
    return this;
  }
}

class NodeRuntimeEnvironmentBuilder {
  private process?: NodeJSProcessAccessor = new NodeJSProcessAccessorStub();

  private osConverter?: PlatformToOperatingSystemConverter = () => OperatingSystem.Windows;

  public withProcess(process?: NodeJSProcessAccessor): this {
    this.process = process;
    return this;
  }

  public withOsConverter(osConverter?: PlatformToOperatingSystemConverter): this {
    this.osConverter = osConverter;
    return this;
  }

  public build(): NodeRuntimeEnvironment {
    return new NodeRuntimeEnvironment(
      this.process,
      this.osConverter,
    );
  }
}
