/* Copyright CloudLinux Zug GmbH 2010-2017 All Rights Reserved
 *
 * Licensed under CLOUD LINUX LICENSE AGREEMENT
 * http://cloudlinux.com/docs/LICENSE.TXT
 */

import { Row, Cell, Field } from '../utils/row';
import { Validator } from '../utils/validator';
import { Helper } from '../utils/helper';
import { US_RESEND_USER, ViewModes } from '../services/view_modes';
import { AppState } from '../app.service';
import { UsersRepo } from '../repository/users_repo';
import { TranslateClass } from '../utils/pipes/translate';
import { CellsMap, FieldProperties, FormatedValue } from '../utils/types';
import { userProperties } from '../utils/componentProperties/packageProperties';


export class User extends Row {
    id: number | string;
    username: string;
    domain: string;
    reseller: string;
    resellerStatus: boolean = false; // Is it activated reseller? Which have enabled limits.
    userTypeTooltip: string;
    isReseller: boolean = false;
    limits_cpu_all: any = null;
    limits_vmem: any = null;
    limits_pmem: any = null;
    limits_io_all: any = null;
    limits_iops: any = null;
    limits_ep: any = null;
    limits_inodes_hard: any = null;
    limits_inodes_soft: any = null;
    limits_nproc: any = null;
    package: any;
    my_sql_gov_cpu: any;
    my_sql_gov_io: any;
    cagefs: any;
    mysql_gov: any;
    notDefault = {};
    prevResponse: any = {}; // storing all response data, except users array
    prevUserData: any = {}; // storing user data from response
    translator: TranslateClass = new TranslateClass();

    private mysqlLimitsDisabled: boolean = false;
    protected _userType: string;
    get userType(): string {
        return this._userType;
    }
    set userType(value: string) {
      if (typeof value === 'undefined') return;
        this._userType = this.prevUserData.userType = value;
        this.userTypeTooltip = ViewModes.availableModes.find((mode) => {
            return mode.name === this.userType;
        }).title.tooltip;
        this.prevUserData.userTypeTooltip = this.userTypeTooltip;
    }

    get isUnderReseller(): boolean {
        return (this.userType === US_RESEND_USER && this.appState.get('isAdmin'));
    }

    get enabledLimits(): boolean {
        return this.isReseller ? this.resellerStatus : true;
    }

    constructor(response: any,
                i: number,
                private appState: AppState,
                private usersRepo: UsersRepo
    ) {
        super();
        this.mysqlLimitsDisabled = this.appState.get('usersMysqlLimitsDisabled');
        this.loadData(response, i);
    }

    public loadData(response: any, i: number, expand: boolean = false) {
        let userData = response.users[i];
        if (!userData.hasOwnProperty('id') && userData.isReseller) userData.id = 'N/A';
        Object.assign(this.prevResponse, response);
        delete this.prevResponse.users; // prevent memory leak
        Helper.recursivelyAssign2(this.prevUserData, userData);
        if (userData.hasOwnProperty('limits') && !Object.keys(userData.limits).length) {
            this.prevUserData.limits = {};
        }
        return this.loadDataAlgorythm(this.prevResponse, this.prevUserData, expand);
    }
    private loadDataAlgorythm(response: any, userData: any, expand: boolean = false) {
        if (typeof userData.id !== 'undefined') {
            this.id = (userData.id === 0) ? 'DEFAULT' : userData.id;
        } else {
            this.id = 'N/A';
        }
        this.username = userData.hasOwnProperty('username') ? userData.username : 'N/A';
        this.reseller = userData.reseller;
        this.resellerStatus = (userData.hasOwnProperty('resellerStatus'))
            ? userData.resellerStatus : false;
        this.isReseller = userData.hasOwnProperty('isReseller')
            ? userData.isReseller : false;
        this.userType = userData.userType;
        this.domain   = userData.hasOwnProperty('domain') ? userData.domain : 'N/A';
        if (userData.hasOwnProperty('limits') && Object.keys(userData.limits).length) {
            if (userData.limits.hasOwnProperty('cpu')) {
                this.limits_cpu_all     = this.prepareParamForTemplate(
                    userData.limits.cpu.all, 'limits_cpu_all'
                );
                this.limits_cpu_all     = Helper.reformatValue(this.limits_cpu_all, 'percents');

                this.my_sql_gov_cpu     = this.getMysqlGovLimits(response, userData, 'cpu');
            }
            if (userData.limits.hasOwnProperty('vmem') && !userData.isReseller) {
                this.limits_vmem        = this.prepareParamForTemplate(
                    userData.limits.vmem, 'limits_vmem'
                );
                this.limits_vmem        = Helper.reformatValue(this.limits_vmem, 'bytes');
            }
            if (userData.limits.hasOwnProperty('pmem')) {
                this.limits_pmem        = this.prepareParamForTemplate(
                    userData.limits.pmem, 'limits_pmem'
                );
                this.limits_pmem        = Helper.reformatValue(this.limits_pmem, 'bytes');
            }
            if (userData.limits.hasOwnProperty('io')) {
                this.limits_io_all      = Helper.reformatValue(
                    this.prepareParamForTemplate(userData.limits.io.all, 'limits_io_all'), 'kbinsec'
                );
                this.my_sql_gov_io      = Helper.reformatValue(
                    this.getMysqlGovLimits(response, userData, 'io'), 'kbinsec'
                );
            }
            if (userData.limits.hasOwnProperty('iops')) {
                this.limits_iops        = this.prepareParamForTemplate(
                    userData.limits.iops, 'limits_iops'
                );
            }
            if (userData.limits.hasOwnProperty('ep')) {
                this.limits_ep = this.prepareParamForTemplate(userData.limits.ep, 'limits_ep');
            }
            if (userData.limits.hasOwnProperty('nproc')) {
                this.limits_nproc       = this.prepareParamForTemplate(
                    userData.limits.nproc, 'limits_nproc'
                );
            }
            if (userData.limits.hasOwnProperty('inodes')) {
                this.limits_inodes_hard = this.prepareParamForTemplate(
                    userData.limits.inodes.hard, 'limits_inodes_hard'
                );
                this.limits_inodes_soft = this.prepareParamForTemplate(
                    userData.limits.inodes.soft, 'limits_inodes_soft'
                );
            }
        } else {
            this.resetLimits();
        }

        // TODO: restore // this.package = userData.package;
        this.package = (userData.hasOwnProperty('package')) ? userData.package : 'N/A';
        if (!userData.isReseller) {
            this.cagefs = this.getCagefsLimits(response, userData);
            this.mysql_gov = userData.mySqlGov;
        }

        this.cells  = this.prepareCellForUser(response, expand, userData.isReseller);
    }

