"use strict";

Object.defineProperty(exports, "__esModule", {
  value: true
});
exports.default = void 0;

var _utils = require("@rivalis/utils");

var _axios = _interopRequireDefault(require("axios"));

var _database = require("../database");

function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }

function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }

/**
 * @typedef Fleet
 * @property {string} id
 * @property {string} name
 * @property {boolean} isLive
 * @property {string} appId
 * @property {number} instances
 * @property {number} connections
 * @property {number} rooms
 * @property {Array<string>} roomTypes
 */

/**
 * @typedef Instance
 * @property {string} id
 * @property {string} name
 * @property {string} fleet
 * @property {string} endpointUrl
 * @property {number} connections
 * @property {number} rooms
 * @property {Array<string>} roomTypes
 * @property {string} appId
 */

/**
 * @typedef Room
 * @property {string} id
 * @property {string} type
 * @property {number} connections
 * @property {string} endpointUrl
 * @property {string} instance
 * @property {string} fleet
 * @property {string} appId
 * 
 */
class FleetService {
  /** @private */

  /** @private */

  /**
   * @private
   * @type {Map<string,import('../database').Fleet>}
   */

  /**
   * @type {Array<Fleet>}
   */

  /**
   * @type {Array<Instance>}
   */
  constructor() {
    _defineProperty(this, "httpClient", _axios.default.create());

    _defineProperty(this, "sync", true);

    _defineProperty(this, "fleetMap", new Map());

    _defineProperty(this, "fleets", []);

    _defineProperty(this, "instances", []);

    _defineProperty(this, "logger", _utils.Logger.getLogger('fleet service'));

    _defineProperty(this, "invokeUpdate", () => this.updateTick());

    _defineProperty(this, "updateTick", async () => {
      let fleetList = [];
      let instanceList = [];

      if (this.sync) {
        this.sync = false;
        const fleets = await _database.Models.Fleets.findAll();
        this.fleetMap.clear();

        for (let fleet of fleets) {
          this.fleetMap.set(fleet.id, fleet.get({
            clone: true
          }));
        }
      }

      for (let fleetId of this.fleetMap.keys()) {
        const {
          id,
          url,
          token,
          appId,
          name
        } = this.fleetMap.get(fleetId);
        /** @type {Fleet} */

        let fleet = {
          id,
          appId,
          name,
          isLive: true,
          connections: 0,
          instances: 0,
          rooms: 0,
          roomTypes: []
        };
        let overview = await this.getFleetOverview(url, token);

        if (overview === null) {
          fleet.isLive = false;
          fleetList.push(fleet);
          continue;
        }

        const {
          connections,
          roomTypes = [],
          instances,
          rooms
        } = overview;
        fleet.instances = instances;
        fleet.rooms = rooms;
        fleet.connections = connections;
        fleet.roomTypes = roomTypes;
        let instList = await this.getFleetInstances(url, token);

        if (instanceList === null) {
          fleet.isLive = false;
        }

        fleetList.push(fleet);
        instList.forEach(record => {
          const {
            id,
            name,
            connections,
            endpointUrl,
            rooms,
            roomTypes
          } = record;
          /** @type {Instance} */

          let instance = {
            id,
            name,
            connections,
            endpointUrl,
            rooms: rooms.length,
            roomTypes,
            appId,
            fleet: fleet.id
          };
          instanceList.push(instance);
        });
      }

      this.fleets = fleetList;
      this.instances = instanceList;
    });

    setInterval(() => {
      try {
        this.updateTick();
      } catch (error) {
        this.logger.error('update failed', error.message);
      }
    }, 10 * 1000);
  }
  /**
   * 
   * @param {string} host 
   * @param {number} port 
   * @param {string} token 
   * @returns {boolean}
   */


  async validate(url, token) {
    let overview = await this.getFleetOverview(url, token);
    return overview !== null ? true : false;
  }
  /**
   * 
   * @param {string} name 
   * @param {string} url 
   * @param {string} token 
   * @returns {import('../database').Fleet}
   */


  async register(name, url, token) {
    let id = this.generateUniqueId();

    let fleet = _database.Models.Fleets.build({
      id,
      name,
      token,
      url
    });

    await fleet.save();
    this.sync = true;
    await this.updateTick();
    return fleet.get();
  }

  async remove(fleetId) {
    if (!this.fleetMap.has(fleetId)) {
      throw new Error(`fleet id=(${fleetId}) does not exist`);
    }

    return (0, _database.getInstance)().transaction(async () => {
      let fleet = await _database.Models.Fleets.findOne({
        where: {
          id: fleetId
        }
      });

      if (fleet === null) {
        throw new Error(`fleet id="${fleetId}" does not exist`);
      }

      let allocatedFleets = this.fleets.filter(f => f.appId === fleet.get().appId);

      if (allocatedFleets.length === 1) {
        throw new Error(`fleet "${fleet.get().name}" can not be disconnected, app id="${fleet.get().appId}" relay on this fleet`);
      }

      fleet.destroy();
    }).then(() => {
      this.sync = true;
      return this.updateTick();
    });
  }

  async assignFleet(fleetId, appId) {
    let fleet = await _database.Models.Fleets.findOne({
      where: {
        id: fleetId
      }
    });

    if (fleet === null) {
      throw new Error(`fleet id="${fleetId}" does not exist`);
    }

    fleet.set('appId', appId);
    await fleet.save();
    this.sync = true;
    return fleet.get();
  }

  async freeFleet(fleetId) {
    let fleet = await _database.Models.Fleets.findOne({
      where: {
        id: fleetId
      }
    });

    if (fleet === null) {
      throw new Error(`fleet id="${fleetId}" does not exist`);
    }

    fleet.set('appId', null);
    await fleet.save();
    this.sync = true;
    return fleet.get();
  }

  async getFleetOverview(url, token) {
    try {
      const {
        data: {
          data
        }
      } = await this.httpClient.get(`${url}/api/stats`, {
        headers: {
          Authorization: token
        }
      });
      return data;
    } catch (error) {
      return null;
    }
  }
  /**
   * 
   * @param {string} url 
   * @param {string} token 
   * @returns {Array}
   */


  async getFleetInstances(url, token) {
    try {
      const {
        data: {
          data
        }
      } = await this.httpClient.get(`${url}/api/instances`, {
        headers: {
          Authorization: token
        }
      });
      return data;
    } catch (error) {
      return null;
    }
  }

  generateUniqueId() {
    let id = (0, _utils.generateId)(4);

    while (this.fleetMap.has(id)) {
      id = (0, _utils.generateId)(4);
    }

    return id;
  }

}
/** @type {FleetService} */


let instance = null;

FleetService.getInstance = () => {
  if (instance === null) {
    instance = new FleetService();
  }

  return instance;
};

var _default = FleetService;
exports.default = _default;