import * as plugins from './smartnginx.plugins';
import * as paths from './smartnginx.paths';
import * as snippets from './smartnginx.snippets';
import { NginxHost } from './smartnginx.classes.nginxhost';
import { NginxProcess } from './smartnginx.classes.nginxprocess';
import { IHostConfig } from './interfaces/hostconfig';

export interface ISmartNginxContructorOptions {
  logger?: plugins.smartlog.Smartlog;
  defaultProxyUrl: string;
}

/**
 * main class that manages a NginxInstance
 */
export class SmartNginx {
  public options: ISmartNginxContructorOptions;
  public logger: plugins.smartlog.Smartlog;

  // the objectmaps
  private deployedHosts = new plugins.lik.Objectmap<NginxHost>();
  private hostCandidates = new plugins.lik.Objectmap<NginxHost>();

  public nginxProcess: NginxProcess = new NginxProcess(this);
  constructor(optionsArg: ISmartNginxContructorOptions) {
    this.options = optionsArg;
    this.options.logger
      ? (this.logger = this.options.logger)
      : (this.logger = plugins.smartlog.defaultLogger);
  }

  // ===================
  // interact with Hosts
  // ===================

  /**
   * add a host
   * @param nginxHostArg
   */
  public addHostCandidate(optionsArg: IHostConfig): NginxHost {
    const nginxHost = new NginxHost(this, optionsArg);
    this.hostCandidates.add(nginxHost);
    return nginxHost;
  }

  /**
   * Gets a NginxHost by hostname
   * @param hostNameArg
   */
  public getDeployedNginxHostByHostName(hostNameArg: string): NginxHost {
    return this.deployedHosts.find(nginxHost => {
      return nginxHost.hostName === hostNameArg;
    });
  }
  /**
   * listHosts
   */
  public async listDeployedHosts(): Promise<NginxHost[]> {
    return this.deployedHosts.getArray();
  }

  /**
   * remove a Host
   * @param nginxHostArg
   */
  public async removeDeployedHost(nginxHostArg: NginxHost) {
    if (this.hostCandidates.isEmpty()) {
      this.deployedHosts.forEach(hostArg => {
        this.hostCandidates.add(hostArg);
      });
    }
    this.hostCandidates.remove(nginxHostArg);
    this.deploy();
  }

  /**
   * check wether there has been a diverging host configuration
   * this function will only redeploy the nginx configuration in case there has been a change
   */
  private async areHostsDiverged(): Promise<boolean> {
    let hostCounter = 0;
    let unfoundHosts = 0;
    await this.hostCandidates.forEach(async hostCandidateArg => {
      let foundHost = false;
      await this.deployedHosts.forEach(async deployedHostArg => {
        if (
          hostCandidateArg.hostName === deployedHostArg.hostName &&
          hostCandidateArg.destination === deployedHostArg.destination &&
          hostCandidateArg.destinationPort === deployedHostArg.destinationPort
        ) {
          hostCounter++;
          foundHost = true;
        }
      });
      if (!foundHost) {
        unfoundHosts++;
      }
    });
    return (
      this.deployedHosts.getArray().length !== this.hostCandidates.getArray().length ||
      hostCounter !== this.deployedHosts.getArray().length ||
      unfoundHosts !== 0
    );
  }

  /**
   * deploy the current stack and restart nginx
   */
  public async deploy() {
    if (await this.areHostsDiverged()) {
      this.logger.log('ok', `hosts have diverged, trigger config deployment and nginx reload!`);
      this.deployedHosts.wipe();
      this.deployedHosts.addArray(this.hostCandidates.getArray());
      this.hostCandidates.wipe();

      // write base config
      plugins.smartfile.fs.ensureDirSync(paths.nginxConfigDirPath);
      plugins.smartfile.memory.toFsSync(snippets.getBaseConfigString(this.options.defaultProxyUrl), paths.nginxConfFile);

      // write standard self signed certificate
      const selfsignedCert = plugins.selfsigned.generate([{ name: 'commonName', value: 'selfsigned.git.zone' }], { days: 365});
      
      // deploy hosts
      plugins.smartfile.fs.ensureDirSync(paths.nginxHostDirPath);
      plugins.smartfile.memory.toFsSync(selfsignedCert.private, plugins.path.join(paths.nginxHostDirPath, './default.private.pem'));
      plugins.smartfile.memory.toFsSync(selfsignedCert.cert, plugins.path.join(paths.nginxHostDirPath, './default.public.pem'));
      for (const host of this.deployedHosts.getArray()) {
        await host.deploy();
        this.logger.log('info', `Host ${host.hostName} deployed!`);
      }
      this.nginxProcess.reloadConfig();
    } else {
      this.logger.log('info', `hosts have not diverged, skipping nginx reload`);
      this.hostCandidates.wipe();
    }
  }

  /**
   * stops the smartnginx instance
   */
  public async stop() {
    if (this.nginxProcess) {
      await this.nginxProcess.stop();
    }
  }
}