    public prepareCellForUser(
        response: any,
        expand: boolean,
        isReseller: boolean = false
    ): CellsMap {
        let cells: CellsMap = {};
        let domain_cells: Field[] = [new Field({value: this.domain})];
        if (!isReseller && this.appState.get('isAdmin')) {
            domain_cells = domain_cells.concat(
                new Field({value: 'MySQL', editing: false, hidden: !expand}),
                new Field({value: 'CageFS', editing: false, hidden: !expand})
            );
        }
        for (let packageFieldsKey of Object.keys(userProperties)) {
            let value = this[packageFieldsKey];
            if (value !== null) {
                let params: FieldProperties = <FieldProperties>userProperties[packageFieldsKey];
                params.value = value;
                cells[packageFieldsKey] = new Cell([new Field(params)]);
            }
        }
        cells['domain'] = new Cell(domain_cells);

        if (this.limits_cpu_all !== null) {
            let cpu_all_cells = [
                new Field({
                    value: this.limits_cpu_all,
                    editing: true,
                    paramName: 'speed',
                    requirements: [
                        {'m': Validator.isRequired},
                        {'m': Validator.isCpuValue},
                    ],
                    placeHolder: 'speed',
                    tooltipText: 'Use postfix if it is needed. ' +
                    'For example: 1024[Mhz|Ghz|%]. By default used "%".'
                })
            ];
            if (!isReseller && this.appState.get('isAdmin')) {
                cpu_all_cells = cpu_all_cells.concat(
                    new Field({
                        value: this.my_sql_gov_cpu,
                        editing: (!(response.mySqlGov === 'error'
                            || response.mySqlGov === 'disabled' || this.mysql_gov === 'ignored')
                            && !this.mysqlLimitsDisabled),
                        hidden: !expand,
                        paramName: 'mysql-cpu',
                        requirements: [{'m': Validator.isPositiveNumber}],
                        isProtected: !this.mysqlLimitsDisabled,
                        altValue: (this.mysql_gov === 'watched' || response.mySqlGov === 'disabled'
                            || response.mySqlGov === 'error')
                            ? Helper.reformatValue(this.my_sql_gov_cpu, 'percents').value
                            : this.translator.transform('current user is not monitored')
                    }).setLongText(true),
                    new Field({
                        value: this.cagefs,
                        hidden: !expand,
                        paramName: 'cagefs',
                        altValue: (this.cagefs === 'enabled' || response.cageFS === 'error')
                            ? this.translator.transform(this.cagefs)
                            : this.translator.transform('is disabled for this user')
                    }).setLongText(true)
                );
            }
            cells['limits_cpu_all'] = new Cell(cpu_all_cells);
        }

        if (this.limits_io_all !== null) {
            let io_all_cells = [
                new Field({
                    value: this.limits_io_all,
                    editing: true,
                    paramName: 'io',
                    requirements: [{'m': Validator.isSpeed}],
                    placeHolder: 'io',
                    tooltipText: 'Use postfix if it is needed. ' +
                    'For example: 1024[KB/s|MB/s|G/s]. By default used "KB/s".'
                })
            ];
            if (!isReseller && this.appState.get('isAdmin')) {
                io_all_cells.push(
                    new Field({
                        value: this.my_sql_gov_io,
                        editing: (!(response.mySqlGov === 'error'
                            || response.mySqlGov === 'disabled' || this.mysql_gov === 'ignored')
                            && !this.mysqlLimitsDisabled),
                        hidden: !expand,
                        paramName: 'mysql-io',
                        requirements: [{'m': Validator.isSpeedMysql}],
                        isProtected: !this.mysqlLimitsDisabled,
                        tooltipText: 'Use postfix if it is needed. ' +
                        'For example: 1024[MB/s|G/s]. By default used "MB/s".'
                    })
                );
            }
            cells['limits_io_all'] = new Cell(io_all_cells);
        }
        return cells;
    }

    private getMysqlGovLimits(response, user, param){
        let my_sql_gov = '';
        if (response.mySqlGov !== 'disabled') {
            if (response.mySqlGov === 'error' && param === 'cpu'
                && typeof response.errors !== 'undefined'
            ) {
                my_sql_gov = this.translator.transform(response.errors['mySqlGov']);
            } else if (user.mySqlGov === 'watched') {
                my_sql_gov = this.prepareParamForTemplate(
                    user.limits[param].mysql, `limits_mysql-${param}`
                );
            } else if (param === 'cpu') {
                my_sql_gov = user.mySqlGov;
            }
        } else if (param === 'cpu') {
            my_sql_gov = this.translator.transform('MySqlGov is disabled');
        }
        return my_sql_gov;
    }

    private getCagefsLimits(response, user){
        let cagefs = '';
        if (response.cageFS === 'error') {
            cagefs = response.errors['cageFS'];
        } else {
            cagefs = user.cageFS;
        }
        return cagefs;
    }

    private prepareParamForTemplate (val: any, param = null) {
        if (typeof val !== 'undefined' && val.toString().substr(0, 1) === '*') {
            this.notDefault[param] = true;
            val = val.toString().substr(1, val.length - 1);
        } else {
            delete this.notDefault[param];
        }
        return val;
    }

    private resetLimits() {
        this.limits_cpu_all = null;
        this.limits_vmem = null;
        this.limits_pmem = null;
        this.limits_io_all = null;
        this.limits_iops = null;
        this.limits_ep = null;
        this.limits_inodes_hard = null;
        this.limits_inodes_soft = null;
        this.limits_nproc = null;
    }

    validate(column: string, lineNumber: number){
        let line = this.getLine(column, lineNumber);
        if (!line) return;
        let stateOfErrorBeforeValidation = line.error;
        line.resetErrors();

        for (let validator of line.requirements) {
            let value = line.stringValue;

            if (validator.p) {
                validator.p.unshift(value.replace(/ /g, ''));
            } else {
                validator.p = [value.replace(/ /g, '')];
            }

            let result = validator.m.apply(null, validator.p);
            if (!result['isOK']) {
                line.addError(result['message']);
            }
        }
        if (line.errorMessages.length) {
            line.error = true;
            this.hasError = true;
            let translate = new TranslateClass();
            line.tooltipTextError = line.errorMessages.map((item) => {
                return translate.transform(item);
            }).join('. ') + '.';
        } else {
            line.tooltipTextError = '';
            if (!this.hasRowError()) {
                this.resetError();
            }
        }
    }

  private parseEditingParams() {
    let res = {};
    let inodes = {};
    for (let field in this.cells) {
      for (let line of this.cells[field].lines) {
        if (line.editing && line.hasChanged) {
            let value: string = line.value.hasOwnProperty('rawValue') ?
                (<FormatedValue>line.value).value : <string>line.value;
          if (['inodes_soft', 'inodes_hard'].includes(line.paramName)){
              inodes[line.paramName] = value.replace(/ /g, '');
          } else {
              res[line.paramName] = (value !== '') ? value.replace(/ /g, '') : '0';
          }
        }
      }
    }
    if (Object.keys(inodes).length) {
        let inodes_soft = inodes['inodes_soft'] || this.cells['limits_inodes_soft'].lines[0].value;
        let inodes_hard = inodes['inodes_hard'] || this.cells['limits_inodes_hard'].lines[0].value;
        res['inodes'] = `${inodes_soft},${inodes_hard}`;
    }
    return res;
  }


  save() {
      return new Promise((resolve, reject) => {
          // Saving of main limits.
          let paramsForSave = this.parseEditingParams();

          let hasChanges = Object.keys(paramsForSave).length;

          // TODO make this for all limits and delete "/1024" when LU-365 is ready
          if (typeof paramsForSave['io'] !== 'undefined' && /^\d+(\.\d+)?(KB\/s|MB\/s|G\/s)+$/i.test(paramsForSave['io'])) {
            paramsForSave['io'] = (Helper.reformatToDirtyValue(paramsForSave['io']) / 1024).toFixed();
          }
          if (typeof paramsForSave['mysql-io'] !== 'undefined' && /^\d+(\.\d+)?(KB\/s|MB\/s|G\/s)+$/i.test(paramsForSave['mysql-io'])) {
            paramsForSave['mysql-io'] = (Helper.reformatToDirtyValue(paramsForSave['mysql-io']) / 1048576).toFixed();
          }

          if (hasChanges) {
            this.loadingStage = true;
            this.usersRepo.saveUser(this, paramsForSave)
              .then(res => {
                resolve(res);
              })
              .catch(() => {
                reject();
              });
          } else {
            reject();
          }
      });
  }


}
